wahwah 1.0.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/wahwah/asf/object.rb +7 -12
- data/lib/wahwah/asf_tag.rb +199 -198
- data/lib/wahwah/errors.rb +2 -1
- data/lib/wahwah/flac/block.rb +8 -14
- data/lib/wahwah/flac/streaminfo_block.rb +5 -5
- data/lib/wahwah/flac_tag.rb +65 -63
- data/lib/wahwah/helper.rb +8 -8
- data/lib/wahwah/id3/comment_frame_body.rb +1 -1
- data/lib/wahwah/id3/frame.rb +57 -61
- data/lib/wahwah/id3/frame_body.rb +6 -7
- data/lib/wahwah/id3/image_frame_body.rb +5 -5
- data/lib/wahwah/id3/text_frame_body.rb +1 -1
- data/lib/wahwah/id3/v1.rb +59 -58
- data/lib/wahwah/id3/v2.rb +42 -35
- data/lib/wahwah/id3/v2_header.rb +4 -4
- data/lib/wahwah/lazy_read.rb +40 -0
- data/lib/wahwah/mp3/mpeg_frame_header.rb +39 -37
- data/lib/wahwah/mp3/vbri_header.rb +2 -2
- data/lib/wahwah/mp3/xing_header.rb +4 -4
- data/lib/wahwah/mp3_tag.rb +52 -51
- data/lib/wahwah/mp4/atom.rb +30 -34
- data/lib/wahwah/mp4_tag.rb +102 -91
- data/lib/wahwah/ogg/flac_tag.rb +3 -3
- data/lib/wahwah/ogg/opus_tag.rb +3 -3
- data/lib/wahwah/ogg/packets.rb +2 -2
- data/lib/wahwah/ogg/page.rb +3 -3
- data/lib/wahwah/ogg/vorbis_comment.rb +15 -15
- data/lib/wahwah/ogg/vorbis_tag.rb +2 -2
- data/lib/wahwah/ogg_tag.rb +34 -34
- data/lib/wahwah/riff/chunk.rb +11 -15
- data/lib/wahwah/riff_tag.rb +81 -79
- data/lib/wahwah/tag.rb +22 -10
- data/lib/wahwah/version.rb +1 -1
- data/lib/wahwah.rb +58 -53
- metadata +28 -14
data/lib/wahwah/id3/v2.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'forwardable'
|
4
|
-
|
5
3
|
module WahWah
|
6
4
|
module ID3
|
7
5
|
class V2 < Tag
|
@@ -14,47 +12,56 @@ module WahWah
|
|
14
12
|
end
|
15
13
|
|
16
14
|
private
|
17
|
-
def parse
|
18
|
-
@file_io.rewind
|
19
|
-
@header = V2Header.new(@file_io)
|
20
15
|
|
21
|
-
|
16
|
+
def parse
|
17
|
+
@file_io.rewind
|
18
|
+
@header = V2Header.new(@file_io)
|
22
19
|
|
23
|
-
|
24
|
-
frame = ID3::Frame.new(@file_io, major_version)
|
25
|
-
next unless frame.valid?
|
20
|
+
return unless valid?
|
26
21
|
|
27
|
-
|
28
|
-
|
29
|
-
end
|
22
|
+
until end_of_tag?
|
23
|
+
frame = ID3::Frame.new(@file_io, major_version)
|
30
24
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
case name
|
36
|
-
when :comment
|
37
|
-
# Because there may be more than one comment frame in each tag,
|
38
|
-
# so push it into a array.
|
39
|
-
@comments.push(value)
|
40
|
-
when :image
|
41
|
-
# Because there may be more than one image frame in each tag,
|
42
|
-
# so push it into a array.
|
43
|
-
@images.push(value)
|
44
|
-
when :track, :disc
|
45
|
-
# Track and disc value may be extended with a "/" character
|
46
|
-
# and a numeric string containing the total numer.
|
47
|
-
count, total_count = value.split('/', 2)
|
48
|
-
instance_variable_set("@#{name}", count)
|
49
|
-
instance_variable_set("@#{name}_total", total_count) unless total_count.nil?
|
50
|
-
else
|
51
|
-
instance_variable_set("@#{name}", value)
|
25
|
+
unless frame.valid?
|
26
|
+
frame.skip
|
27
|
+
next
|
52
28
|
end
|
29
|
+
|
30
|
+
update_attribute(frame)
|
53
31
|
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def update_attribute(frame)
|
35
|
+
name = frame.name
|
54
36
|
|
55
|
-
|
56
|
-
|
37
|
+
case name
|
38
|
+
when :comment
|
39
|
+
# Because there may be more than one comment frame in each tag,
|
40
|
+
# so push it into a array.
|
41
|
+
@comments.push(frame.value)
|
42
|
+
when :image
|
43
|
+
# Because there may be more than one image frame in each tag,
|
44
|
+
# so push it into a array.
|
45
|
+
@images_data.push(frame)
|
46
|
+
frame.skip
|
47
|
+
when :track, :disc
|
48
|
+
# Track and disc value may be extended with a "/" character
|
49
|
+
# and a numeric string containing the total numer.
|
50
|
+
count, total_count = frame.value.split("/", 2)
|
51
|
+
instance_variable_set("@#{name}", count)
|
52
|
+
instance_variable_set("@#{name}_total", total_count) unless total_count.nil?
|
53
|
+
else
|
54
|
+
instance_variable_set("@#{name}", frame.value)
|
57
55
|
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def end_of_tag?
|
59
|
+
size <= @file_io.pos || file_size <= @file_io.pos
|
60
|
+
end
|
61
|
+
|
62
|
+
def parse_image_data(image_frame)
|
63
|
+
image_frame.value
|
64
|
+
end
|
58
65
|
end
|
59
66
|
end
|
60
67
|
end
|
data/lib/wahwah/id3/v2_header.rb
CHANGED
@@ -10,9 +10,9 @@ module WahWah
|
|
10
10
|
# ID3v2 flags %abc00000
|
11
11
|
# ID3v2 size 4 * %0xxxxxxx
|
12
12
|
class V2Header
|
13
|
-
TAG_ID =
|
13
|
+
TAG_ID = "ID3"
|
14
14
|
HEADER_SIZE = 10
|
15
|
-
HEADER_FORMAT =
|
15
|
+
HEADER_FORMAT = "A3CxB8B*"
|
16
16
|
|
17
17
|
attr_reader :major_version, :size
|
18
18
|
|
@@ -34,7 +34,7 @@ module WahWah
|
|
34
34
|
# Size of padding $xx xx xx xx
|
35
35
|
|
36
36
|
# Skip extended_header
|
37
|
-
extended_header_size = Helper.id3_size_caculate(file_io.read(4).
|
37
|
+
extended_header_size = Helper.id3_size_caculate(file_io.read(4).unpack1("B32"))
|
38
38
|
file_io.seek(extended_header_size - 4, IO::SEEK_CUR)
|
39
39
|
end
|
40
40
|
end
|
@@ -46,7 +46,7 @@ module WahWah
|
|
46
46
|
# The second bit in flags byte indicates whether or not the header
|
47
47
|
# is followed by an extended header.
|
48
48
|
def has_extended_header?
|
49
|
-
@flags[1] ==
|
49
|
+
@flags[1] == "1"
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WahWah
|
4
|
+
module LazyRead
|
5
|
+
def self.prepended(base)
|
6
|
+
base.class_eval do
|
7
|
+
attr_reader :size
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(file_io, *arg)
|
12
|
+
@file_io = file_io
|
13
|
+
super(*arg)
|
14
|
+
@position = @file_io.pos
|
15
|
+
@data = get_data if @file_io.is_a?(StringIO)
|
16
|
+
end
|
17
|
+
|
18
|
+
def data
|
19
|
+
if @file_io.closed? && @file_io.is_a?(File)
|
20
|
+
@file_io = File.open(@file_io.path)
|
21
|
+
@data = get_data
|
22
|
+
@file_io.close
|
23
|
+
end
|
24
|
+
|
25
|
+
@data ||= get_data
|
26
|
+
end
|
27
|
+
|
28
|
+
def skip
|
29
|
+
@file_io.seek(@position)
|
30
|
+
@file_io.seek(size, IO::SEEK_CUR)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def get_data
|
36
|
+
@file_io.seek(@position)
|
37
|
+
@file_io.read(size)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -46,41 +46,41 @@ module WahWah
|
|
46
46
|
HEADER_SIZE = 4
|
47
47
|
|
48
48
|
FRAME_BITRATE_INDEX = {
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
"MPEG1 layer1" => [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0],
|
50
|
+
"MPEG1 layer2" => [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],
|
51
|
+
"MPEG1 layer3" => [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
53
|
+
"MPEG2 layer1" => [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0],
|
54
|
+
"MPEG2 layer2" => [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
|
55
|
+
"MPEG2 layer3" => [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
|
57
|
+
"MPEG2.5 layer1" => [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0],
|
58
|
+
"MPEG2.5 layer2" => [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
|
59
|
+
"MPEG2.5 layer3" => [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0]
|
60
60
|
}
|
61
61
|
|
62
|
-
VERSIONS_INDEX = [
|
63
|
-
LAYER_INDEX = [nil,
|
64
|
-
CHANNEL_MODE_INDEX = [
|
62
|
+
VERSIONS_INDEX = ["MPEG2.5", nil, "MPEG2", "MPEG1"]
|
63
|
+
LAYER_INDEX = [nil, "layer3", "layer2", "layer1"]
|
64
|
+
CHANNEL_MODE_INDEX = ["Stereo", "Joint Stereo", "Dual Channel", "Single Channel"]
|
65
65
|
|
66
66
|
SAMPLE_RATE_INDEX = {
|
67
|
-
|
68
|
-
|
69
|
-
|
67
|
+
"MPEG1" => [44100, 48000, 32000],
|
68
|
+
"MPEG2" => [22050, 24000, 16000],
|
69
|
+
"MPEG2.5" => [11025, 12000, 8000]
|
70
70
|
}
|
71
71
|
|
72
72
|
SAMPLES_PER_FRAME_INDEX = {
|
73
|
-
|
74
|
-
|
75
|
-
|
73
|
+
"MPEG1 layer1" => 384,
|
74
|
+
"MPEG1 layer2" => 1152,
|
75
|
+
"MPEG1 layer3" => 1152,
|
76
76
|
|
77
|
-
|
78
|
-
|
79
|
-
|
77
|
+
"MPEG2 layer1" => 384,
|
78
|
+
"MPEG2 layer2" => 1152,
|
79
|
+
"MPEG2 layer3" => 576,
|
80
80
|
|
81
|
-
|
82
|
-
|
83
|
-
|
81
|
+
"MPEG2.5 layer1" => 384,
|
82
|
+
"MPEG2.5 layer2" => 1152,
|
83
|
+
"MPEG2.5 layer3" => 576
|
84
84
|
}
|
85
85
|
|
86
86
|
attr_reader :version, :layer, :frame_bitrate, :channel_mode, :sample_rate
|
@@ -95,13 +95,14 @@ module WahWah
|
|
95
95
|
break if file_io.eof?
|
96
96
|
|
97
97
|
header = file_io.read(HEADER_SIZE)
|
98
|
-
sync_bits = header.
|
98
|
+
sync_bits = header.unpack1("B11")
|
99
99
|
|
100
|
-
if sync_bits == "
|
101
|
-
@header = header.
|
100
|
+
if sync_bits == ("1" * 11).b
|
101
|
+
@header = header.unpack1("B*")
|
102
102
|
@position = offset
|
103
103
|
|
104
|
-
parse
|
104
|
+
parse
|
105
|
+
break
|
105
106
|
end
|
106
107
|
|
107
108
|
offset += 1
|
@@ -127,15 +128,16 @@ module WahWah
|
|
127
128
|
end
|
128
129
|
|
129
130
|
private
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
131
|
+
|
132
|
+
def parse
|
133
|
+
return unless valid?
|
134
|
+
|
135
|
+
@version = VERSIONS_INDEX[@header[11..12].to_i(2)]
|
136
|
+
@layer = LAYER_INDEX[@header[13..14].to_i(2)]
|
137
|
+
@frame_bitrate = FRAME_BITRATE_INDEX[kind]&.fetch(@header[16..19].to_i(2))
|
138
|
+
@channel_mode = CHANNEL_MODE_INDEX[@header[24..25].to_i(2)]
|
139
|
+
@sample_rate = SAMPLE_RATE_INDEX[@version]&.fetch(@header[20..21].to_i(2))
|
140
|
+
end
|
139
141
|
end
|
140
142
|
end
|
141
143
|
end
|
@@ -30,7 +30,7 @@ module WahWah
|
|
30
30
|
# you can calculate the length of this field.
|
31
31
|
class VbriHeader
|
32
32
|
HEADER_SIZE = 32
|
33
|
-
HEADER_FORMAT =
|
33
|
+
HEADER_FORMAT = "A4x6NN"
|
34
34
|
|
35
35
|
attr_reader :frames_count, :bytes_count
|
36
36
|
|
@@ -40,7 +40,7 @@ module WahWah
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def valid?
|
43
|
-
@id ==
|
43
|
+
@id == "VBRI"
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
@@ -30,15 +30,15 @@ module WahWah
|
|
30
30
|
def initialize(file_io, offset = 0)
|
31
31
|
file_io.seek(offset)
|
32
32
|
|
33
|
-
@id, @flags = file_io.read(8)&.unpack(
|
33
|
+
@id, @flags = file_io.read(8)&.unpack("A4N")
|
34
34
|
return unless valid?
|
35
35
|
|
36
|
-
@frames_count = @flags & 1 == 1 ? file_io.read(4).
|
37
|
-
@bytes_count = @flags & 2 == 2 ? file_io.read(4).
|
36
|
+
@frames_count = @flags & 1 == 1 ? file_io.read(4).unpack1("N") : 0
|
37
|
+
@bytes_count = @flags & 2 == 2 ? file_io.read(4).unpack1("N") : 0
|
38
38
|
end
|
39
39
|
|
40
40
|
def valid?
|
41
|
-
%w
|
41
|
+
%w[Xing Info].include? @id
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
data/lib/wahwah/mp3_tag.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'forwardable'
|
4
|
-
|
5
3
|
module WahWah
|
6
4
|
class Mp3Tag < Tag
|
7
5
|
extend TagDelegate
|
@@ -40,71 +38,74 @@ module WahWah
|
|
40
38
|
end
|
41
39
|
|
42
40
|
def is_vbr?
|
43
|
-
xing_header.valid? || vbri_header.valid?
|
41
|
+
mpeg_frame_header.valid? && (xing_header.valid? || vbri_header.valid?)
|
44
42
|
end
|
45
43
|
|
46
44
|
private
|
47
|
-
def parse
|
48
|
-
@id3_tag = parse_id3_tag
|
49
|
-
parse_duration if mpeg_frame_header.valid?
|
50
|
-
end
|
51
45
|
|
52
|
-
|
53
|
-
|
54
|
-
|
46
|
+
def parse
|
47
|
+
@id3_tag = parse_id3_tag
|
48
|
+
parse_duration if mpeg_frame_header.valid?
|
49
|
+
end
|
55
50
|
|
56
|
-
|
57
|
-
|
58
|
-
|
51
|
+
def parse_id3_tag
|
52
|
+
id3_v1_tag = ID3::V1.new(@file_io.dup)
|
53
|
+
id3_v2_tag = ID3::V2.new(@file_io.dup)
|
59
54
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
@bitrate = bytes_count * 8 / @duration / 1000 unless @duration.zero?
|
64
|
-
else
|
65
|
-
@bitrate = mpeg_frame_header.frame_bitrate
|
66
|
-
@duration = (file_size - (@id3_tag&.size || 0)) * 8 / (@bitrate * 1000) unless @bitrate.zero?
|
67
|
-
end
|
68
|
-
end
|
55
|
+
return id3_v2_tag if id3_v2_tag.valid?
|
56
|
+
id3_v1_tag if id3_v1_tag.valid?
|
57
|
+
end
|
69
58
|
|
70
|
-
|
71
|
-
|
72
|
-
@
|
59
|
+
def parse_duration
|
60
|
+
if is_vbr?
|
61
|
+
@duration = frames_count * (mpeg_frame_header.samples_per_frame / sample_rate.to_f)
|
62
|
+
@bitrate = (bytes_count * 8 / @duration / 1000).round unless @duration.zero?
|
63
|
+
else
|
64
|
+
@bitrate = mpeg_frame_header.frame_bitrate
|
65
|
+
@duration = (file_size - (@id3_tag&.size || 0)) * 8 / (@bitrate * 1000).to_f unless @bitrate.zero?
|
73
66
|
end
|
67
|
+
end
|
74
68
|
|
75
|
-
|
76
|
-
|
77
|
-
|
69
|
+
def mpeg_frame_header
|
70
|
+
# Because id3v2 tag on the file header so skip id3v2 tag
|
71
|
+
@mpeg_frame_header ||= Mp3::MpegFrameHeader.new(@file_io, id3v2? ? @id3_tag&.size : 0)
|
72
|
+
end
|
78
73
|
|
79
|
-
|
80
|
-
|
81
|
-
|
74
|
+
def xing_header
|
75
|
+
@xing_header ||= Mp3::XingHeader.new(@file_io, xing_header_offset)
|
76
|
+
end
|
82
77
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
mpeg_frame_side_info_size = mpeg_version == 'MPEG1' ?
|
87
|
-
(channel_mode == 'Single Channel' ? 17 : 32) :
|
88
|
-
(channel_mode == 'Single Channel' ? 9 : 17)
|
78
|
+
def vbri_header
|
79
|
+
@vbri_header ||= Mp3::VbriHeader.new(@file_io, vbri_header_offset)
|
80
|
+
end
|
89
81
|
|
90
|
-
|
82
|
+
def xing_header_offset
|
83
|
+
mpeg_frame_header_position = mpeg_frame_header.position
|
84
|
+
mpeg_frame_header_size = Mp3::MpegFrameHeader::HEADER_SIZE
|
85
|
+
mpeg_frame_side_info_size = if mpeg_version == "MPEG1"
|
86
|
+
channel_mode == "Single Channel" ? 17 : 32
|
87
|
+
else
|
88
|
+
channel_mode == "Single Channel" ? 9 : 17
|
91
89
|
end
|
92
90
|
|
93
|
-
|
94
|
-
|
95
|
-
mpeg_frame_header_size = Mp3::MpegFrameHeader::HEADER_SIZE
|
91
|
+
mpeg_frame_header_position + mpeg_frame_header_size + mpeg_frame_side_info_size
|
92
|
+
end
|
96
93
|
|
97
|
-
|
98
|
-
|
94
|
+
def vbri_header_offset
|
95
|
+
mpeg_frame_header_position = mpeg_frame_header.position
|
96
|
+
mpeg_frame_header_size = Mp3::MpegFrameHeader::HEADER_SIZE
|
99
97
|
|
100
|
-
|
101
|
-
|
102
|
-
vbri_header.frames_count if vbri_header.valid?
|
103
|
-
end
|
98
|
+
mpeg_frame_header_position + mpeg_frame_header_size + 32
|
99
|
+
end
|
104
100
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
101
|
+
def frames_count
|
102
|
+
return xing_header.frames_count if xing_header.valid?
|
103
|
+
vbri_header.frames_count if vbri_header.valid?
|
104
|
+
end
|
105
|
+
|
106
|
+
def bytes_count
|
107
|
+
return xing_header.bytes_count if xing_header.valid?
|
108
|
+
vbri_header.bytes_count if vbri_header.valid?
|
109
|
+
end
|
109
110
|
end
|
110
111
|
end
|
data/lib/wahwah/mp4/atom.rb
CHANGED
@@ -3,13 +3,15 @@
|
|
3
3
|
module WahWah
|
4
4
|
module Mp4
|
5
5
|
class Atom
|
6
|
-
|
7
|
-
|
6
|
+
prepend LazyRead
|
7
|
+
|
8
|
+
VERSIONED_ATOMS = %w[meta stsd]
|
9
|
+
FLAGGED_ATOMS = %w[stsd]
|
8
10
|
HEADER_SIZE = 8
|
9
11
|
HEADER_SIZE_FIELD_SIZE = 4
|
10
12
|
EXTENDED_HEADER_SIZE = 8
|
11
13
|
|
12
|
-
attr_reader :
|
14
|
+
attr_reader :type
|
13
15
|
|
14
16
|
def self.find(file_io, *atom_path)
|
15
17
|
file_io.rewind
|
@@ -20,14 +22,15 @@ module WahWah
|
|
20
22
|
atom = new(file_io)
|
21
23
|
|
22
24
|
next unless atom.valid?
|
23
|
-
file_io.seek(atom.size, IO::SEEK_CUR)
|
25
|
+
file_io.seek(atom.size, IO::SEEK_CUR)
|
26
|
+
next unless atom.type == atom_type
|
24
27
|
|
25
28
|
return atom if atom_path.empty?
|
26
29
|
return atom.find(*atom_path)
|
27
30
|
end
|
28
31
|
|
29
32
|
# Return empty atom if can not found
|
30
|
-
new(StringIO.new(
|
33
|
+
new(StringIO.new(""))
|
31
34
|
end
|
32
35
|
|
33
36
|
# An atom header consists of the following fields:
|
@@ -39,25 +42,18 @@ module WahWah
|
|
39
42
|
# Type:
|
40
43
|
# A 32-bit integer that contains the type of the atom.
|
41
44
|
# This can often be usefully treated as a four-character field with a mnemonic value .
|
42
|
-
def initialize
|
43
|
-
@size, @type = file_io.read(HEADER_SIZE)&.unpack(
|
45
|
+
def initialize
|
46
|
+
@size, @type = @file_io.read(HEADER_SIZE)&.unpack("Na4")
|
44
47
|
return unless valid?
|
45
48
|
|
46
49
|
# If the size field of an atom is set to 1, the type field is followed by a 64-bit extended size field,
|
47
50
|
# which contains the actual size of the atom as a 64-bit unsigned integer.
|
48
|
-
@size = file_io.read(EXTENDED_HEADER_SIZE).
|
51
|
+
@size = @file_io.read(EXTENDED_HEADER_SIZE).unpack1("Q>") - EXTENDED_HEADER_SIZE if @size == 1
|
49
52
|
|
50
53
|
# If the size field of an atom is set to 0, which is allowed only for a top-level atom,
|
51
54
|
# designates the last atom in the file and indicates that the atom extends to the end of the file.
|
52
|
-
@size = file_io.size if @size == 0
|
53
|
-
@size
|
54
|
-
@file_io = file_io
|
55
|
-
@position = file_io.pos
|
56
|
-
end
|
57
|
-
|
58
|
-
def data
|
59
|
-
@file_io.seek(@position)
|
60
|
-
@file_io.read(size)
|
55
|
+
@size = @file_io.size if @size == 0
|
56
|
+
@size -= HEADER_SIZE
|
61
57
|
end
|
62
58
|
|
63
59
|
def valid?
|
@@ -68,38 +64,38 @@ module WahWah
|
|
68
64
|
child_atom_index = data.index(atom_path.first)
|
69
65
|
|
70
66
|
# Return empty atom if can not found
|
71
|
-
return self.class.new(StringIO.new(
|
67
|
+
return self.class.new(StringIO.new("")) if child_atom_index.nil?
|
72
68
|
|
73
69
|
# Because before atom type field there are 4 bytes of size field,
|
74
70
|
# So the child_atom_index should reduce 4.
|
75
|
-
self.class.find(StringIO.new(data[child_atom_index - HEADER_SIZE_FIELD_SIZE
|
71
|
+
self.class.find(StringIO.new(data[child_atom_index - HEADER_SIZE_FIELD_SIZE..]), *atom_path)
|
76
72
|
end
|
77
73
|
|
78
|
-
|
79
74
|
def children
|
80
75
|
@children ||= parse_children_atoms
|
81
76
|
end
|
82
77
|
|
83
78
|
private
|
84
|
-
def parse_children_atoms
|
85
|
-
children_atoms = []
|
86
|
-
atoms_data = data
|
87
79
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
atoms_data = atoms_data[4..-1] if FLAGGED_ATOMS.include? type # Skip 4 bytes for flag
|
92
|
-
atoms_data_io = StringIO.new(atoms_data)
|
80
|
+
def parse_children_atoms
|
81
|
+
children_atoms = []
|
82
|
+
atoms_data = data
|
93
83
|
|
94
|
-
|
95
|
-
|
96
|
-
|
84
|
+
# Some atoms data contain extra content before child atom data.
|
85
|
+
# So reduce those extra content to get child atom data.
|
86
|
+
atoms_data = atoms_data[4..] if VERSIONED_ATOMS.include? type # Skip 4 bytes for version
|
87
|
+
atoms_data = atoms_data[4..] if FLAGGED_ATOMS.include? type # Skip 4 bytes for flag
|
88
|
+
atoms_data_io = StringIO.new(atoms_data)
|
97
89
|
|
98
|
-
|
99
|
-
|
90
|
+
until atoms_data_io.eof?
|
91
|
+
atom = self.class.new(atoms_data_io)
|
92
|
+
children_atoms.push(atom)
|
100
93
|
|
101
|
-
|
94
|
+
atom.skip
|
102
95
|
end
|
96
|
+
|
97
|
+
children_atoms
|
98
|
+
end
|
103
99
|
end
|
104
100
|
end
|
105
101
|
end
|