wahwah 1.5.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/wahwah/helper.rb +41 -2
- data/lib/wahwah/id3/v2_header.rb +1 -1
- data/lib/wahwah/mp3_tag.rb +14 -5
- data/lib/wahwah/ogg_tag.rb +14 -2
- data/lib/wahwah/riff/chunk.rb +1 -1
- data/lib/wahwah/riff_tag.rb +6 -0
- data/lib/wahwah/tag.rb +5 -12
- data/lib/wahwah/version.rb +1 -1
- data/lib/wahwah.rb +24 -11
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ed4753543c1ad49e2c3c274198e3bc9a4a2416ff4e8937d6d418587f777756f
|
4
|
+
data.tar.gz: 16079b5131d51a3ba203a859acd7c17627a34d705105234a4a15a00bd68c9eb3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf8adc7cef79b90caa947f5997a489b7b678defac1efc64fc51e2e95c9a50453a762dcf03a8295e4036cd5c9b6bd55fbd565381ba7d1fa9fcc74008bc4013eb0
|
7
|
+
data.tar.gz: a09f45c83ce58685d6da2df6ebffc20883acb2ce70cf95168c6896659b98fd682d696f3c52a066b092ac92694a9c3cb92e790c08edf3d0485e4a125116fac9ed
|
data/lib/wahwah/helper.rb
CHANGED
@@ -25,13 +25,52 @@ module WahWah
|
|
25
25
|
string.split(Regexp.new(('\x00' * terminator_size).b), 2)
|
26
26
|
end
|
27
27
|
|
28
|
-
def self.file_format(
|
29
|
-
|
28
|
+
def self.file_format(io)
|
29
|
+
if io.respond_to?(:path) && io.path && !io.path.empty?
|
30
|
+
file_format = file_format_from_extension io.path
|
31
|
+
return file_format unless file_format.nil? || file_format.empty?
|
32
|
+
end
|
33
|
+
|
34
|
+
file_format_from_signature io
|
30
35
|
end
|
31
36
|
|
32
37
|
def self.byte_string_to_guid(byte_string)
|
33
38
|
guid = byte_string.unpack("NnnA*").pack("VvvA*").unpack1("H*")
|
34
39
|
[guid[0..7], guid[8..11], guid[12..15], guid[16..19], guid[20..]].join("-").upcase
|
35
40
|
end
|
41
|
+
|
42
|
+
def self.file_format_from_extension(file_path)
|
43
|
+
File.extname(file_path).downcase[1..]
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.file_format_from_signature(io)
|
47
|
+
io.rewind
|
48
|
+
signature = io.read(16)
|
49
|
+
|
50
|
+
# Source: https://en.wikipedia.org/wiki/List_of_file_signatures
|
51
|
+
|
52
|
+
# M4A is checked for first, since MP4 files start with a chunk size -
|
53
|
+
# and that chunk size may incidentally match another signature.
|
54
|
+
# No other formats would reasonably have "ftyp" as the next for bytes.
|
55
|
+
return "m4a" if signature[4...12] == "ftypM4A ".b
|
56
|
+
# Handled separately simply because it requires two checks.
|
57
|
+
return "wav" if signature.start_with?("RIFF".b) && signature[8...12] == "WAVE".b
|
58
|
+
magic_numbers = {
|
59
|
+
"fLaC".b => "flac",
|
60
|
+
"\xFF\xFB".b => "mp3",
|
61
|
+
"\xFF\xF3".b => "mp3",
|
62
|
+
"\xFF\xF2".b => "mp3",
|
63
|
+
"ID3".b => "mp3",
|
64
|
+
"OggS".b => "ogg",
|
65
|
+
"\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C".b => "wma"
|
66
|
+
}
|
67
|
+
magic_numbers.each do |expected_signature, file_format|
|
68
|
+
return file_format if signature.start_with? expected_signature
|
69
|
+
end
|
70
|
+
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
|
74
|
+
private_class_method :file_format_from_extension, :file_format_from_signature
|
36
75
|
end
|
37
76
|
end
|
data/lib/wahwah/id3/v2_header.rb
CHANGED
@@ -17,7 +17,7 @@ module WahWah
|
|
17
17
|
attr_reader :major_version, :size
|
18
18
|
|
19
19
|
def initialize(file_io)
|
20
|
-
header_content = file_io.read(HEADER_SIZE)
|
20
|
+
header_content = file_io.read(HEADER_SIZE) || ""
|
21
21
|
@id, @major_version, @flags, size_bits = header_content.unpack(HEADER_FORMAT) if header_content.size >= HEADER_SIZE
|
22
22
|
|
23
23
|
return unless valid?
|
data/lib/wahwah/mp3_tag.rb
CHANGED
@@ -50,11 +50,20 @@ module WahWah
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def parse_id3_tag
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
@file_io.rewind
|
54
|
+
signature = @file_io.read(6)
|
55
|
+
|
56
|
+
if signature.start_with?("ID3".b)
|
57
|
+
id3_v2_tag = ID3::V2.new(@file_io)
|
58
|
+
id3_v2_tag if id3_v2_tag.valid?
|
59
|
+
elsif ["\xFF\xFB".b, "\xFF\xF3".b, "\xFF\xF2".b].include? signature[...2]
|
60
|
+
# It's important to only initialize an ID3v1 object as the last option,
|
61
|
+
# as it requires reading to the end of the file. If the file is a
|
62
|
+
# streaming download (using, for example, Down), that requires
|
63
|
+
# downloading the whole file, which is not the case with ID3v2.
|
64
|
+
id3_v1_tag = ID3::V1.new(@file_io.dup)
|
65
|
+
id3_v1_tag if id3_v1_tag.valid?
|
66
|
+
end
|
58
67
|
end
|
59
68
|
|
60
69
|
def parse_duration
|
data/lib/wahwah/ogg_tag.rb
CHANGED
@@ -17,6 +17,20 @@ module WahWah
|
|
17
17
|
:sample_rate,
|
18
18
|
:lyrics
|
19
19
|
|
20
|
+
# Oggs require reading to the end of the file in order to calculate their
|
21
|
+
# duration (as it's not stored in any header or metadata). Thus, if the
|
22
|
+
# IO like object supplied to WahWah is a streaming download, getting the
|
23
|
+
# duration would download the entire audio file, which may not be
|
24
|
+
# desirable. Making it lazy in this manner allows the user to avoid that.
|
25
|
+
def duration
|
26
|
+
@duration ||= parse_duration
|
27
|
+
end
|
28
|
+
|
29
|
+
# Requires duration to calculate, so lazy as well.
|
30
|
+
def bitrate
|
31
|
+
@bitrate ||= parse_bitrate
|
32
|
+
end
|
33
|
+
|
20
34
|
private
|
21
35
|
|
22
36
|
def packets
|
@@ -41,8 +55,6 @@ module WahWah
|
|
41
55
|
Ogg::FlacTag.new(identification_packet, comment_packet)
|
42
56
|
end
|
43
57
|
|
44
|
-
@duration = parse_duration
|
45
|
-
@bitrate = parse_bitrate
|
46
58
|
@bit_depth = parse_bit_depth
|
47
59
|
end
|
48
60
|
|
data/lib/wahwah/riff/chunk.rb
CHANGED
data/lib/wahwah/riff_tag.rb
CHANGED
@@ -95,6 +95,12 @@ module WahWah
|
|
95
95
|
def parse_fmt_chunk(chunk)
|
96
96
|
_, @channel, @sample_rate, _, _, @bit_depth = chunk.data.unpack("vvVVvv")
|
97
97
|
@bitrate = @sample_rate * @channel * @bit_depth / 1000
|
98
|
+
|
99
|
+
# There is a rare situation where the data chunk is not included in the top RIFF chunk.
|
100
|
+
# In this case, the duration can not be got from the data chunk, some player even cannot play the file.
|
101
|
+
# So try to estimate the duration from the file size and bitrate.
|
102
|
+
# This may be inaccurate, but at least can get approximate duration.
|
103
|
+
@duration = (@file_size * 8) / (@bitrate * 1000).to_f if @duration.nil?
|
98
104
|
end
|
99
105
|
|
100
106
|
def parse_data_chunk(chunk)
|
data/lib/wahwah/tag.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module WahWah
|
4
4
|
class Tag
|
5
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
|
6
|
+
INSPECT_ATTRIBUTES = %i[title artist album albumartist composer track track_total genre year disc disc_total]
|
7
7
|
|
8
8
|
attr_reader(
|
9
9
|
:title,
|
@@ -26,14 +26,9 @@ module WahWah
|
|
26
26
|
:file_size
|
27
27
|
)
|
28
28
|
|
29
|
-
def initialize(
|
30
|
-
|
31
|
-
|
32
|
-
@file_io = file
|
33
|
-
else
|
34
|
-
@file_size = File.size(file)
|
35
|
-
@file_io = File.open(file)
|
36
|
-
end
|
29
|
+
def initialize(io)
|
30
|
+
@file_size = io.size
|
31
|
+
@file_io = io
|
37
32
|
|
38
33
|
@comments = []
|
39
34
|
@images_data = []
|
@@ -44,8 +39,6 @@ module WahWah
|
|
44
39
|
value = instance_variable_get("@#{attr_name}")&.to_i
|
45
40
|
instance_variable_set("@#{attr_name}", value)
|
46
41
|
end
|
47
|
-
ensure
|
48
|
-
@file_io.close
|
49
42
|
end
|
50
43
|
|
51
44
|
def inspect
|
@@ -58,7 +51,7 @@ module WahWah
|
|
58
51
|
def images
|
59
52
|
return @images_data if @images_data.empty?
|
60
53
|
|
61
|
-
@images_data.map do |data|
|
54
|
+
@images ||= @images_data.map do |data|
|
62
55
|
parse_image_data(data)
|
63
56
|
end
|
64
57
|
end
|
data/lib/wahwah/version.rb
CHANGED
data/lib/wahwah.rb
CHANGED
@@ -60,23 +60,36 @@ module WahWah
|
|
60
60
|
Mp4Tag: ["m4a"]
|
61
61
|
}.freeze
|
62
62
|
|
63
|
-
def self.open(
|
64
|
-
|
65
|
-
|
63
|
+
def self.open(path_or_io)
|
64
|
+
with_io path_or_io do |io|
|
65
|
+
file_format = Helper.file_format io
|
66
66
|
|
67
|
-
|
67
|
+
raise WahWahArgumentError, "No supported format found" unless support_formats.include? file_format
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
raise WahWahArgumentError, "No supported format found" unless support_formats.include? file_format
|
73
|
-
|
74
|
-
FORMATE_MAPPING.each do |tag, formats|
|
75
|
-
break const_get(tag).new(file_path) if formats.include?(file_format)
|
69
|
+
FORMATE_MAPPING.each do |tag, formats|
|
70
|
+
break const_get(tag).new(io) if formats.include?(file_format)
|
71
|
+
end
|
76
72
|
end
|
77
73
|
end
|
78
74
|
|
79
75
|
def self.support_formats
|
80
76
|
FORMATE_MAPPING.values.flatten
|
81
77
|
end
|
78
|
+
|
79
|
+
def self.with_io(path_or_io, &block)
|
80
|
+
path_or_io = Pathname.new path_or_io if path_or_io.respond_to? :to_str
|
81
|
+
|
82
|
+
if path_or_io.is_a? Pathname
|
83
|
+
raise WahWahArgumentError, "File does not exist" unless File.exist? path_or_io
|
84
|
+
raise WahWahArgumentError, "File is unreadable" unless File.readable? path_or_io
|
85
|
+
raise WahWahArgumentError, "File is empty" unless File.size(path_or_io) > 0
|
86
|
+
|
87
|
+
path_or_io.open(&block)
|
88
|
+
else
|
89
|
+
|
90
|
+
block.call path_or_io
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private_class_method :with_io
|
82
95
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wahwah
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- aidewoode
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-06-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -175,7 +175,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
175
175
|
- !ruby/object:Gem::Version
|
176
176
|
version: '0'
|
177
177
|
requirements: []
|
178
|
-
rubygems_version: 3.3.
|
178
|
+
rubygems_version: 3.3.27
|
179
179
|
signing_key:
|
180
180
|
specification_version: 4
|
181
181
|
summary: Audio metadata reader ruby gem
|