wahwah 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -42,67 +42,70 @@ module WahWah
42
42
  end
43
43
 
44
44
  private
45
- def parse
46
- @id3_tag = parse_id3_tag
47
- parse_duration if mpeg_frame_header.valid?
48
- end
49
45
 
50
- def parse_id3_tag
51
- id3_v1_tag = ID3::V1.new(@file_io.dup)
52
- id3_v2_tag = ID3::V2.new(@file_io.dup)
46
+ def parse
47
+ @id3_tag = parse_id3_tag
48
+ parse_duration if mpeg_frame_header.valid?
49
+ end
53
50
 
54
- return id3_v2_tag if id3_v2_tag.valid?
55
- id3_v1_tag if id3_v1_tag.valid?
56
- 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)
57
54
 
58
- def parse_duration
59
- if is_vbr?
60
- @duration = (frames_count * (mpeg_frame_header.samples_per_frame / sample_rate.to_f)).round
61
- @bitrate = bytes_count * 8 / @duration / 1000 unless @duration.zero?
62
- else
63
- @bitrate = mpeg_frame_header.frame_bitrate
64
- @duration = (file_size - (@id3_tag&.size || 0)) * 8 / (@bitrate * 1000) unless @bitrate.zero?
65
- end
66
- end
55
+ return id3_v2_tag if id3_v2_tag.valid?
56
+ id3_v1_tag if id3_v1_tag.valid?
57
+ end
67
58
 
68
- def mpeg_frame_header
69
- # Because id3v2 tag on the file header so skip id3v2 tag
70
- @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?
71
66
  end
67
+ end
72
68
 
73
- def xing_header
74
- @xing_header ||= Mp3::XingHeader.new(@file_io, xing_header_offset)
75
- 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
76
73
 
77
- def vbri_header
78
- @vbri_header ||= Mp3::VbriHeader.new(@file_io, vbri_header_offset)
79
- end
74
+ def xing_header
75
+ @xing_header ||= Mp3::XingHeader.new(@file_io, xing_header_offset)
76
+ end
80
77
 
81
- def xing_header_offset
82
- mpeg_frame_header_position = mpeg_frame_header.position
83
- mpeg_frame_header_size = Mp3::MpegFrameHeader::HEADER_SIZE
84
- mpeg_frame_side_info_size = mpeg_version == 'MPEG1' ?
85
- (channel_mode == 'Single Channel' ? 17 : 32) :
86
- (channel_mode == 'Single Channel' ? 9 : 17)
78
+ def vbri_header
79
+ @vbri_header ||= Mp3::VbriHeader.new(@file_io, vbri_header_offset)
80
+ end
87
81
 
88
- 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
89
89
  end
90
90
 
91
- def vbri_header_offset
92
- mpeg_frame_header_position = mpeg_frame_header.position
93
- mpeg_frame_header_size = Mp3::MpegFrameHeader::HEADER_SIZE
91
+ mpeg_frame_header_position + mpeg_frame_header_size + mpeg_frame_side_info_size
92
+ end
94
93
 
95
- mpeg_frame_header_position + mpeg_frame_header_size + 32
96
- end
94
+ def vbri_header_offset
95
+ mpeg_frame_header_position = mpeg_frame_header.position
96
+ mpeg_frame_header_size = Mp3::MpegFrameHeader::HEADER_SIZE
97
97
 
98
- def frames_count
99
- return xing_header.frames_count if xing_header.valid?
100
- vbri_header.frames_count if vbri_header.valid?
101
- end
98
+ mpeg_frame_header_position + mpeg_frame_header_size + 32
99
+ end
102
100
 
103
- def bytes_count
104
- return xing_header.bytes_count if xing_header.valid?
105
- vbri_header.bytes_count if vbri_header.valid?
106
- 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
107
110
  end
108
111
  end
@@ -5,8 +5,8 @@ module WahWah
5
5
  class Atom
6
6
  prepend LazyRead
7
7
 
8
- VERSIONED_ATOMS = %w(meta stsd)
9
- FLAGGED_ATOMS = %w(stsd)
8
+ VERSIONED_ATOMS = %w[meta stsd]
9
+ FLAGGED_ATOMS = %w[stsd]
10
10
  HEADER_SIZE = 8
11
11
  HEADER_SIZE_FIELD_SIZE = 4
12
12
  EXTENDED_HEADER_SIZE = 8
@@ -22,14 +22,15 @@ module WahWah
22
22
  atom = new(file_io)
23
23
 
24
24
  next unless atom.valid?
25
- 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
26
27
 
27
28
  return atom if atom_path.empty?
28
29
  return atom.find(*atom_path)
29
30
  end
30
31
 
31
32
  # Return empty atom if can not found
32
- new(StringIO.new(''))
33
+ new(StringIO.new(""))
33
34
  end
34
35
 
35
36
  # An atom header consists of the following fields:
@@ -42,17 +43,17 @@ module WahWah
42
43
  # A 32-bit integer that contains the type of the atom.
43
44
  # This can often be usefully treated as a four-character field with a mnemonic value .
44
45
  def initialize
45
- @size, @type = @file_io.read(HEADER_SIZE)&.unpack('Na4')
46
+ @size, @type = @file_io.read(HEADER_SIZE)&.unpack("Na4")
46
47
  return unless valid?
47
48
 
48
49
  # If the size field of an atom is set to 1, the type field is followed by a 64-bit extended size field,
49
50
  # which contains the actual size of the atom as a 64-bit unsigned integer.
50
- @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
51
52
 
52
53
  # If the size field of an atom is set to 0, which is allowed only for a top-level atom,
53
54
  # designates the last atom in the file and indicates that the atom extends to the end of the file.
54
55
  @size = @file_io.size if @size == 0
55
- @size = @size - HEADER_SIZE
56
+ @size -= HEADER_SIZE
56
57
  end
57
58
 
58
59
  def valid?
@@ -63,38 +64,38 @@ module WahWah
63
64
  child_atom_index = data.index(atom_path.first)
64
65
 
65
66
  # Return empty atom if can not found
66
- return self.class.new(StringIO.new('')) if child_atom_index.nil?
67
+ return self.class.new(StringIO.new("")) if child_atom_index.nil?
67
68
 
68
69
  # Because before atom type field there are 4 bytes of size field,
69
70
  # So the child_atom_index should reduce 4.
70
- 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)
71
72
  end
72
73
 
73
-
74
74
  def children
75
75
  @children ||= parse_children_atoms
76
76
  end
77
77
 
78
78
  private
79
- def parse_children_atoms
80
- children_atoms = []
81
- atoms_data = data
82
79
 
83
- # Some atoms data contain extra content before child atom data.
84
- # So reduce those extra content to get child atom data.
85
- atoms_data = atoms_data[4..-1] if VERSIONED_ATOMS.include? type # Skip 4 bytes for version
86
- atoms_data = atoms_data[4..-1] if FLAGGED_ATOMS.include? type # Skip 4 bytes for flag
87
- atoms_data_io = StringIO.new(atoms_data)
80
+ def parse_children_atoms
81
+ children_atoms = []
82
+ atoms_data = data
88
83
 
89
- until atoms_data_io.eof?
90
- atom = self.class.new(atoms_data_io)
91
- 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)
92
89
 
93
- atom.skip
94
- end
90
+ until atoms_data_io.eof?
91
+ atom = self.class.new(atoms_data_io)
92
+ children_atoms.push(atom)
95
93
 
96
- children_atoms
94
+ atom.skip
97
95
  end
96
+
97
+ children_atoms
98
+ end
98
99
  end
99
100
  end
100
101
  end
@@ -10,124 +10,128 @@ module WahWah
10
10
  "\xA9day".b => :year,
11
11
  "\xA9gen".b => :genre,
12
12
  "\xA9nam".b => :title,
13
- 'covr'.b => :image,
14
- 'disk'.b => :disc,
15
- 'trkn'.b => :track,
16
- 'aART'.b => :albumartist
13
+ "covr".b => :image,
14
+ "disk".b => :disc,
15
+ "trkn".b => :track,
16
+ "aART".b => :albumartist
17
17
  }
18
18
 
19
19
  META_ATOM_DECODE_BY_TYPE = {
20
- 0 => -> (data) { data }, # reserved
21
- 1 => -> (data) { Helper.encode_to_utf8(data) }, # UTF-8
22
- 2 => -> (data) { Helper.encode_to_utf8(data, 'UTF-16BE') }, # UTF-16BE
23
- 3 => -> (data) { Helper.encode_to_utf8(data, 'SJIS') }, # SJIS
24
-
25
- 13 => -> (data) { { data: data, mime_type: 'image/jpeg', type: :cover } }, # JPEG
26
- 14 => -> (data) { { data: data, mime_type: 'image/png', type: :cover } }, # PNG
27
-
28
- 21 => -> (data) { data.unpack('i>').first }, # Big endian signed integer
29
- 22 => -> (data) { data.unpack('I>').first }, # Big endian unsigned integer
30
- 23 => -> (data) { data.unpack('g').first }, # Big endian 32-bit floating point value
31
- 24 => -> (data) { data.unpack('G').first }, # Big endian 64-bit floating point value
32
-
33
- 65 => -> (data) { data.unpack('c').first }, # 8-bit signed integer
34
- 66 => -> (data) { data.unpack('s>').first }, # Big-endian 16-bit signed integer
35
- 67 => -> (data) { data.unpack('l>').first }, # Big-endian 32-bit signed integer
36
- 74 => -> (data) { data.unpack('q>').first }, # Big-endian 64-bit signed integer
37
-
38
- 75 => -> (data) { data.unpack('C').first }, # 8-bit unsigned integer
39
- 76 => -> (data) { data.unpack('S>').first }, # Big-endian 16-bit unsigned integer
40
- 77 => -> (data) { data.unpack('L>').first }, # Big-endian 32-bit unsigned integer
41
- 78 => -> (data) { data.unpack('Q>').first } # Big-endian 64-bit unsigned integer
20
+ 0 => ->(data) { data }, # reserved
21
+ 1 => ->(data) { Helper.encode_to_utf8(data) }, # UTF-8
22
+ 2 => ->(data) { Helper.encode_to_utf8(data, "UTF-16BE") }, # UTF-16BE
23
+ 3 => ->(data) { Helper.encode_to_utf8(data, "SJIS") }, # SJIS
24
+
25
+ 13 => ->(data) { {data: data, mime_type: "image/jpeg", type: :cover} }, # JPEG
26
+ 14 => ->(data) { {data: data, mime_type: "image/png", type: :cover} }, # PNG
27
+
28
+ 21 => ->(data) { data.unpack1("i>") }, # Big endian signed integer
29
+ 22 => ->(data) { data.unpack1("I>") }, # Big endian unsigned integer
30
+ 23 => ->(data) { data.unpack1("g") }, # Big endian 32-bit floating point value
31
+ 24 => ->(data) { data.unpack1("G") }, # Big endian 64-bit floating point value
32
+
33
+ 65 => ->(data) { data.unpack1("c") }, # 8-bit signed integer
34
+ 66 => ->(data) { data.unpack1("s>") }, # Big-endian 16-bit signed integer
35
+ 67 => ->(data) { data.unpack1("l>") }, # Big-endian 32-bit signed integer
36
+ 74 => ->(data) { data.unpack1("q>") }, # Big-endian 64-bit signed integer
37
+
38
+ 75 => ->(data) { data.unpack1("C") }, # 8-bit unsigned integer
39
+ 76 => ->(data) { data.unpack1("S>") }, # Big-endian 16-bit unsigned integer
40
+ 77 => ->(data) { data.unpack1("L>") }, # Big-endian 32-bit unsigned integer
41
+ 78 => ->(data) { data.unpack1("Q>") } # Big-endian 64-bit unsigned integer
42
42
  }
43
43
 
44
44
  private
45
- def parse
46
- movie_atom = Mp4::Atom.find(@file_io, 'moov')
47
- return unless movie_atom.valid?
48
45
 
49
- parse_meta_list_atom movie_atom.find('udta', 'meta', 'ilst')
50
- parse_mvhd_atom movie_atom.find('mvhd')
51
- parse_stsd_atom movie_atom.find('trak', 'mdia', 'minf', 'stbl', 'stsd')
52
- end
53
-
54
- def parse_meta_list_atom(atom)
55
- return unless atom.valid?
56
-
57
- # The metadata item list atom holds a list of actual metadata values that are present in the metadata atom.
58
- # The metadata items are formatted as a list of items.
59
- # The metadata item list atom is of type ‘ilst’ and contains a number of metadata items, each of which is an atom.
60
- # each metadata item atom contains a Value Atom, to hold the value of the metadata item
61
- atom.children.each do |child_atom|
62
- attr_name = META_ATOM_MAPPING[child_atom.type]
63
-
64
- # The value of the metadata item is expressed as immediate data in a value atom.
65
- # The value atom starts with two fields: a type indicator, and a locale indicator.
66
- # Both the type and locale indicators are four bytes long.
67
- # There may be multiple ‘value’ entries, using different type
68
- data_atom = child_atom.find('data')
69
- return unless data_atom.valid?
70
-
71
- (@images_data.push(data_atom); next) if attr_name == :image
72
-
73
- encoded_data_value = parse_meta_data_atom(data_atom)
74
- next if attr_name.nil? || encoded_data_value.nil?
75
-
76
- case attr_name
77
- when :comment
78
- @comments.push(encoded_data_value)
79
- when :track, :disc
80
- count, total_count = encoded_data_value.unpack('x2nn')
81
-
82
- instance_variable_set("@#{attr_name}", count) unless count.zero?
83
- instance_variable_set("@#{attr_name}_total", total_count) unless total_count.zero?
84
- else
85
- instance_variable_set("@#{attr_name}", encoded_data_value)
86
- end
46
+ def parse
47
+ movie_atom = Mp4::Atom.find(@file_io, "moov")
48
+ return unless movie_atom.valid?
49
+
50
+ parse_meta_list_atom movie_atom.find("udta", "meta", "ilst")
51
+ parse_mvhd_atom movie_atom.find("mvhd")
52
+ parse_stsd_atom movie_atom.find("trak", "mdia", "minf", "stbl", "stsd")
53
+ end
54
+
55
+ def parse_meta_list_atom(atom)
56
+ return unless atom.valid?
57
+
58
+ # The metadata item list atom holds a list of actual metadata values that are present in the metadata atom.
59
+ # The metadata items are formatted as a list of items.
60
+ # The metadata item list atom is of type ‘ilst’ and contains a number of metadata items, each of which is an atom.
61
+ # each metadata item atom contains a Value Atom, to hold the value of the metadata item
62
+ atom.children.each do |child_atom|
63
+ attr_name = META_ATOM_MAPPING[child_atom.type]
64
+
65
+ # The value of the metadata item is expressed as immediate data in a value atom.
66
+ # The value atom starts with two fields: a type indicator, and a locale indicator.
67
+ # Both the type and locale indicators are four bytes long.
68
+ # There may be multiple ‘value’ entries, using different type
69
+ data_atom = child_atom.find("data")
70
+ next unless data_atom.valid?
71
+
72
+ if attr_name == :image
73
+ @images_data.push(data_atom)
74
+ next
87
75
  end
88
- end
89
76
 
90
- def parse_meta_data_atom(atom)
91
- data_type, data_value = atom.data.unpack('Nx4a*')
92
- META_ATOM_DECODE_BY_TYPE[data_type]&.call(data_value)
77
+ encoded_data_value = parse_meta_data_atom(data_atom)
78
+ next if attr_name.nil? || encoded_data_value.nil?
79
+
80
+ case attr_name
81
+ when :comment
82
+ @comments.push(encoded_data_value)
83
+ when :track, :disc
84
+ count, total_count = encoded_data_value.unpack("x2nn")
85
+
86
+ instance_variable_set("@#{attr_name}", count) unless count.zero?
87
+ instance_variable_set("@#{attr_name}_total", total_count) unless total_count.zero?
88
+ else
89
+ instance_variable_set("@#{attr_name}", encoded_data_value)
90
+ end
93
91
  end
92
+ end
94
93
 
95
- def parse_mvhd_atom(atom)
96
- return unless atom.valid?
94
+ def parse_meta_data_atom(atom)
95
+ data_type, data_value = atom.data.unpack("Nx4a*")
96
+ META_ATOM_DECODE_BY_TYPE[data_type]&.call(data_value)
97
+ end
97
98
 
98
- atom_data = StringIO.new(atom.data)
99
- version = atom_data.read(1).unpack('c').first
99
+ def parse_mvhd_atom(atom)
100
+ return unless atom.valid?
100
101
 
101
- # Skip flags
102
- atom_data.seek(3, IO::SEEK_CUR)
102
+ atom_data = StringIO.new(atom.data)
103
+ version = atom_data.read(1).unpack1("c")
103
104
 
104
- if version == 0
105
- # Skip creation and modification time
106
- atom_data.seek(8, IO::SEEK_CUR)
105
+ # Skip flags
106
+ atom_data.seek(3, IO::SEEK_CUR)
107
107
 
108
- time_scale, duration = atom_data.read(8).unpack('l>l>')
109
- elsif version == 1
110
- # Skip creation and modification time
111
- atom_data.seek(16, IO::SEEK_CUR)
108
+ if version == 0
109
+ # Skip creation and modification time
110
+ atom_data.seek(8, IO::SEEK_CUR)
112
111
 
113
- time_scale, duration = atom_data.read(12).unpack('l>q>')
114
- end
112
+ time_scale, duration = atom_data.read(8).unpack("l>l>")
113
+ elsif version == 1
114
+ # Skip creation and modification time
115
+ atom_data.seek(16, IO::SEEK_CUR)
115
116
 
116
- @duration = (duration / time_scale.to_f).round
117
+ time_scale, duration = atom_data.read(12).unpack("l>q>")
117
118
  end
118
119
 
119
- def parse_stsd_atom(atom)
120
- return unless atom.valid?
120
+ @duration = duration / time_scale.to_f
121
+ end
121
122
 
122
- mp4a_atom = atom.find('mp4a')
123
- esds_atom = atom.find('esds')
123
+ def parse_stsd_atom(atom)
124
+ return unless atom.valid?
124
125
 
125
- @sample_rate = mp4a_atom.data.unpack('x22I>').first if mp4a_atom.valid?
126
- @bitrate = esds_atom.data.unpack('x26I>').first / 1000 if esds_atom.valid?
127
- end
126
+ mp4a_atom = atom.find("mp4a")
127
+ esds_atom = atom.find("esds")
128
128
 
129
- def parse_image_data(image_data_atom)
130
- parse_meta_data_atom(image_data_atom)
131
- end
129
+ @sample_rate = mp4a_atom.data.unpack1("x22I>") if mp4a_atom.valid?
130
+ @bitrate = esds_atom.data.unpack1("x26I>") / 1000 if esds_atom.valid?
131
+ end
132
+
133
+ def parse_image_data(image_data_atom)
134
+ parse_meta_data_atom(image_data_atom)
135
+ end
132
136
  end
133
137
  end
@@ -23,9 +23,9 @@ module WahWah
23
23
  # Each such packet will contain a single native FLAC metadata block.
24
24
  # The first of these must be a VORBIS_COMMENT block.
25
25
 
26
- id, streaminfo_block_data = identification_packet.unpack('x9A4A*')
26
+ id, streaminfo_block_data = identification_packet.unpack("x9A4A*")
27
27
 
28
- return unless id == 'fLaC'
28
+ return unless id == "fLaC"
29
29
  streaminfo_block = Flac::Block.new(StringIO.new(streaminfo_block_data))
30
30
  vorbis_comment_block = Flac::Block.new(StringIO.new(comment_packet))
31
31
 
@@ -19,12 +19,12 @@ module WahWah
19
19
  # 7) [channel_mapping_family] = read 8 bit as unsigned integer
20
20
  # 8) [channel_mapping_table]
21
21
  @sample_rate = 48000
22
- @pre_skip = identification_packet[10..11].unpack('v').first
22
+ @pre_skip = identification_packet[10..11].unpack1("v")
23
23
 
24
- comment_packet_id, comment_packet_body = [comment_packet[0..7], comment_packet[8..-1]]
24
+ comment_packet_id, comment_packet_body = [comment_packet[0..7], comment_packet[8..]]
25
25
 
26
26
  # Opus comment packet start with 'OpusTags'
27
- return unless comment_packet_id == 'OpusTags'
27
+ return unless comment_packet_id == "OpusTags"
28
28
 
29
29
  parse_vorbis_comment(comment_packet_body)
30
30
  end