wahwah 1.0.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,117 +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
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("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
75
+ end
76
+
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")
53
85
 
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
- attribute_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
- data_type, data_value = data_atom.data.unpack('Nx4a*')
72
- encoded_data_value = META_ATOM_DECODE_BY_TYPE[data_type]&.call(data_value)
73
-
74
- next if attribute_name.nil? || encoded_data_value.nil?
75
-
76
- case attribute_name
77
- when :image
78
- @images.push(encoded_data_value)
79
- when :comment
80
- @comments.push(encoded_data_value)
81
- when :track, :disc
82
- count, total_count = encoded_data_value.unpack('x2nn')
83
-
84
- instance_variable_set("@#{attribute_name}", count) unless count.zero?
85
- instance_variable_set("@#{attribute_name}_total", total_count) unless total_count.zero?
86
- else
87
- instance_variable_set("@#{attribute_name}", encoded_data_value)
88
- end
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)
89
90
  end
90
91
  end
92
+ end
91
93
 
92
- def parse_mvhd_atom(atom)
93
- 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
94
98
 
95
- atom_data = StringIO.new(atom.data)
96
- version = atom_data.read(1).unpack('c').first
99
+ def parse_mvhd_atom(atom)
100
+ return unless atom.valid?
97
101
 
98
- # Skip flags
99
- atom_data.seek(3, IO::SEEK_CUR)
102
+ atom_data = StringIO.new(atom.data)
103
+ version = atom_data.read(1).unpack1("c")
100
104
 
101
- if version == 0
102
- # Skip creation and modification time
103
- atom_data.seek(8, IO::SEEK_CUR)
105
+ # Skip flags
106
+ atom_data.seek(3, IO::SEEK_CUR)
104
107
 
105
- time_scale, duration = atom_data.read(8).unpack('l>l>')
106
- elsif version == 1
107
- # Skip creation and modification time
108
- 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)
109
111
 
110
- time_scale, duration = atom_data.read(12).unpack('l>q>')
111
- 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)
112
116
 
113
- @duration = (duration / time_scale.to_f).round
117
+ time_scale, duration = atom_data.read(12).unpack("l>q>")
114
118
  end
115
119
 
116
- def parse_stsd_atom(atom)
117
- return unless atom.valid?
120
+ @duration = duration / time_scale.to_f
121
+ end
118
122
 
119
- mp4a_atom = atom.find('mp4a')
120
- esds_atom = atom.find('esds')
123
+ def parse_stsd_atom(atom)
124
+ return unless atom.valid?
121
125
 
122
- @sample_rate = mp4a_atom.data.unpack('x22I>').first if mp4a_atom.valid?
123
- @bitrate = esds_atom.data.unpack('x26I>').first / 1000 if esds_atom.valid?
124
- end
126
+ mp4a_atom = atom.find("mp4a")
127
+ esds_atom = atom.find("esds")
128
+
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
125
136
  end
126
137
  end
@@ -6,7 +6,7 @@ module WahWah
6
6
  include VorbisComment
7
7
  include Flac::StreaminfoBlock
8
8
 
9
- attr_reader :bitrate, :duration, :sample_rate, *COMMET_FIELD_MAPPING.values
9
+ attr_reader :bitrate, :duration, :sample_rate, :bit_depth, *COMMET_FIELD_MAPPING.values
10
10
 
11
11
  def initialize(identification_packet, comment_packet)
12
12
  # Identification packet structure:
@@ -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
@@ -20,7 +20,7 @@ module WahWah
20
20
  def each
21
21
  @file_io.rewind
22
22
 
23
- packet = +''
23
+ packet = +""
24
24
  pages = Ogg::Pages.new(@file_io)
25
25
 
26
26
  pages.each do |page|
@@ -31,7 +31,7 @@ module WahWah
31
31
  # So when segment length is less than 255 byte, it's the final segment.
32
32
  if segment.length < 255
33
33
  yield packet
34
- packet = +''
34
+ packet = +""
35
35
  end
36
36
  end
37
37
  end
@@ -99,7 +99,7 @@ module WahWah
99
99
  # lacing value.
100
100
  class Page
101
101
  HEADER_SIZE = 27
102
- HEADER_FORMAT = 'A4CxQx12C'
102
+ HEADER_FORMAT = "A4CxQx12C"
103
103
 
104
104
  attr_reader :segments, :granule_position
105
105
 
@@ -109,12 +109,12 @@ module WahWah
109
109
 
110
110
  return unless valid?
111
111
 
112
- segment_table = file_io.read(page_segments).unpack('C' * page_segments)
112
+ segment_table = file_io.read(page_segments).unpack("C" * page_segments)
113
113
  @segments = segment_table.map { |segment_length| file_io.read(segment_length) }
114
114
  end
115
115
 
116
116
  def valid?
117
- @capture_pattern == 'OggS' && @version == 0
117
+ @capture_pattern == "OggS" && @version == 0
118
118
  end
119
119
  end
120
120
  end
@@ -16,32 +16,32 @@ module WahWah
16
16
  # 9) done.
17
17
  module VorbisComment
18
18
  COMMET_FIELD_MAPPING = {
19
- 'TITLE' => :title,
20
- 'ALBUM' => :album,
21
- 'ALBUMARTIST' => :albumartist,
22
- 'TRACKNUMBER' => :track,
23
- 'ARTIST' => :artist,
24
- 'DATE' => :year,
25
- 'GENRE' => :genre,
26
- 'DISCNUMBER' => :disc,
27
- 'COMPOSER' => :composer
19
+ "TITLE" => :title,
20
+ "ALBUM" => :album,
21
+ "ALBUMARTIST" => :albumartist,
22
+ "TRACKNUMBER" => :track,
23
+ "ARTIST" => :artist,
24
+ "DATE" => :year,
25
+ "GENRE" => :genre,
26
+ "DISCNUMBER" => :disc,
27
+ "COMPOSER" => :composer
28
28
  }
29
29
 
30
30
  def parse_vorbis_comment(comment_content)
31
31
  comment_content = StringIO.new(comment_content)
32
32
 
33
- vendor_length = comment_content.read(4).unpack('V').first
33
+ vendor_length = comment_content.read(4).unpack1("V")
34
34
  comment_content.seek(vendor_length, IO::SEEK_CUR) # Skip vendor_string
35
35
 
36
- comment_list_length = comment_content.read(4).unpack('V').first
36
+ comment_list_length = comment_content.read(4).unpack1("V")
37
37
 
38
38
  comment_list_length.times do
39
- comment_length = comment_content.read(4).unpack('V').first
39
+ comment_length = comment_content.read(4).unpack1("V")
40
40
  comment = Helper.encode_to_utf8(comment_content.read(comment_length))
41
- field_name, field_value = comment.split('=', 2)
42
- attr_name = COMMET_FIELD_MAPPING[field_name]
41
+ field_name, field_value = comment.split("=", 2)
42
+ attr_name = COMMET_FIELD_MAPPING[field_name&.upcase]
43
43
 
44
- field_value = field_value.to_i if %i(track disc).include? attr_name
44
+ field_value = field_value.to_i if %i[track disc].include? attr_name
45
45
 
46
46
  instance_variable_set("@#{attr_name}", field_value) unless attr_name.nil?
47
47
  end
@@ -20,10 +20,10 @@ module WahWah
20
20
  # 8) [blocksize_0] = 2 exponent (read 4 bits as unsigned integer)
21
21
  # 9) [blocksize_1] = 2 exponent (read 4 bits as unsigned integer)
22
22
  # 10) [framing_flag] = read one bit
23
- @sample_rate, bitrate = identification_packet[12, 12].unpack('Vx4V')
23
+ @sample_rate, bitrate = identification_packet[12, 12].unpack("Vx4V")
24
24
  @bitrate = bitrate / 1000
25
25
 
26
- comment_packet_id, comment_packet_body = [comment_packet[0..6], comment_packet[7..-1]]
26
+ comment_packet_id, comment_packet_body = [comment_packet[0..6], comment_packet[7..]]
27
27
 
28
28
  # Vorbis comment packet start with "\x03vorbis"
29
29
  return unless comment_packet_id == "\x03vorbis"
@@ -16,51 +16,51 @@ module WahWah
16
16
  :composer,
17
17
  :sample_rate
18
18
 
19
- def duration
20
- @duration ||= parse_duration
19
+ private
20
+
21
+ def packets
22
+ @packets ||= Ogg::Packets.new(@file_io)
21
23
  end
22
24
 
23
- def bitrate
24
- @bitrate ||= parse_bitrate
25
+ def pages
26
+ @pages ||= Ogg::Pages.new(@file_io)
25
27
  end
26
28
 
27
- private
28
- def packets
29
- @packets ||= Ogg::Packets.new(@file_io)
30
- end
29
+ def parse
30
+ identification_packet, comment_packet = packets.first(2)
31
+ return if identification_packet.nil? || comment_packet.nil?
31
32
 
32
- def pages
33
- @pages ||= Ogg::Pages.new(@file_io)
33
+ @overhead_packets_size = identification_packet.size + comment_packet.size
34
+
35
+ @tag = if identification_packet.start_with?("\x01vorbis")
36
+ Ogg::VorbisTag.new(identification_packet, comment_packet)
37
+ elsif identification_packet.start_with?("OpusHead")
38
+ Ogg::OpusTag.new(identification_packet, comment_packet)
39
+ elsif identification_packet.start_with?("\x7FFLAC")
40
+ Ogg::FlacTag.new(identification_packet, comment_packet)
34
41
  end
35
42
 
36
- def parse
37
- identification_packet, comment_packet = packets.first(2)
38
- return if identification_packet.nil? || comment_packet.nil?
43
+ @duration = parse_duration
44
+ @bitrate = parse_bitrate
45
+ @bit_depth = parse_bit_depth
46
+ end
39
47
 
40
- @overhead_packets_size = identification_packet.size + comment_packet.size
48
+ def parse_duration
49
+ return @tag.duration if @tag.respond_to? :duration
41
50
 
42
- @tag = case true
43
- when identification_packet.start_with?("\x01vorbis")
44
- Ogg::VorbisTag.new(identification_packet, comment_packet)
45
- when identification_packet.start_with?('OpusHead')
46
- Ogg::OpusTag.new(identification_packet, comment_packet)
47
- when identification_packet.start_with?("\x7FFLAC")
48
- Ogg::FlacTag.new(identification_packet, comment_packet)
49
- end
50
- end
51
+ last_page = pages.to_a.last
52
+ pre_skip = @tag.respond_to?(:pre_skip) ? @tag.pre_skip : 0
51
53
 
52
- def parse_duration
53
- return @tag.duration if @tag.respond_to? :duration
54
-
55
- last_page = pages.to_a.last
56
- pre_skip = @tag.respond_to?(:pre_skip) ? @tag.pre_skip : 0
54
+ (last_page.granule_position - pre_skip) / @tag.sample_rate.to_f
55
+ end
57
56
 
58
- ((last_page.granule_position - pre_skip) / @tag.sample_rate.to_f).round
59
- end
57
+ def parse_bitrate
58
+ return @tag.bitrate if @tag.respond_to? :bitrate
59
+ ((file_size - @overhead_packets_size) * 8.0 / duration / 1000).round
60
+ end
60
61
 
61
- def parse_bitrate
62
- return @tag.bitrate if @tag.respond_to? :bitrate
63
- ((file_size - @overhead_packets_size) * 8.0 / duration / 1000).round
64
- end
62
+ def parse_bit_depth
63
+ @tag.bit_depth if @tag.respond_to? :bit_depth
64
+ end
65
65
  end
66
66
  end
@@ -16,39 +16,35 @@ module WahWah
16
16
  # 4 bytes: an ASCII identifier for this particular RIFF or LIST chunk (for RIFF in the typical case, these 4 bytes describe the content of the entire file, such as "AVI " or "WAVE").
17
17
  # rest of data: subchunks.
18
18
  class Chunk
19
+ prepend LazyRead
20
+
19
21
  HEADER_SIZE = 8
20
- HEADER_FORMAT = 'A4V'
22
+ HEADER_FORMAT = "A4V"
21
23
  HEADER_TYPE_SIZE = 4
22
24
 
23
25
  attr_reader :id, :type
24
26
 
25
- def initialize(file_io)
26
- @id, @size = file_io.read(HEADER_SIZE)&.unpack(HEADER_FORMAT)
27
+ def initialize
28
+ @id, @size = @file_io.read(HEADER_SIZE)&.unpack(HEADER_FORMAT)
27
29
  return unless valid?
28
30
 
29
- @type = file_io.read(HEADER_TYPE_SIZE).unpack('A4').first if have_type?
30
- @file_io = file_io
31
- @position = file_io.pos
31
+ @type = @file_io.read(HEADER_TYPE_SIZE).unpack1("A4") if have_type?
32
32
  end
33
33
 
34
34
  def size
35
- @size = @size + 1 if @size.odd?
35
+ @size += 1 if @size.odd?
36
36
  have_type? ? @size - HEADER_TYPE_SIZE : @size
37
37
  end
38
38
 
39
- def data
40
- @file_io.seek(@position)
41
- @file_io.read(size)
42
- end
43
-
44
39
  def valid?
45
40
  !@id.empty? && !@size.nil? && @size > 0
46
41
  end
47
42
 
48
43
  private
49
- def have_type?
50
- %w(RIFF LIST).include? @id
51
- end
44
+
45
+ def have_type?
46
+ %w[RIFF LIST].include? @id
47
+ end
52
48
  end
53
49
  end
54
50
  end