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.
@@ -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