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.
- checksums.yaml +4 -4
- data/.gitignore +8 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +22 -0
- data/README.md +35 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/wahwah.rb +3 -74
- data/lib/wahwah/version.rb +2 -4
- data/wahwah.gemspec +27 -0
- metadata +25 -96
- data/LICENSE +0 -21
- data/lib/wahwah/asf/object.rb +0 -39
- data/lib/wahwah/asf_tag.rb +0 -220
- data/lib/wahwah/errors.rb +0 -6
- data/lib/wahwah/flac/block.rb +0 -57
- data/lib/wahwah/flac/streaminfo_block.rb +0 -51
- data/lib/wahwah/flac_tag.rb +0 -84
- data/lib/wahwah/helper.rb +0 -37
- data/lib/wahwah/id3/comment_frame_body.rb +0 -21
- data/lib/wahwah/id3/frame.rb +0 -180
- data/lib/wahwah/id3/frame_body.rb +0 -36
- data/lib/wahwah/id3/genre_frame_body.rb +0 -15
- data/lib/wahwah/id3/image_frame_body.rb +0 -60
- data/lib/wahwah/id3/text_frame_body.rb +0 -16
- data/lib/wahwah/id3/v1.rb +0 -96
- data/lib/wahwah/id3/v2.rb +0 -60
- data/lib/wahwah/id3/v2_header.rb +0 -53
- data/lib/wahwah/mp3/mpeg_frame_header.rb +0 -141
- data/lib/wahwah/mp3/vbri_header.rb +0 -47
- data/lib/wahwah/mp3/xing_header.rb +0 -45
- data/lib/wahwah/mp3_tag.rb +0 -110
- data/lib/wahwah/mp4/atom.rb +0 -105
- data/lib/wahwah/mp4_tag.rb +0 -126
- data/lib/wahwah/ogg/flac_tag.rb +0 -37
- data/lib/wahwah/ogg/opus_tag.rb +0 -33
- data/lib/wahwah/ogg/packets.rb +0 -41
- data/lib/wahwah/ogg/page.rb +0 -121
- data/lib/wahwah/ogg/pages.rb +0 -24
- data/lib/wahwah/ogg/vorbis_comment.rb +0 -51
- data/lib/wahwah/ogg/vorbis_tag.rb +0 -35
- data/lib/wahwah/ogg_tag.rb +0 -66
- data/lib/wahwah/riff/chunk.rb +0 -54
- data/lib/wahwah/riff_tag.rb +0 -140
- data/lib/wahwah/tag.rb +0 -59
- data/lib/wahwah/tag_delegate.rb +0 -16
data/lib/wahwah/ogg/pages.rb
DELETED
@@ -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
|
data/lib/wahwah/ogg_tag.rb
DELETED
@@ -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
|
data/lib/wahwah/riff/chunk.rb
DELETED
@@ -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
|
data/lib/wahwah/riff_tag.rb
DELETED
@@ -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
|
data/lib/wahwah/tag_delegate.rb
DELETED
@@ -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
|