ruby-audioinfo 0.5.2 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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