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.
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
- return unless valid?
16
+ def parse
17
+ @file_io.rewind
18
+ @header = V2Header.new(@file_io)
22
19
 
23
- until end_of_tag? do
24
- frame = ID3::Frame.new(@file_io, major_version)
25
- next unless frame.valid?
20
+ return unless valid?
26
21
 
27
- update_attribute(frame)
28
- end
29
- end
22
+ until end_of_tag?
23
+ frame = ID3::Frame.new(@file_io, major_version)
30
24
 
31
- def update_attribute(frame)
32
- name = frame.name
33
- value = frame.value
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
- def end_of_tag?
56
- size <= @file_io.pos || file_size <= @file_io.pos
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
@@ -10,9 +10,9 @@ module WahWah
10
10
  # ID3v2 flags %abc00000
11
11
  # ID3v2 size 4 * %0xxxxxxx
12
12
  class V2Header
13
- TAG_ID = 'ID3'
13
+ TAG_ID = "ID3"
14
14
  HEADER_SIZE = 10
15
- HEADER_FORMAT = 'A3CxB8B*'
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).unpack('B32').first)
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] == '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
- '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],
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
- '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],
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
- '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]
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 = ['MPEG2.5', nil, 'MPEG2', 'MPEG1']
63
- LAYER_INDEX = [nil, 'layer3', 'layer2', 'layer1']
64
- CHANNEL_MODE_INDEX = ['Stereo', 'Joint Stereo', 'Dual Channel', 'Single Channel']
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
- 'MPEG1' => [44100, 48000, 32000],
68
- 'MPEG2' => [22050, 24000, 16000],
69
- 'MPEG2.5' => [11025, 12000, 8000]
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
- 'MPEG1 layer1' => 384,
74
- 'MPEG1 layer2' => 1152,
75
- 'MPEG1 layer3' => 1152,
73
+ "MPEG1 layer1" => 384,
74
+ "MPEG1 layer2" => 1152,
75
+ "MPEG1 layer3" => 1152,
76
76
 
77
- 'MPEG2 layer1' => 384,
78
- 'MPEG2 layer2' => 1152,
79
- 'MPEG2 layer3' => 576,
77
+ "MPEG2 layer1" => 384,
78
+ "MPEG2 layer2" => 1152,
79
+ "MPEG2 layer3" => 576,
80
80
 
81
- 'MPEG2.5 layer1' => 384,
82
- 'MPEG2.5 layer2' => 1152,
83
- 'MPEG2.5 layer3' => 576
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.unpack('B11').first
98
+ sync_bits = header.unpack1("B11")
99
99
 
100
- if sync_bits == "#{'1' * 11}".b
101
- @header = header.unpack('B*').first
100
+ if sync_bits == ("1" * 11).b
101
+ @header = header.unpack1("B*")
102
102
  @position = offset
103
103
 
104
- parse; break
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
- def parse
131
- return unless valid?
132
-
133
- @version = VERSIONS_INDEX[@header[11..12].to_i(2)]
134
- @layer = LAYER_INDEX[@header[13..14].to_i(2)]
135
- @frame_bitrate = FRAME_BITRATE_INDEX[kind]&.fetch(@header[16..19].to_i(2))
136
- @channel_mode = CHANNEL_MODE_INDEX[@header[24..25].to_i(2)]
137
- @sample_rate = SAMPLE_RATE_INDEX[@version]&.fetch(@header[20..21].to_i(2))
138
- end
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 = 'A4x6NN'
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 == 'VBRI'
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('A4N')
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).unpack('N').first : 0
37
- @bytes_count = @flags & 2 == 2 ? file_io.read(4).unpack('N').first : 0
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(Xing Info).include? @id
41
+ %w[Xing Info].include? @id
42
42
  end
43
43
  end
44
44
  end
@@ -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
- def parse_id3_tag
53
- id3_v1_tag = ID3::V1.new(@file_io)
54
- id3_v2_tag = ID3::V2.new(@file_io)
46
+ def parse
47
+ @id3_tag = parse_id3_tag
48
+ parse_duration if mpeg_frame_header.valid?
49
+ end
55
50
 
56
- return id3_v2_tag if id3_v2_tag.valid?
57
- id3_v1_tag if id3_v1_tag.valid?
58
- end
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
- def parse_duration
61
- if is_vbr?
62
- @duration = (frames_count * (mpeg_frame_header.samples_per_frame / sample_rate.to_f)).round
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
- def mpeg_frame_header
71
- # Because id3v2 tag on the file header so skip id3v2 tag
72
- @mpeg_frame_header ||= Mp3::MpegFrameHeader.new(@file_io, id3v2? ? @id3_tag&.size : 0)
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
- def xing_header
76
- @xing_header ||= Mp3::XingHeader.new(@file_io, xing_header_offset)
77
- end
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
- def vbri_header
80
- @vbri_header ||= Mp3::VbriHeader.new(@file_io, vbri_header_offset)
81
- end
74
+ def xing_header
75
+ @xing_header ||= Mp3::XingHeader.new(@file_io, xing_header_offset)
76
+ end
82
77
 
83
- def xing_header_offset
84
- mpeg_frame_header_position = mpeg_frame_header.position
85
- mpeg_frame_header_size = Mp3::MpegFrameHeader::HEADER_SIZE
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
- mpeg_frame_header_position + mpeg_frame_header_size + mpeg_frame_side_info_size
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
- def vbri_header_offset
94
- mpeg_frame_header_position = mpeg_frame_header.position
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
- mpeg_frame_header_position + mpeg_frame_header_size + 32
98
- end
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
- def frames_count
101
- return xing_header.frames_count if xing_header.valid?
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
- def bytes_count
106
- return xing_header.bytes_count if xing_header.valid?
107
- vbri_header.bytes_count if vbri_header.valid?
108
- end
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
@@ -3,13 +3,15 @@
3
3
  module WahWah
4
4
  module Mp4
5
5
  class Atom
6
- VERSIONED_ATOMS = %w(meta stsd)
7
- FLAGGED_ATOMS = %w(stsd)
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 :size, :type
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); next unless atom.type == atom_type
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(file_io)
43
- @size, @type = file_io.read(HEADER_SIZE)&.unpack('Na4')
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).unpack('Q>').first - EXTENDED_HEADER_SIZE if @size == 1
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 = @size - HEADER_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('')) if child_atom_index.nil?
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..-1]), *atom_path)
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
- # Some atoms data contain extra content before child atom data.
89
- # So reduce those extra content to get child atom data.
90
- atoms_data = atoms_data[4..-1] if VERSIONED_ATOMS.include? type # Skip 4 bytes for version
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
- until atoms_data_io.eof?
95
- atom = self.class.new(atoms_data_io)
96
- children_atoms.push(atom)
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
- atoms_data_io.seek(atom.size, IO::SEEK_CUR)
99
- end
90
+ until atoms_data_io.eof?
91
+ atom = self.class.new(atoms_data_io)
92
+ children_atoms.push(atom)
100
93
 
101
- children_atoms
94
+ atom.skip
102
95
  end
96
+
97
+ children_atoms
98
+ end
103
99
  end
104
100
  end
105
101
  end