wahwah 0.1.0 → 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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +21 -0
  3. data/lib/wahwah/asf/object.rb +34 -0
  4. data/lib/wahwah/asf_tag.rb +221 -0
  5. data/lib/wahwah/errors.rb +7 -0
  6. data/lib/wahwah/flac/block.rb +51 -0
  7. data/lib/wahwah/flac/streaminfo_block.rb +51 -0
  8. data/lib/wahwah/flac_tag.rb +86 -0
  9. data/lib/wahwah/helper.rb +37 -0
  10. data/lib/wahwah/id3/comment_frame_body.rb +21 -0
  11. data/lib/wahwah/id3/frame.rb +176 -0
  12. data/lib/wahwah/id3/frame_body.rb +35 -0
  13. data/lib/wahwah/id3/genre_frame_body.rb +15 -0
  14. data/lib/wahwah/id3/image_frame_body.rb +60 -0
  15. data/lib/wahwah/id3/text_frame_body.rb +16 -0
  16. data/lib/wahwah/id3/v1.rb +97 -0
  17. data/lib/wahwah/id3/v2.rb +67 -0
  18. data/lib/wahwah/id3/v2_header.rb +53 -0
  19. data/lib/wahwah/lazy_read.rb +40 -0
  20. data/lib/wahwah/mp3/mpeg_frame_header.rb +143 -0
  21. data/lib/wahwah/mp3/vbri_header.rb +47 -0
  22. data/lib/wahwah/mp3/xing_header.rb +45 -0
  23. data/lib/wahwah/mp3_tag.rb +111 -0
  24. data/lib/wahwah/mp4/atom.rb +101 -0
  25. data/lib/wahwah/mp4_tag.rb +137 -0
  26. data/lib/wahwah/ogg/flac_tag.rb +37 -0
  27. data/lib/wahwah/ogg/opus_tag.rb +33 -0
  28. data/lib/wahwah/ogg/packets.rb +41 -0
  29. data/lib/wahwah/ogg/page.rb +121 -0
  30. data/lib/wahwah/ogg/pages.rb +24 -0
  31. data/lib/wahwah/ogg/vorbis_comment.rb +51 -0
  32. data/lib/wahwah/ogg/vorbis_tag.rb +35 -0
  33. data/lib/wahwah/ogg_tag.rb +66 -0
  34. data/lib/wahwah/riff/chunk.rb +50 -0
  35. data/lib/wahwah/riff_tag.rb +142 -0
  36. data/lib/wahwah/tag.rb +71 -0
  37. data/lib/wahwah/tag_delegate.rb +16 -0
  38. data/lib/wahwah/version.rb +4 -2
  39. data/lib/wahwah.rb +78 -2
  40. metadata +109 -24
  41. data/.gitignore +0 -8
  42. data/.travis.yml +0 -5
  43. data/Gemfile +0 -6
  44. data/Gemfile.lock +0 -22
  45. data/README.md +0 -35
  46. data/Rakefile +0 -10
  47. data/bin/console +0 -14
  48. data/bin/setup +0 -8
  49. data/wahwah.gemspec +0 -27
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WahWah
4
+ module Ogg
5
+ # The Ogg page header has the following format:
6
+ #
7
+ # 0 1 2 3
8
+ # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1| Byte
9
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
10
+ # | capture_pattern: Magic number for page start "OggS" | 0-3
11
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
12
+ # | version | header_type | granule_position | 4-7
13
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
14
+ # | | 8-11
15
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16
+ # | | bitstream_serial_number | 12-15
17
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
18
+ # | | page_sequence_number | 16-19
19
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20
+ # | | CRC_checksum | 20-23
21
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
22
+ # | |page_segments | segment_table | 24-27
23
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
24
+ # | ... | 28-
25
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
26
+ #
27
+ #
28
+ # The fields in the page header have the following meaning:
29
+ #
30
+ # 1. capture_pattern: a 4 Byte field that signifies the beginning of a
31
+ # page. It contains the magic numbers:
32
+
33
+ # 0x4f 'O'
34
+
35
+ # 0x67 'g'
36
+
37
+ # 0x67 'g'
38
+
39
+ # 0x53 'S'
40
+
41
+ # It helps a decoder to find the page boundaries and regain
42
+ # synchronisation after parsing a corrupted stream. Once the
43
+ # capture pattern is found, the decoder verifies page sync and
44
+ # integrity by computing and comparing the checksum.
45
+
46
+ # 2. stream_structure_version: 1 Byte signifying the version number of
47
+ # the Ogg file format used in this stream (this document specifies
48
+ # version 0).
49
+
50
+ # 3. header_type_flag: the bits in this 1 Byte field identify the
51
+ # specific type of this page.
52
+
53
+ # * bit 0x01
54
+
55
+ # set: page contains data of a packet continued from the previous
56
+ # page
57
+
58
+ # unset: page contains a fresh packet
59
+
60
+ # * bit 0x02
61
+
62
+ # set: this is the first page of a logical bitstream (bos)
63
+
64
+ # unset: this page is not a first page
65
+
66
+ # * bit 0x04
67
+
68
+ # set: this is the last page of a logical bitstream (eos)
69
+
70
+ # unset: this page is not a last page
71
+
72
+ # 4. granule_position: an 8 Byte field containing position information.
73
+ # For example, for an audio stream, it MAY contain the total number
74
+ # of PCM samples encoded after including all frames finished on this
75
+ # page. For a video stream it MAY contain the total number of video
76
+ # frames encoded after this page. This is a hint for the decoder
77
+ # and gives it some timing and position information. Its meaning is
78
+ # dependent on the codec for that logical bitstream and specified in
79
+ # a specific media mapping. A special value of -1 (in two's
80
+ # complement) indicates that no packets finish on this page.
81
+
82
+ # 5. bitstream_serial_number: a 4 Byte field containing the unique
83
+ # serial number by which the logical bitstream is identified.
84
+
85
+ # 6. page_sequence_number: a 4 Byte field containing the sequence
86
+ # number of the page so the decoder can identify page loss. This
87
+ # sequence number is increasing on each logical bitstream
88
+ # separately.
89
+
90
+ # 7. CRC_checksum: a 4 Byte field containing a 32 bit CRC checksum of
91
+ # the page (including header with zero CRC field and page content).
92
+ # The generator polynomial is 0x04c11db7.
93
+
94
+ # 8. number_page_segments: 1 Byte giving the number of segment entries
95
+ # encoded in the segment table.
96
+
97
+ # 9. segment_table: number_page_segments Bytes containing the lacing
98
+ # values of all segments in this page. Each Byte contains one
99
+ # lacing value.
100
+ class Page
101
+ HEADER_SIZE = 27
102
+ HEADER_FORMAT = "A4CxQx12C"
103
+
104
+ attr_reader :segments, :granule_position
105
+
106
+ def initialize(file_io)
107
+ header_content = file_io.read(HEADER_SIZE)
108
+ @capture_pattern, @version, @granule_position, page_segments = header_content.unpack(HEADER_FORMAT) if header_content.size >= HEADER_SIZE
109
+
110
+ return unless valid?
111
+
112
+ segment_table = file_io.read(page_segments).unpack("C" * page_segments)
113
+ @segments = segment_table.map { |segment_length| file_io.read(segment_length) }
114
+ end
115
+
116
+ def valid?
117
+ @capture_pattern == "OggS" && @version == 0
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WahWah
4
+ module Ogg
5
+ class Pages
6
+ include Enumerable
7
+
8
+ def initialize(file_io)
9
+ @file_io = file_io
10
+ end
11
+
12
+ def each
13
+ @file_io.rewind
14
+
15
+ until @file_io.eof?
16
+ page = Ogg::Page.new(@file_io)
17
+ break unless page.valid?
18
+
19
+ yield page
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WahWah
4
+ module Ogg
5
+ # Vorbis comment structure:
6
+ #
7
+ # 1) [vendor_length] = read an unsigned integer of 32 bits
8
+ # 2) [vendor_string] = read a UTF-8 vector as [vendor_length] octets
9
+ # 3) [user_comment_list_length] = read an unsigned integer of 32 bits
10
+ # 4) iterate [user_comment_list_length] times {
11
+ # 5) [length] = read an unsigned integer of 32 bits
12
+ # 6) this iteration’s user comment = read a UTF-8 vector as [length] octets
13
+ # }
14
+ # 7) [framing_bit] = read a single bit as boolean
15
+ # 8) if ( [framing_bit] unset or end-of-packet ) then ERROR
16
+ # 9) done.
17
+ module VorbisComment
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
28
+ }
29
+
30
+ def parse_vorbis_comment(comment_content)
31
+ comment_content = StringIO.new(comment_content)
32
+
33
+ vendor_length = comment_content.read(4).unpack1("V")
34
+ comment_content.seek(vendor_length, IO::SEEK_CUR) # Skip vendor_string
35
+
36
+ comment_list_length = comment_content.read(4).unpack1("V")
37
+
38
+ comment_list_length.times do
39
+ comment_length = comment_content.read(4).unpack1("V")
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&.upcase]
43
+
44
+ field_value = field_value.to_i if %i[track disc].include? attr_name
45
+
46
+ instance_variable_set("@#{attr_name}", field_value) unless attr_name.nil?
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WahWah
4
+ module Ogg
5
+ class VorbisTag
6
+ include VorbisComment
7
+
8
+ attr_reader :bitrate, :sample_rate, *COMMET_FIELD_MAPPING.values
9
+
10
+ def initialize(identification_packet, comment_packet)
11
+ # Identification packet structure:
12
+ #
13
+ # 1) "\x01vorbis"
14
+ # 2) [vorbis_version] = read 32 bits as unsigned integer
15
+ # 3) [audio_channels] = read 8 bit integer as unsigned
16
+ # 4) [audio_sample_rate] = read 32 bits as unsigned integer
17
+ # 5) [bitrate_maximum] = read 32 bits as signed integer
18
+ # 6) [bitrate_nominal] = read 32 bits as signed integer
19
+ # 7) [bitrate_minimum] = read 32 bits as signed integer
20
+ # 8) [blocksize_0] = 2 exponent (read 4 bits as unsigned integer)
21
+ # 9) [blocksize_1] = 2 exponent (read 4 bits as unsigned integer)
22
+ # 10) [framing_flag] = read one bit
23
+ @sample_rate, bitrate = identification_packet[12, 12].unpack("Vx4V")
24
+ @bitrate = bitrate / 1000
25
+
26
+ comment_packet_id, comment_packet_body = [comment_packet[0..6], comment_packet[7..]]
27
+
28
+ # Vorbis comment packet start with "\x03vorbis"
29
+ return unless comment_packet_id == "\x03vorbis"
30
+
31
+ parse_vorbis_comment(comment_packet_body)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WahWah
4
+ class OggTag < Tag
5
+ extend TagDelegate
6
+
7
+ tag_delegate :@tag,
8
+ :title,
9
+ :album,
10
+ :albumartist,
11
+ :track,
12
+ :artist,
13
+ :year,
14
+ :genre,
15
+ :disc,
16
+ :composer,
17
+ :sample_rate
18
+
19
+ private
20
+
21
+ def packets
22
+ @packets ||= Ogg::Packets.new(@file_io)
23
+ end
24
+
25
+ def pages
26
+ @pages ||= Ogg::Pages.new(@file_io)
27
+ end
28
+
29
+ def parse
30
+ identification_packet, comment_packet = packets.first(2)
31
+ return if identification_packet.nil? || comment_packet.nil?
32
+
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)
41
+ end
42
+
43
+ @duration = parse_duration
44
+ @bitrate = parse_bitrate
45
+ @bit_depth = parse_bit_depth
46
+ end
47
+
48
+ def parse_duration
49
+ return @tag.duration if @tag.respond_to? :duration
50
+
51
+ last_page = pages.to_a.last
52
+ pre_skip = @tag.respond_to?(:pre_skip) ? @tag.pre_skip : 0
53
+
54
+ (last_page.granule_position - pre_skip) / @tag.sample_rate.to_f
55
+ end
56
+
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
61
+
62
+ def parse_bit_depth
63
+ @tag.bit_depth if @tag.respond_to? :bit_depth
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WahWah
4
+ module Riff
5
+ # RIFF files consist entirely of "chunks".
6
+
7
+ # All chunks have the following format:
8
+
9
+ # 4 bytes: an ASCII identifier for this chunk (examples are "fmt " and "data"; note the space in "fmt ").
10
+ # 4 bytes: an unsigned, little-endian 32-bit integer with the length of this chunk (except this field itself and the chunk identifier).
11
+ # variable-sized field: the chunk data itself, of the size given in the previous field.
12
+ # a pad byte, if the chunk's length is not even.
13
+
14
+ # chunk identifiers, "RIFF" and "LIST", introduce a chunk that can contain subchunks. The RIFF and LIST chunk data (appearing after the identifier and length) have the following format:
15
+
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
+ # rest of data: subchunks.
18
+ class Chunk
19
+ prepend LazyRead
20
+
21
+ HEADER_SIZE = 8
22
+ HEADER_FORMAT = "A4V"
23
+ HEADER_TYPE_SIZE = 4
24
+
25
+ attr_reader :id, :type
26
+
27
+ def initialize
28
+ @id, @size = @file_io.read(HEADER_SIZE)&.unpack(HEADER_FORMAT)
29
+ return unless valid?
30
+
31
+ @type = @file_io.read(HEADER_TYPE_SIZE).unpack1("A4") if have_type?
32
+ end
33
+
34
+ def size
35
+ @size += 1 if @size.odd?
36
+ have_type? ? @size - HEADER_TYPE_SIZE : @size
37
+ end
38
+
39
+ def valid?
40
+ !@id.empty? && !@size.nil? && @size > 0
41
+ end
42
+
43
+ private
44
+
45
+ def have_type?
46
+ %w[RIFF LIST].include? @id
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WahWah
4
+ class RiffTag < Tag
5
+ extend TagDelegate
6
+
7
+ # see https://exiftool.org/TagNames/RIFF.html#Info for more info
8
+ INFO_ID_MAPPING = {
9
+ INAM: :title,
10
+ TITL: :title,
11
+ IART: :artist,
12
+ IPRD: :album,
13
+ ICMT: :comment,
14
+ ICRD: :year,
15
+ YEAR: :year,
16
+ IGNR: :genre,
17
+ TRCK: :track
18
+ }
19
+
20
+ CHANNEL_MODE_INDEX = %w[Mono Stereo]
21
+
22
+ tag_delegate :@id3_tag,
23
+ :title,
24
+ :artist,
25
+ :album,
26
+ :albumartist,
27
+ :composer,
28
+ :comments,
29
+ :track,
30
+ :track_total,
31
+ :genre,
32
+ :year,
33
+ :disc,
34
+ :disc_total,
35
+ :images
36
+
37
+ def channel_mode
38
+ CHANNEL_MODE_INDEX[@channel - 1]
39
+ end
40
+
41
+ private
42
+
43
+ def parse
44
+ top_chunk = Riff::Chunk.new(@file_io)
45
+ return unless top_chunk.valid?
46
+
47
+ total_chunk_size = top_chunk.size + Riff::Chunk::HEADER_SIZE
48
+
49
+ # The top "RIFF" chunks include an additional field in the first four bytes of the data field.
50
+ # This additional field provides the form type of the field.
51
+ # For wav file, the value of the type field is 'WAVE'
52
+ return unless top_chunk.id == "RIFF" && top_chunk.type == "WAVE"
53
+
54
+ until total_chunk_size <= @file_io.pos || @file_io.eof?
55
+ sub_chunk = Riff::Chunk.new(@file_io)
56
+ parse_sub_chunk(sub_chunk)
57
+ end
58
+ end
59
+
60
+ def parse_sub_chunk(sub_chunk)
61
+ return unless sub_chunk.valid?
62
+
63
+ case sub_chunk.id
64
+ when "fmt"
65
+ parse_fmt_chunk(sub_chunk)
66
+ when "data"
67
+ parse_data_chunk(sub_chunk)
68
+ when "LIST"
69
+ parse_list_chunk(sub_chunk)
70
+ when "id3", "ID3"
71
+ parse_id3_chunk(sub_chunk)
72
+ else
73
+ sub_chunk.skip
74
+ end
75
+ end
76
+
77
+ # The fmt chunk data structure:
78
+ # Length Meaning Description
79
+ #
80
+ # 2(little endian) AudioFormat PCM = 1 (i.e. Linear quantization)
81
+ # Values other than 1 indicate some
82
+ # form of compression.
83
+ #
84
+ # 2(little endian) NumChannels Mono = 1, Stereo = 2, etc.
85
+ #
86
+ # 4(little endian) SampleRate 8000, 44100, etc.
87
+ #
88
+ # 4(little endian) ByteRate == SampleRate * NumChannels * BitsPerSample/8
89
+ #
90
+ # 2(little endian) BlockAlign == NumChannels * BitsPerSample/8
91
+ # The number of bytes for one sample including
92
+ # all channels.
93
+ #
94
+ # 2(little endian) BitsPerSample 8 bits = 8, 16 bits = 16, etc.
95
+ def parse_fmt_chunk(chunk)
96
+ _, @channel, @sample_rate, _, _, @bit_depth = chunk.data.unpack("vvVVvv")
97
+ @bitrate = @sample_rate * @channel * @bit_depth / 1000
98
+ end
99
+
100
+ def parse_data_chunk(chunk)
101
+ @duration = chunk.size * 8 / (@bitrate * 1000).to_f
102
+ chunk.skip
103
+ end
104
+
105
+ def parse_list_chunk(chunk)
106
+ list_chunk_end_position = @file_io.pos + chunk.size
107
+
108
+ # RIFF can be tagged with metadata in the INFO chunk.
109
+ # And INFO chunk as a subchunk for LIST chunk.
110
+ if chunk.type != "INFO"
111
+ chunk.skip
112
+ else
113
+ until list_chunk_end_position <= @file_io.pos
114
+ info_chunk = Riff::Chunk.new(@file_io)
115
+
116
+ unless INFO_ID_MAPPING.key? info_chunk.id.to_sym
117
+ info_chunk.skip
118
+ next
119
+ end
120
+
121
+ update_attribute(info_chunk)
122
+ end
123
+ end
124
+ end
125
+
126
+ def parse_id3_chunk(chunk)
127
+ @id3_tag = ID3::V2.new(StringIO.new(chunk.data))
128
+ end
129
+
130
+ def update_attribute(chunk)
131
+ attr_name = INFO_ID_MAPPING[chunk.id.to_sym]
132
+ chunk_data = Helper.encode_to_utf8(chunk.data)
133
+
134
+ case attr_name
135
+ when :comment
136
+ @comments.push(chunk_data)
137
+ else
138
+ instance_variable_set("@#{attr_name}", chunk_data)
139
+ end
140
+ end
141
+ end
142
+ end
data/lib/wahwah/tag.rb ADDED
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WahWah
4
+ class Tag
5
+ INTEGER_ATTRIBUTES = %i[disc disc_total track track_total]
6
+ INSPECT_ATTRIBUTES = %i[title artist album albumartist composer track track_total genre year disc disc_total duration bitrate sample_rate bit_depth]
7
+
8
+ attr_reader(
9
+ :title,
10
+ :artist,
11
+ :album,
12
+ :albumartist,
13
+ :composer,
14
+ :comments,
15
+ :track,
16
+ :track_total,
17
+ :genre,
18
+ :year,
19
+ :disc,
20
+ :disc_total,
21
+ :duration,
22
+ :bitrate,
23
+ :sample_rate,
24
+ :bit_depth,
25
+ :file_size
26
+ )
27
+
28
+ def initialize(file)
29
+ if file.is_a?(IO) || file.is_a?(StringIO)
30
+ @file_size = file.size
31
+ @file_io = file
32
+ else
33
+ @file_size = File.size(file)
34
+ @file_io = File.open(file)
35
+ end
36
+
37
+ @comments = []
38
+ @images_data = []
39
+
40
+ parse if @file_size > 0
41
+
42
+ INTEGER_ATTRIBUTES.each do |attr_name|
43
+ value = instance_variable_get("@#{attr_name}")&.to_i
44
+ instance_variable_set("@#{attr_name}", value)
45
+ end
46
+ ensure
47
+ @file_io.close
48
+ end
49
+
50
+ def inspect
51
+ inspect_id = ::Kernel.format "%x", (object_id * 2)
52
+ inspect_attributes_values = INSPECT_ATTRIBUTES.map { |attr_name| "#{attr_name}=#{send(attr_name)}" }.join(" ")
53
+
54
+ "<#{self.class.name}:0x#{inspect_id} #{inspect_attributes_values}>"
55
+ end
56
+
57
+ def images
58
+ return @images_data if @images_data.empty?
59
+
60
+ @images_data.map do |data|
61
+ parse_image_data(data)
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def parse
68
+ raise WahWahNotImplementedError, "The parse method is not implemented"
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WahWah
4
+ module TagDelegate
5
+ def tag_delegate(accessor, *attributes)
6
+ attributes.each do |attr|
7
+ define_method(attr) do
8
+ tag = instance_variable_get(accessor)
9
+
10
+ return super() if tag.nil?
11
+ tag.send(attr)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,5 @@
1
- module Wahwah
2
- VERSION = "0.1.0"
1
+ # frozen_string_literal: true
2
+
3
+ module WahWah
4
+ VERSION = "1.2.0"
3
5
  end
data/lib/wahwah.rb CHANGED
@@ -1,5 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+ require "forwardable"
5
+ require "zlib"
6
+
1
7
  require "wahwah/version"
8
+ require "wahwah/errors"
9
+ require "wahwah/helper"
10
+ require "wahwah/tag_delegate"
11
+ require "wahwah/lazy_read"
12
+ require "wahwah/tag"
13
+
14
+ require "wahwah/id3/v1"
15
+ require "wahwah/id3/v2"
16
+ require "wahwah/id3/v2_header"
17
+ require "wahwah/id3/frame"
18
+ require "wahwah/id3/frame_body"
19
+ require "wahwah/id3/text_frame_body"
20
+ require "wahwah/id3/genre_frame_body"
21
+ require "wahwah/id3/comment_frame_body"
22
+ require "wahwah/id3/image_frame_body"
23
+
24
+ require "wahwah/mp3/mpeg_frame_header"
25
+ require "wahwah/mp3/xing_header"
26
+ require "wahwah/mp3/vbri_header"
27
+
28
+ require "wahwah/riff/chunk"
29
+
30
+ require "wahwah/flac/block"
31
+ require "wahwah/flac/streaminfo_block"
32
+
33
+ require "wahwah/ogg/page"
34
+ require "wahwah/ogg/pages"
35
+ require "wahwah/ogg/packets"
36
+ require "wahwah/ogg/vorbis_comment"
37
+ require "wahwah/ogg/vorbis_tag"
38
+ require "wahwah/ogg/opus_tag"
39
+ require "wahwah/ogg/flac_tag"
40
+
41
+ require "wahwah/asf/object"
42
+
43
+ require "wahwah/mp4/atom"
44
+
45
+ require "wahwah/mp3_tag"
46
+ require "wahwah/mp4_tag"
47
+ require "wahwah/ogg_tag"
48
+ require "wahwah/riff_tag"
49
+ require "wahwah/asf_tag"
50
+ require "wahwah/flac_tag"
51
+
52
+ module WahWah
53
+ FORMATE_MAPPING = {
54
+ Mp3Tag: ["mp3"],
55
+ OggTag: ["ogg", "oga", "opus"],
56
+ RiffTag: ["wav"],
57
+ FlacTag: ["flac"],
58
+ AsfTag: ["wma"],
59
+ Mp4Tag: ["m4a"]
60
+ }.freeze
61
+
62
+ def self.open(file_path)
63
+ file_path = file_path.to_path if file_path.respond_to? :to_path
64
+ file_path = file_path.to_str
65
+
66
+ file_format = Helper.file_format(file_path)
67
+
68
+ raise WahWahArgumentError, "File is not exists" unless File.exist? file_path
69
+ raise WahWahArgumentError, "File is unreadable" unless File.readable? file_path
70
+ raise WahWahArgumentError, "File is empty" unless File.size(file_path) > 0
71
+ raise WahWahArgumentError, "No supported format found" unless support_formats.include? file_format
72
+
73
+ FORMATE_MAPPING.each do |tag, formats|
74
+ break const_get(tag).new(file_path) if formats.include?(file_format)
75
+ end
76
+ end
2
77
 
3
- module Wahwah
4
- # Your code goes here...
78
+ def self.support_formats
79
+ FORMATE_MAPPING.values.flatten
80
+ end
5
81
  end