wahwah 0.1.0.pre.test → 0.1.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +22 -0
  6. data/README.md +35 -0
  7. data/Rakefile +10 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +8 -0
  10. data/lib/wahwah.rb +3 -74
  11. data/lib/wahwah/version.rb +2 -4
  12. data/wahwah.gemspec +27 -0
  13. metadata +25 -96
  14. data/LICENSE +0 -21
  15. data/lib/wahwah/asf/object.rb +0 -39
  16. data/lib/wahwah/asf_tag.rb +0 -220
  17. data/lib/wahwah/errors.rb +0 -6
  18. data/lib/wahwah/flac/block.rb +0 -57
  19. data/lib/wahwah/flac/streaminfo_block.rb +0 -51
  20. data/lib/wahwah/flac_tag.rb +0 -84
  21. data/lib/wahwah/helper.rb +0 -37
  22. data/lib/wahwah/id3/comment_frame_body.rb +0 -21
  23. data/lib/wahwah/id3/frame.rb +0 -180
  24. data/lib/wahwah/id3/frame_body.rb +0 -36
  25. data/lib/wahwah/id3/genre_frame_body.rb +0 -15
  26. data/lib/wahwah/id3/image_frame_body.rb +0 -60
  27. data/lib/wahwah/id3/text_frame_body.rb +0 -16
  28. data/lib/wahwah/id3/v1.rb +0 -96
  29. data/lib/wahwah/id3/v2.rb +0 -60
  30. data/lib/wahwah/id3/v2_header.rb +0 -53
  31. data/lib/wahwah/mp3/mpeg_frame_header.rb +0 -141
  32. data/lib/wahwah/mp3/vbri_header.rb +0 -47
  33. data/lib/wahwah/mp3/xing_header.rb +0 -45
  34. data/lib/wahwah/mp3_tag.rb +0 -110
  35. data/lib/wahwah/mp4/atom.rb +0 -105
  36. data/lib/wahwah/mp4_tag.rb +0 -126
  37. data/lib/wahwah/ogg/flac_tag.rb +0 -37
  38. data/lib/wahwah/ogg/opus_tag.rb +0 -33
  39. data/lib/wahwah/ogg/packets.rb +0 -41
  40. data/lib/wahwah/ogg/page.rb +0 -121
  41. data/lib/wahwah/ogg/pages.rb +0 -24
  42. data/lib/wahwah/ogg/vorbis_comment.rb +0 -51
  43. data/lib/wahwah/ogg/vorbis_tag.rb +0 -35
  44. data/lib/wahwah/ogg_tag.rb +0 -66
  45. data/lib/wahwah/riff/chunk.rb +0 -54
  46. data/lib/wahwah/riff_tag.rb +0 -140
  47. data/lib/wahwah/tag.rb +0 -59
  48. data/lib/wahwah/tag_delegate.rb +0 -16
@@ -1,24 +0,0 @@
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
@@ -1,51 +0,0 @@
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).unpack('V').first
34
- comment_content.seek(vendor_length, IO::SEEK_CUR) # Skip vendor_string
35
-
36
- comment_list_length = comment_content.read(4).unpack('V').first
37
-
38
- comment_list_length.times do
39
- comment_length = comment_content.read(4).unpack('V').first
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]
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
@@ -1,35 +0,0 @@
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..-1]]
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
@@ -1,66 +0,0 @@
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
- def duration
20
- @duration ||= parse_duration
21
- end
22
-
23
- def bitrate
24
- @bitrate ||= parse_bitrate
25
- end
26
-
27
- private
28
- def packets
29
- @packets ||= Ogg::Packets.new(@file_io)
30
- end
31
-
32
- def pages
33
- @pages ||= Ogg::Pages.new(@file_io)
34
- end
35
-
36
- def parse
37
- identification_packet, comment_packet = packets.first(2)
38
- return if identification_packet.nil? || comment_packet.nil?
39
-
40
- @overhead_packets_size = identification_packet.size + comment_packet.size
41
-
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
-
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
57
-
58
- ((last_page.granule_position - pre_skip) / @tag.sample_rate.to_f).round
59
- end
60
-
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
65
- end
66
- end
@@ -1,54 +0,0 @@
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
- HEADER_SIZE = 8
20
- HEADER_FORMAT = 'A4V'
21
- HEADER_TYPE_SIZE = 4
22
-
23
- attr_reader :id, :type
24
-
25
- def initialize(file_io)
26
- @id, @size = file_io.read(HEADER_SIZE)&.unpack(HEADER_FORMAT)
27
- return unless valid?
28
-
29
- @type = file_io.read(HEADER_TYPE_SIZE).unpack('A4').first if have_type?
30
- @file_io = file_io
31
- @position = file_io.pos
32
- end
33
-
34
- def size
35
- @size = @size + 1 if @size.odd?
36
- have_type? ? @size - HEADER_TYPE_SIZE : @size
37
- end
38
-
39
- def data
40
- @file_io.seek(@position)
41
- @file_io.read(size)
42
- end
43
-
44
- def valid?
45
- !@id.empty? && !@size.nil? && @size > 0
46
- end
47
-
48
- private
49
- def have_type?
50
- %w(RIFF LIST).include? @id
51
- end
52
- end
53
- end
54
- end
@@ -1,140 +0,0 @@
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
- def parse
43
- top_chunk = Riff::Chunk.new(@file_io)
44
- return unless top_chunk.valid?
45
-
46
- total_chunk_size = top_chunk.size + Riff::Chunk::HEADER_SIZE
47
-
48
- # The top "RIFF" chunks include an additional field in the first four bytes of the data field.
49
- # This additional field provides the form type of the field.
50
- # For wav file, the value of the type field is 'WAVE'
51
- return unless top_chunk.id == 'RIFF' && top_chunk.type == 'WAVE'
52
-
53
- until total_chunk_size <= @file_io.pos || @file_io.eof? do
54
- sub_chunk = Riff::Chunk.new(@file_io)
55
- parse_sub_chunk(sub_chunk)
56
- end
57
- end
58
-
59
- def parse_sub_chunk(sub_chunk)
60
- return unless sub_chunk.valid?
61
-
62
- case sub_chunk.id
63
- when 'fmt'
64
- parse_fmt_chunk(sub_chunk)
65
- when 'data'
66
- parse_data_chunk(sub_chunk)
67
- when 'LIST'
68
- parse_list_chunk(sub_chunk)
69
- when 'id3', 'ID3'
70
- parse_id3_chunk(sub_chunk)
71
- else
72
- @file_io.seek(sub_chunk.size, IO::SEEK_CUR)
73
- end
74
- end
75
-
76
- # The fmt chunk data structure:
77
- # Length Meaning Description
78
- #
79
- # 2(little endian) AudioFormat PCM = 1 (i.e. Linear quantization)
80
- # Values other than 1 indicate some
81
- # form of compression.
82
- #
83
- # 2(little endian) NumChannels Mono = 1, Stereo = 2, etc.
84
- #
85
- # 4(little endian) SampleRate 8000, 44100, etc.
86
- #
87
- # 4(little endian) ByteRate == SampleRate * NumChannels * BitsPerSample/8
88
- #
89
- # 2(little endian) BlockAlign == NumChannels * BitsPerSample/8
90
- # The number of bytes for one sample including
91
- # all channels.
92
- #
93
- # 2(little endian) BitsPerSample 8 bits = 8, 16 bits = 16, etc.
94
- def parse_fmt_chunk(chunk)
95
- _, @channel, @sample_rate, _, _, @bits_per_sample = chunk.data.unpack('vvVVvv')
96
- @bitrate = @sample_rate * @channel * @bits_per_sample / 1000
97
- end
98
-
99
- def parse_data_chunk(chunk)
100
- @duration = chunk.size * 8 / (@bitrate * 1000)
101
- @file_io.seek(chunk.size, IO::SEEK_CUR)
102
- end
103
-
104
- def parse_list_chunk(chunk)
105
- list_chunk_end_position = @file_io.pos + chunk.size
106
-
107
- # RIFF can be tagged with metadata in the INFO chunk.
108
- # And INFO chunk as a subchunk for LIST chunk.
109
- if chunk.type != 'INFO'
110
- @file_io.seek(chunk.size, IO::SEEK_CUR)
111
- else
112
- until list_chunk_end_position <= @file_io.pos do
113
- info_chunk = Riff::Chunk.new(@file_io)
114
-
115
- unless INFO_ID_MAPPING.keys.include? info_chunk.id.to_sym
116
- @file_io.seek(info_chunk.size, IO::SEEK_CUR); next
117
- end
118
-
119
- update_attribute(info_chunk)
120
- end
121
- end
122
- end
123
-
124
- def parse_id3_chunk(chunk)
125
- @id3_tag = ID3::V2.new(StringIO.new(chunk.data))
126
- end
127
-
128
- def update_attribute(chunk)
129
- attribute_name = INFO_ID_MAPPING[chunk.id.to_sym]
130
- chunk_data = Helper.encode_to_utf8(chunk.data)
131
-
132
- case attribute_name
133
- when :comment
134
- @comments.push(chunk_data)
135
- else
136
- instance_variable_set("@#{attribute_name}", chunk_data)
137
- end
138
- end
139
- end
140
- end
data/lib/wahwah/tag.rb DELETED
@@ -1,59 +0,0 @@
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)
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
- :images,
22
- :duration,
23
- :bitrate,
24
- :sample_rate,
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 = []
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
- end
47
-
48
- def parse
49
- raise WahWahNotImplementedError, 'The parse method is not implemented'
50
- end
51
-
52
- def inspect
53
- inspect_id = ::Kernel.format '%x', (object_id * 2)
54
- inspect_attributes_values = INSPECT_ATTRIBUTES.map { |attr_name| "#{attr_name}=#{self.send(attr_name)}" }.join(' ')
55
-
56
- "<#{self.class.name}:0x#{inspect_id} #{inspect_attributes_values}>"
57
- end
58
- end
59
- end
@@ -1,16 +0,0 @@
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