wahwah 1.5.1 → 1.6.1
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 +50 -3
- data/lib/wahwah/id3/image_frame_body.rb +2 -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/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: d366d9e5b09b02f94de258c09e03b9d107dda83fe31052bf9b93696e669aac08
|
4
|
+
data.tar.gz: 5a6cc11a98174b658d2b35d3440940023e7aec4a2cb964636c58930d9690338a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 47ad25531fe9835c08f0db10ca919cb73f5688237d19586e7b4f0b7cfacd85c176d2e6555243a1ed566c092dcfea83f39fd9f5c9869240f9a0f3b4b2c056bd64
|
7
|
+
data.tar.gz: 8284a87d145252d60146f5bf2a2fa42c0201422b72ecc4755388a65ff6d2e890b2d029059d9c5d7199b9dd7f873309e38b0890faeae8a3910b960e38556f4c53
|
data/lib/wahwah/helper.rb
CHANGED
@@ -22,16 +22,63 @@ module WahWah
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def self.split_with_terminator(string, terminator_size)
|
25
|
-
|
25
|
+
terminator = ("\x00" * terminator_size).b
|
26
|
+
|
27
|
+
(0...string.size).step(terminator_size).each do |index|
|
28
|
+
if string[index...(index + terminator_size)] == terminator
|
29
|
+
return [string[0...index], string[index + terminator_size..]]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
[]
|
26
34
|
end
|
27
35
|
|
28
|
-
def self.file_format(
|
29
|
-
|
36
|
+
def self.file_format(io)
|
37
|
+
if io.respond_to?(:path) && io.path && !io.path.empty?
|
38
|
+
file_format = file_format_from_extension io.path
|
39
|
+
return file_format unless file_format.nil? || file_format.empty?
|
40
|
+
end
|
41
|
+
|
42
|
+
file_format_from_signature io
|
30
43
|
end
|
31
44
|
|
32
45
|
def self.byte_string_to_guid(byte_string)
|
33
46
|
guid = byte_string.unpack("NnnA*").pack("VvvA*").unpack1("H*")
|
34
47
|
[guid[0..7], guid[8..11], guid[12..15], guid[16..19], guid[20..]].join("-").upcase
|
35
48
|
end
|
49
|
+
|
50
|
+
def self.file_format_from_extension(file_path)
|
51
|
+
File.extname(file_path).downcase[1..]
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.file_format_from_signature(io)
|
55
|
+
io.rewind
|
56
|
+
signature = io.read(16)
|
57
|
+
|
58
|
+
# Source: https://en.wikipedia.org/wiki/List_of_file_signatures
|
59
|
+
|
60
|
+
# M4A is checked for first, since MP4 files start with a chunk size -
|
61
|
+
# and that chunk size may incidentally match another signature.
|
62
|
+
# No other formats would reasonably have "ftyp" as the next for bytes.
|
63
|
+
return "m4a" if signature[4...12] == "ftypM4A ".b
|
64
|
+
# Handled separately simply because it requires two checks.
|
65
|
+
return "wav" if signature.start_with?("RIFF".b) && signature[8...12] == "WAVE".b
|
66
|
+
magic_numbers = {
|
67
|
+
"fLaC".b => "flac",
|
68
|
+
"\xFF\xFB".b => "mp3",
|
69
|
+
"\xFF\xF3".b => "mp3",
|
70
|
+
"\xFF\xF2".b => "mp3",
|
71
|
+
"ID3".b => "mp3",
|
72
|
+
"OggS".b => "ogg",
|
73
|
+
"\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C".b => "wma"
|
74
|
+
}
|
75
|
+
magic_numbers.each do |expected_signature, file_format|
|
76
|
+
return file_format if signature.start_with? expected_signature
|
77
|
+
end
|
78
|
+
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
|
82
|
+
private_class_method :file_format_from_extension, :file_format_from_signature
|
36
83
|
end
|
37
84
|
end
|
@@ -49,9 +49,9 @@ module WahWah
|
|
49
49
|
# Picture data <binary data>
|
50
50
|
def parse
|
51
51
|
frame_format = @version > 2 ? "CZ*Ca*" : "Ca3Ca*"
|
52
|
-
encoding_id, @mime_type, type_index,
|
52
|
+
encoding_id, @mime_type, type_index, rest_content = @content.unpack(frame_format)
|
53
53
|
encoding = ENCODING_MAPPING[encoding_id]
|
54
|
-
_description, data = Helper.split_with_terminator(
|
54
|
+
_description, data = Helper.split_with_terminator(rest_content, ENCODING_TERMINATOR_SIZE[encoding])
|
55
55
|
|
56
56
|
@value = {data: data, mime_type: mime_type, type: TYPES[type_index]}
|
57
57
|
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/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.1
|
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-07 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
|