wahwah 1.5.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ae84eaac046bfc70016dd80a23f1c246066d70264c064fd048127946a40bd51
4
- data.tar.gz: bea598207d92e57cac06bfc948b1ea86a70b68b7ffd61c2231a5b2f75d193785
3
+ metadata.gz: 7ed4753543c1ad49e2c3c274198e3bc9a4a2416ff4e8937d6d418587f777756f
4
+ data.tar.gz: 16079b5131d51a3ba203a859acd7c17627a34d705105234a4a15a00bd68c9eb3
5
5
  SHA512:
6
- metadata.gz: cc3e3ad5fb44882840f7fe1d21d77aa722a0bc5b13ef41b1d8243f6dc24ec83ac83ce2765e5941f09e7e097b458810235f11235d6b9b997623b9910082d8e023
7
- data.tar.gz: 4f5fd37cbe0eadfec74eae4b73952bd9fe03b5a8ce67569c4662f54e142a33a163c94722257b6d461286b537b7c59cd0c52b33728659f606d1a1219b10332e00
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(file_path)
29
- File.extname(file_path).downcase.delete(".")
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
@@ -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?
@@ -50,11 +50,20 @@ module WahWah
50
50
  end
51
51
 
52
52
  def parse_id3_tag
53
- id3_v1_tag = ID3::V1.new(@file_io.dup)
54
- id3_v2_tag = ID3::V2.new(@file_io.dup)
55
-
56
- return id3_v2_tag if id3_v2_tag.valid?
57
- id3_v1_tag if id3_v1_tag.valid?
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
@@ -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
 
@@ -37,7 +37,7 @@ module WahWah
37
37
  end
38
38
 
39
39
  def valid?
40
- !@id.empty? && !@size.nil? && @size > 0
40
+ @id && !@id.empty? && !@size.nil? && @size > 0
41
41
  end
42
42
 
43
43
  private
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 duration bitrate sample_rate bit_depth]
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(file)
30
- if file.is_a?(IO) || file.is_a?(StringIO)
31
- @file_size = file.size
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WahWah
4
- VERSION = "1.5.1"
4
+ VERSION = "1.6.0"
5
5
  end
data/lib/wahwah.rb CHANGED
@@ -60,23 +60,36 @@ module WahWah
60
60
  Mp4Tag: ["m4a"]
61
61
  }.freeze
62
62
 
63
- def self.open(file_path)
64
- file_path = file_path.to_path if file_path.respond_to? :to_path
65
- file_path = file_path.to_str
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
- file_format = Helper.file_format(file_path)
67
+ raise WahWahArgumentError, "No supported format found" unless support_formats.include? file_format
68
68
 
69
- raise WahWahArgumentError, "File is not exists" unless File.exist? file_path
70
- raise WahWahArgumentError, "File is unreadable" unless File.readable? file_path
71
- raise WahWahArgumentError, "File is empty" unless File.size(file_path) > 0
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.5.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: 2023-08-09 00:00:00.000000000 Z
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.26
178
+ rubygems_version: 3.3.27
179
179
  signing_key:
180
180
  specification_version: 4
181
181
  summary: Audio metadata reader ruby gem