wahwah 1.0.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|