wahwah 0.1.0.pre.test → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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