wahwah 1.5.0 → 1.6.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/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
|