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