ruby-audioinfo 0.5.2 → 0.5.4

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.
@@ -1,146 +1,145 @@
1
- require "audioinfo"
1
+ # frozen_string_literal: true
2
2
 
3
- class AudioInfo::Album
3
+ require 'audioinfo'
4
4
 
5
- IMAGE_EXTENSIONS = %w{jpg jpeg gif png}
5
+ module AudioInfo
6
+ class Album
7
+ IMAGE_EXTENSIONS = %w[jpg jpeg gif png].freeze
6
8
 
7
- # a regexp to match the "multicd" suffix of a "multicd" string
8
- # example: "toto (disc 1)" will match ' (disc 1)'
9
- MULTICD_REGEXP = /\s*(\(|\[)?\s*(disc|cd):?-?\s*(\d+).*(\)|\])?\s*$/i
9
+ # a regexp to match the "multicd" suffix of a "multicd" string
10
+ # example: "toto (disc 1)" will match ' (disc 1)'
11
+ MULTICD_REGEXP = /\s*(\(|\[)?\s*(disc|cd):?-?\s*(\d+).*(\)|\])?\s*$/i.freeze
10
12
 
11
- attr_reader :files, :discnum, :multicd, :basename, :infos, :path
13
+ attr_reader :files, :discnum, :multicd, :basename, :infos, :path
12
14
 
13
- # return the list of images in the album directory, with "folder.*" in first
14
- def self.images(path)
15
- path = path.dup.force_encoding("binary")
16
- arr = Dir.glob( File.join(path, "*.{#{IMAGE_EXTENSIONS.join(",")}}"), File::FNM_CASEFOLD).collect do |f|
17
- File.expand_path(f)
18
- end
19
- # move "folder.*" image on top of the array
20
- if folder = arr.detect { |f| f =~ /folder\.[^.]+$/ }
21
- arr.delete(folder)
22
- arr.unshift(folder)
15
+ # return the list of images in the album directory, with "folder.*" in first
16
+ def self.images(path)
17
+ path = path.dup.force_encoding('binary')
18
+ arr = Dir.glob(File.join(path, "*.{#{IMAGE_EXTENSIONS.join(',')}}"), File::FNM_CASEFOLD).collect do |f|
19
+ File.expand_path(f)
20
+ end
21
+ # move "folder.*" image on top of the array
22
+ if folder = arr.detect { |f| f =~ /folder\.[^.]+$/ }
23
+ arr.delete(folder)
24
+ arr.unshift(folder)
25
+ end
26
+ arr
23
27
  end
24
- arr
25
- end
26
28
 
27
- # strip the "multicd" string from the given +name+
28
- def self.basename(name)
29
- name.sub(MULTICD_REGEXP, '')
30
- end
29
+ # strip the "multicd" string from the given +name+
30
+ def self.basename(name)
31
+ name.sub(MULTICD_REGEXP, '')
32
+ end
31
33
 
32
- # return the number of the disc in the box or 0
33
- def self.discnum(name)
34
- if name =~ MULTICD_REGEXP
35
- $3.to_i
36
- else
37
- 0
34
+ # return the number of the disc in the box or 0
35
+ def self.discnum(name)
36
+ if name =~ MULTICD_REGEXP
37
+ Regexp.last_match(3).to_i
38
+ else
39
+ 0
40
+ end
38
41
  end
39
- end
40
42
 
41
- # open the Album with +path+. +fast_lookup+ will only check
42
- # first and last file of the directory
43
- def initialize(path, fast_lookup = false)
44
- @path = path
45
- @multicd = false
46
- @basename = @path
47
- exts = AudioInfo::SUPPORTED_EXTENSIONS.collect do |ext|
48
- ext.gsub(/[a-z]/) { |c| "[#{c.downcase}#{c.upcase}]" }
49
- end.join(",")
43
+ # open the Album with +path+. +fast_lookup+ will only check
44
+ # first and last file of the directory
45
+ def initialize(path, fast_lookup = false)
46
+ @path = path
47
+ @multicd = false
48
+ @basename = @path
49
+ exts = AudioInfo::SUPPORTED_EXTENSIONS.collect do |ext|
50
+ ext.gsub(/[a-z]/) { |c| "[#{c.downcase}#{c.upcase}]" }
51
+ end.join(',')
50
52
 
51
- # need to escape the glob path
52
- glob_escaped_path = @path.gsub(/([{}?*\[\]])/) { |s| '\\' << s }
53
+ # need to escape the glob path
54
+ glob_escaped_path = @path.gsub(/([{}?*\[\]])/) { |s| '\\' << s }
53
55
 
54
- glob_val = File.join(glob_escaped_path, "*.{#{exts}}")
55
- file_names = Dir.glob(glob_val).sort
56
+ glob_val = File.join(glob_escaped_path, "*.{#{exts}}")
57
+ file_names = Dir.glob(glob_val).sort
56
58
 
57
- if fast_lookup
58
- file_names = [file_names.first, file_names.last]
59
+ file_names = [file_names.first, file_names.last] if fast_lookup
60
+
61
+ @files = file_names.collect do |f|
62
+ AudioInfo.new(f)
63
+ end
64
+
65
+ @infos = {}
66
+ @infos['album'] = @files.collect(&:album).uniq
67
+ @infos['album'] = @infos['album'].first if @infos['album'].size == 1
68
+ artists = @files.collect(&:artist).uniq
69
+ @infos['artist'] = artists.size > 1 ? 'various' : artists.first
70
+ @discnum = self.class.discnum(@infos['album'])
71
+
72
+ unless @discnum.zero?
73
+ @multicd = true
74
+ @basename = self.class.basename(@infos['album'])
75
+ end
59
76
  end
60
77
 
61
- @files = file_names.collect do |f|
62
- AudioInfo.new(f)
78
+ # is the album empty?
79
+ def empty?
80
+ @files.empty?
63
81
  end
64
82
 
65
- @infos = {}
66
- @infos["album"] = @files.collect { |i| i.album }.uniq
67
- @infos["album"] = @infos["album"].first if @infos["album"].size == 1
68
- artists = @files.collect { |i| i.artist }.uniq
69
- @infos["artist"] = artists.size > 1 ? "various" : artists.first
70
- @discnum = self.class.discnum(@infos["album"])
83
+ # are all the files of the album MusicBrainz tagged ?
84
+ def mb_tagged?
85
+ return false if @files.empty?
71
86
 
72
- if not @discnum.zero?
73
- @multicd = true
74
- @basename = self.class.basename(@infos["album"])
87
+ mb = true
88
+ @files.each do |f|
89
+ mb &&= f.mb_tagged?
90
+ end
91
+ mb
75
92
  end
76
- end
77
93
 
78
- # is the album empty?
79
- def empty?
80
- @files.empty?
81
- end
94
+ # return an array of images with "folder.*" in first
95
+ def images
96
+ self.class.images(@path)
97
+ end
82
98
 
83
- # are all the files of the album MusicBrainz tagged ?
84
- def mb_tagged?
85
- return false if @files.empty?
86
- mb = true
87
- @files.each do |f|
88
- mb &&= f.mb_tagged?
99
+ # title of the album
100
+ def title
101
+ # count the occurences of the title and take the one who has most
102
+ hash_counted = files.collect(&:album).each_with_object(Hash.new(0)) { |album, hash| hash[album] += 1; }
103
+ if hash_counted.empty?
104
+ nil
105
+ else
106
+ hash_counted.max_by { |_k, v| v }[0]
107
+ end
89
108
  end
90
- mb
91
- end
92
109
 
93
- # return an array of images with "folder.*" in first
94
- def images
95
- self.class.images(@path)
96
- end
110
+ # mbid (MusicBrainz ID) of the album
111
+ def mbid
112
+ return nil unless mb_tagged?
97
113
 
98
- # title of the album
99
- def title
100
- # count the occurences of the title and take the one who has most
101
- hash_counted = self.files.collect { |f| f.album }.inject(Hash.new(0)) { |hash, album| hash[album] += 1; hash }
102
- if hash_counted.empty?
103
- nil
104
- else
105
- hash_counted.sort_by { |k, v| v }.last[0]
114
+ @files.collect { |f| f.musicbrainz_infos['albumid'] }.uniq.first
106
115
  end
107
- end
108
-
109
- # mbid (MusicBrainz ID) of the album
110
- def mbid
111
- return nil unless mb_tagged?
112
- @files.collect { |f| f.musicbrainz_infos["albumid"] }.uniq.first
113
- end
114
116
 
115
- # is the album multi-artist?
116
- def va?
117
- @files.collect { |f| f.artist }.uniq.size > 1
118
- end
117
+ # is the album multi-artist?
118
+ def va?
119
+ @files.collect(&:artist).uniq.size > 1
120
+ end
119
121
 
120
- # pretty print
121
- def to_s
122
- out = StringIO.new
123
- out.puts(@path)
124
- out.print "'#{title}'"
122
+ # pretty print
123
+ def to_s
124
+ out = StringIO.new
125
+ out.puts(@path)
126
+ out.print "'#{title}'"
125
127
 
126
- unless va?
127
- out.print " by '#{@files.first.artist}' "
128
- end
128
+ out.print " by '#{@files.first.artist}' " unless va?
129
129
 
130
- out.puts
130
+ out.puts
131
131
 
132
- @files.sort_by { |f| f.tracknum }.each do |f|
133
- out.printf("%02d %s %3d %s", f.tracknum, f.extension, f.bitrate, f.title)
134
- if va?
135
- out.print(" "+f.artist)
132
+ @files.sort_by(&:tracknum).each do |f|
133
+ out.printf('%02d %s %3d %s', f.tracknum, f.extension, f.bitrate, f.title)
134
+ out.print(" #{f.artist}") if va?
135
+ out.puts
136
136
  end
137
- out.puts
138
- end
139
137
 
140
- out.string
141
- end
138
+ out.string
139
+ end
142
140
 
143
- def inspect
144
- @infos.inspect
141
+ def inspect
142
+ @infos.inspect
143
+ end
145
144
  end
146
145
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class CaseInsensitiveHash < Hash
2
4
  def initialize(hash = {})
3
5
  super
@@ -5,7 +7,7 @@ class CaseInsensitiveHash < Hash
5
7
  self[key.downcase] = value
6
8
  end
7
9
  end
8
-
10
+
9
11
  def [](key)
10
12
  super(key.downcase)
11
13
  end
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "stringio"
4
- require "mp3info/id3v2"
4
+ require 'stringio'
5
+ require 'mp3info/id3v2'
5
6
 
6
7
  class MpcInfoError < StandardError; end
7
8
 
8
9
  class MpcInfo
9
-
10
- PROFILES_NAMES = [
10
+ PROFILES_NAMES = [
11
11
  'no profile',
12
12
  'Experimental',
13
13
  'unused',
@@ -24,17 +24,20 @@ class MpcInfo
24
24
  'BrainDead (q = 8.0)',
25
25
  'above BrainDead (q = 9.0)',
26
26
  'above BrainDead (q = 10.0)'
27
- ]
27
+ ].freeze
28
28
 
29
- FREQUENCIES = [ 44100, 48000, 37800, 32000 ]
29
+ FREQUENCIES = [44_100, 48_000, 37_800, 32_000].freeze
30
30
 
31
- SV4_6_HEADER = Regexp.new('^[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0]/', nil, 'n')
31
+ SV4_6_HEADER = Regexp.new(
32
+ '^[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0]/',
33
+ nil,
34
+ 'n'
35
+ )
32
36
 
33
- attr_reader :infos
34
- attr_reader :id3v2_tag
37
+ attr_reader :infos, :id3v2_tag
35
38
 
36
39
  def initialize(filename)
37
- @file = File.open(filename, "rb")
40
+ @file = File.open(filename, 'rb')
38
41
 
39
42
  @infos = {}
40
43
  @infos['raw'] = {}
@@ -46,21 +49,20 @@ class MpcInfo
46
49
  def parse_infos
47
50
  mpc_header = @file.read(3)
48
51
 
49
- if mpc_header == "MP+"
52
+ case mpc_header
53
+ when 'MP+'
50
54
  # this is SV7+
51
-
55
+
52
56
  header = StringIO.new(@file.read(25))
53
57
  header_size = 28
54
- #stream_version_byte = header.read(4).unpack("V").first
55
- stream_version_byte = header.read(1)[0].ord #.unpack("c").first
58
+ # stream_version_byte = header.read(4).unpack("V").first
59
+ stream_version_byte = header.read(1)[0].ord # .unpack("c").first
56
60
 
57
61
  @infos['stream_major_version'] = (stream_version_byte & 0x0F)
58
62
  @infos['stream_minor_version'] = (stream_version_byte & 0xF0) >> 4
59
63
 
60
64
  @infos['frame_count'] = read32(header)
61
- if @infos['stream_major_version'] != 7
62
- raise(MpcInfoError, "Only Musepack SV7 supported")
63
- end
65
+ raise(MpcInfoError, 'Only Musepack SV7 supported') if @infos['stream_major_version'] != 7
64
66
 
65
67
  flags_dword1 = read32(header)
66
68
 
@@ -83,103 +85,98 @@ class MpcInfo
83
85
  @infos['true_gapless'] = ((flags_dword2 & 0x80000000) >> 31) == 1
84
86
  @infos['last_frame_length'] = (flags_dword2 & 0x7FF00000) >> 20
85
87
 
86
-
87
- not_sure_what = read32(header, 3)
88
+ not_sure_what = read32(header, 3)
88
89
  @infos['raw']['encoder_version'] = read8(header)
89
-
90
- @infos['profile'] = PROFILES_NAMES[ @infos['raw']['profile'] ] || 'invalid'
91
- @infos['sample_rate'] = FREQUENCIES[ @infos['raw']['sample_rate'] ]
92
90
 
93
- if @infos['sample_rate'] == 0
94
- raise(MpcInfoError, 'Corrupt MPC file: frequency == zero')
95
- end
91
+ @infos['profile'] = PROFILES_NAMES[@infos['raw']['profile']] || 'invalid'
92
+ @infos['sample_rate'] = FREQUENCIES[@infos['raw']['sample_rate']]
96
93
 
97
- sample_rate = @infos['sample_rate'];
98
- channels = 2 #appears to be hardcoded
94
+ raise(MpcInfoError, 'Corrupt MPC file: frequency == zero') if (@infos['sample_rate']).zero?
95
+
96
+ sample_rate = @infos['sample_rate']
97
+ channels = 2 # appears to be hardcoded
99
98
  @infos['samples'] = (((@infos['frame_count'] - 1) * 1152) + @infos['last_frame_length']) * channels
100
99
  @infos['length'] = (((@infos['frame_count'] - 1) * 1152) + @infos['last_frame_length']) * channels
101
100
 
102
- @infos['length'] = (@infos['samples'] / channels) / @infos['sample_rate'].to_f
103
- if @infos['length'] == 0
104
- raise(MpcInfoError, 'Corrupt MPC file: playtime_seconds == zero')
105
- end
106
-
101
+ @infos['length'] = (@infos['samples'] / channels) / @infos['sample_rate'].to_f
102
+ raise(MpcInfoError, 'Corrupt MPC file: playtime_seconds == zero') if (@infos['length']).zero?
103
+
107
104
  # add size of file header to avdataoffset - calc bitrate correctly + MD5 data
108
105
  avdataoffset = header_size
109
106
 
110
- # FIXME is $ThisFileInfo['avdataend'] == File.size ????
107
+ # FIXME: is $ThisFileInfo['avdataend'] == File.size ????
111
108
  @infos['bitrate'] = ((@file.stat.size - avdataoffset) * 8) / @infos['length']
112
109
 
113
110
  @infos['title_peak'] = @infos['raw']['title_peak']
114
111
  @infos['title_peak_db'] = @infos['title_peak'].zero? ? 0 : peak_db(@infos['title_peak'])
115
- if @infos['raw']['title_gain'] < 0
116
- @infos['title_gain_db'] = (32768 + @infos['raw']['title_gain']) / -100.0
117
- else
118
- @infos['title_gain_db'] = @infos['raw']['title_gain'] / 100.0
119
- end
112
+ @infos['title_gain_db'] = if (@infos['raw']['title_gain']).negative?
113
+ (32_768 + @infos['raw']['title_gain']) / -100.0
114
+ else
115
+ @infos['raw']['title_gain'] / 100.0
116
+ end
120
117
 
121
- @infos['album_peak'] = @infos['raw']['album_peak'];
118
+ @infos['album_peak'] = @infos['raw']['album_peak']
122
119
  @infos['album_peak_db'] = @infos['album_peak'].zero? ? 0 : peak_db(@infos['album_peak'])
123
120
 
124
- if @infos['raw']['album_gain'] < 0
125
- @infos['album_gain_db'] = (32768 + @infos['raw']['album_gain']) / -100.0
126
- else
127
- @infos['album_gain_db'] = @infos['raw']['album_gain'] / 100.0
128
- end
129
- @infos['encoder_version'] = encoder_version(@infos['raw']['encoder_version'])
130
-
131
- =begin
132
- #FIXME
133
- $ThisFileInfo['replay_gain']['track']['adjustment'] = @infos['title_gain_db'];
134
- $ThisFileInfo['replay_gain']['album']['adjustment'] = @infos['album_gain_db'];
135
- if @infos['title_peak'] > 0
136
- #$ThisFileInfo['replay_gain']['track']['peak'] = @infos['title_peak']
137
- elsif round(@infos['max_level'] * 1.18) > 0)
138
- # ThisFileInfo['replay_gain']['track']['peak'] = getid3_lib::CastAsInt(round(@infos['max_level'] * 1.18)); // why? I don't know - see mppdec.c
139
- end
140
-
141
- if @infos['album_peak'] > 0
142
- #$ThisFileInfo['replay_gain']['album']['peak'] = @infos['album_peak'];
143
- end
144
-
145
- #ThisFileInfo['audio']['encoder'] = 'SV'.@infos['stream_major_version'].'.'.@infos['stream_minor_version'].', '.@infos['encoder_version'];
146
- #$ThisFileInfo['audio']['encoder'] = @infos['encoder_version'];
147
- #$ThisFileInfo['audio']['encoder_options'] = @infos['profile'];
148
- =end
149
- elsif mpc_header =~ SV4_6_HEADER
121
+ @infos['album_gain_db'] = if (@infos['raw']['album_gain']).negative?
122
+ (32_768 + @infos['raw']['album_gain']) / -100.0
123
+ else
124
+ @infos['raw']['album_gain'] / 100.0
125
+ end
126
+ @infos['encoder_version'] = encoder_version(@infos['raw']['encoder_version'])
127
+
128
+ # #FIXME
129
+ # $ThisFileInfo['replay_gain']['track']['adjustment'] = @infos['title_gain_db'];
130
+ # $ThisFileInfo['replay_gain']['album']['adjustment'] = @infos['album_gain_db'];
131
+ # if @infos['title_peak'] > 0
132
+ # #$ThisFileInfo['replay_gain']['track']['peak'] = @infos['title_peak']
133
+ # elsif round(@infos['max_level'] * 1.18) > 0)
134
+ # // why? I don't know - see mppdec.c
135
+ # # ThisFileInfo['replay_gain']['track']['peak'] = getid3_lib::CastAsInt(round(@infos['max_level'] * 1.18));
136
+ # end
137
+ #
138
+ # if @infos['album_peak'] > 0
139
+ # #$ThisFileInfo['replay_gain']['album']['peak'] = @infos['album_peak'];
140
+ # end
141
+ #
142
+ # #ThisFileInfo['audio']['encoder'] =
143
+ # # 'SV'.@infos['stream_major_version'].'.'.@infos['stream_minor_version'].', '.@infos['encoder_version'];
144
+ # #$ThisFileInfo['audio']['encoder'] = @infos['encoder_version'];
145
+ # #$ThisFileInfo['audio']['encoder_options'] = @infos['profile'];
146
+ when SV4_6_HEADER
150
147
  # this is SV4 - SV6, handle seperately
151
148
  header_size = 8
152
- elsif mpc_header == "ID3"
149
+ when 'ID3'
153
150
  @id3v2_tag = ID3v2.new
154
151
  @id3v2_tag.from_io(@file)
155
152
  @file.seek(@id3v2_tag.io_position)
156
153
  # very dirty hack to allow parsing of mpc infos after id3v2 tag
157
- while @file.read(1) != "M"; end
158
- if @file.read(2) == "P+"
154
+ while @file.read(1) != 'M'; end
155
+ if @file.read(2) == 'P+'
159
156
  @file.seek(-3, IO::SEEK_CUR)
160
157
  # we need to reparse the tag, since we have the beggining of the mpc file
161
158
  parse_infos
162
159
  else
163
- raise(MpcInfoError, "cannot find MPC header after id3 tag")
160
+ raise(MpcInfoError, 'cannot find MPC header after id3 tag')
164
161
  end
165
162
  else
166
- raise(MpcInfoError, "cannot find MPC header")
163
+ raise(MpcInfoError, 'cannot find MPC header')
167
164
  end
168
165
  end
169
-
166
+
170
167
  def read8(io)
171
168
  io.read(1)[0].ord
172
169
  end
173
170
 
174
171
  def read16(io)
175
- io.read(2).unpack("v").first
172
+ io.read(2).unpack1('v')
176
173
  end
177
174
 
178
175
  def read32(io, size = 4)
179
- io.read(size).unpack("V").first
176
+ io.read(size).unpack1('V')
180
177
  end
181
178
 
182
- def peak_db(i)
179
+ def peak_db(i)
183
180
  ((Math.log10(i) / Math.log10(2)) - 15) * 6
184
181
  end
185
182
 
@@ -189,25 +186,24 @@ class MpcInfo
189
186
  # EncoderVersion % 2 == 0 Beta (1.06)
190
187
  # EncoderVersion % 2 == 1 Alpha (1.05a...z)
191
188
 
192
- if encoderversion == 0
189
+ if encoderversion.zero?
193
190
  # very old version, not known exactly which
194
- 'Buschmann v1.7.0-v1.7.9 or Klemm v0.90-v1.05';
195
- elsif encoderversion % 10 == 0
191
+ 'Buschmann v1.7.0-v1.7.9 or Klemm v0.90-v1.05'
192
+ elsif (encoderversion % 10).zero?
196
193
  # release version
197
- sprintf("%.2f", encoderversion/100.0)
198
- elsif encoderversion % 2 == 0
199
- sprintf("%.2f beta", encoderversion / 100.0)
194
+ format('%.2f', encoderversion / 100.0)
195
+ elsif encoderversion.even?
196
+ format('%.2f beta', encoderversion / 100.0)
200
197
  else
201
- sprintf("%.2f alpha", encoderversion/100.0)
198
+ format('%.2f alpha', encoderversion / 100.0)
202
199
  end
203
200
  end
204
-
205
201
  end
206
202
 
207
- if __FILE__ == $0
208
- require "pp"
203
+ if __FILE__ == $PROGRAM_NAME
204
+ require 'pp'
209
205
 
210
206
  mpcinfo = MpcInfo.new(ARGV[0])
211
207
  pp mpcinfo.infos.sort
212
-
208
+
213
209
  end