wahwah 1.1.1 → 1.2.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.
@@ -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