ruby-audioinfo 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,3 @@
1
+ === 0.1 / 2008-03-28
2
+
3
+ * first release
data/Manifest.txt ADDED
@@ -0,0 +1,9 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/audioinfo.rb
6
+ lib/audioinfo/album.rb
7
+ lib/audioinfo/apetag.rb
8
+ lib/audioinfo/mpcinfo.rb
9
+ test/mpcinfo.rb
data/README.txt ADDED
@@ -0,0 +1,44 @@
1
+ = ruby-audioinfo
2
+
3
+ by Guillaume Pierronnet
4
+ * http://ruby-audioinfo.rubyforge.org
5
+ * http://rubyforge.org/projects/ruby-audioinfo/
6
+
7
+ == DESCRIPTION:
8
+
9
+ ruby-audioinfo glue together various audio ruby libraries and presents a unified
10
+ API to the developper. Currently, supported formats are: mp3, ogg, mpc, ape,
11
+ wma, flac, aac, mp4, m4a.
12
+
13
+ == FEATURES/PROBLEMS:
14
+
15
+ * beta write support for mp3 and ogg tags (other to be written)
16
+ * unified support for tag text-encoding. AudioInfo.new("file", "utf-8") and you're done!
17
+ * support for MusicBrainz tags
18
+ * AudioInfo::Album class included, which gives an unified way to manage an album in a given directory.
19
+
20
+ == SYNOPSIS:
21
+
22
+ AudioInfo.open("audio_file.one_of_supported_extensions") do |info|
23
+ info.artist # or info["artist"]
24
+ info.title # or info["title"]
25
+ info.length # playing time of the file
26
+ info.bitrate # average bitrate
27
+ info.to_h # { "artist" => "artist", "title" => "title", etc... }
28
+ end
29
+
30
+ == REQUIREMENTS:
31
+
32
+ * ruby-mp3info
33
+ * ruby-ogginfo
34
+ * MP4Info
35
+ * flacinfo-rb
36
+ * wmainfo-rb
37
+
38
+ == INSTALL:
39
+
40
+ * sudo gem install ruby-audioinfo
41
+
42
+ == LICENSE:
43
+
44
+ Ruby
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+ require 'lib/audioinfo.rb'
4
+ #require "pp"
5
+
6
+ Hoe.new('ruby-audioinfo', AudioInfo::VERSION) do |p|
7
+ p.rubyforge_name = 'ruby-audioinfo'
8
+ p.author = 'Guillaume Pierronnet'
9
+ p.email = 'moumar@rubyforge.org'
10
+ p.extra_deps << [ "ruby-mp3info", ">= 0.6.3"]
11
+ p.extra_deps << [ "ruby-ogginfo", ">= 0.3.1" ]
12
+ p.extra_deps << [ "MP4Info", ">= 0.3.1" ]
13
+ p.extra_deps << [ "wmainfo-rb", ">= 0.5" ]
14
+ p.extra_deps << "flacinfo-rb"
15
+ p.description = p.paragraphs_of('README.txt', 3).first
16
+ p.summary = "ruby-audioinfo glue together various audio ruby libraries and presents a single API to the developper. Currently, supported formats are: mp3, ogg, mpc, ape, wma, flac, aac, mp4, m4a."
17
+ p.url = "http://ruby-audioinfo.rubyforge.org"
18
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
19
+ p.remote_rdoc_dir = ''
20
+ #pp p
21
+ end
data/lib/audioinfo.rb ADDED
@@ -0,0 +1,305 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "iconv"
4
+ require "stringio"
5
+
6
+ require "shell_escape"
7
+ $: << File.dirname(__FILE__)+"/audioinfo"
8
+
9
+ require "mp3info"
10
+ require "ogginfo"
11
+ require "mpcinfo"
12
+ require "apetag"
13
+ require "wmainfo"
14
+ require "mp4info"
15
+ require "flacinfo"
16
+
17
+ class AudioInfoError < Exception ; end
18
+
19
+ class AudioInfo
20
+ MUSICBRAINZ_FIELDS = {
21
+ "trmid" => "TRM Id",
22
+ "artistid" => "Artist Id",
23
+ "albumid" => "Album Id",
24
+ "albumtype" => "Album Type",
25
+ "albumstatus" => "Album Status",
26
+ "albumartistid" => "Album Artist Id",
27
+ "sortname" => "Sort Name",
28
+ "trackid" => "Track Id"
29
+ }
30
+
31
+ SUPPORTED_EXTENSIONS = %w{mp3 ogg mpc wma mp4 aac m4a flac}
32
+
33
+ VERSION = "0.1"
34
+
35
+ attr_reader :path, :extension, :musicbrainz_infos, :tracknum, :bitrate, :vbr
36
+ attr_reader :artist, :album, :title, :length, :date
37
+
38
+ # "block version" of #new()
39
+ def self.open(*args)
40
+ audio_info = self.new(*args)
41
+ ret = nil
42
+ if block_given?
43
+ begin
44
+ ret = yield(audio_info)
45
+ ensure
46
+ audio_info.close
47
+ end
48
+ else
49
+ ret = audio_info
50
+ end
51
+ ret
52
+ end
53
+
54
+ # open the file with path +fn+ and convert all tags from/to specified +encoding+
55
+ def initialize(fn, encoding = 'utf-8')
56
+ raise(AudioInfoError, "path is nil") if fn.nil?
57
+ @path = fn
58
+ ext = File.extname(@path)
59
+ raise(AudioInfoError, "cannot find extension") if ext.empty?
60
+ @extension = ext[1..-1].downcase
61
+ @musicbrainz_infos = {}
62
+ @encoding = encoding
63
+
64
+ begin
65
+ case @extension
66
+ when 'mp3'
67
+ @info = Mp3Info.new(fn, :encoding => @encoding)
68
+ default_tag_fill
69
+ #"TXXX"=>
70
+ #["MusicBrainz TRM Id\000",
71
+ #"MusicBrainz Artist Id\000aba64937-3334-4c65-90a1-4e6b9d4d7ada",
72
+ #"MusicBrainz Album Id\000e1a223c1-cbc2-427f-a192-5d22fefd7c4c",
73
+ #"MusicBrainz Album Type\000album",
74
+ #"MusicBrainz Album Status\000official",
75
+ #"MusicBrainz Album Artist Id\000"]
76
+ if (arr = @info.tag2["TXXX"]).is_a?(Array)
77
+ fields = MUSICBRAINZ_FIELDS.invert
78
+ arr.each do |val|
79
+ if val =~ /^MusicBrainz (.+)\000(.*)$/
80
+ short_name = fields[$1]
81
+ @musicbrainz_infos[short_name] = $2
82
+ end
83
+ end
84
+ end
85
+ @bitrate = @info.bitrate
86
+ i = @info.tag.tracknum
87
+ @tracknum = (i.is_a?(Array) ? i.last : i).to_i
88
+ @length = @info.length.to_i
89
+ @date = @info.tag["date"]
90
+ @vbr = @info.vbr
91
+ @info.close
92
+
93
+ when 'ogg'
94
+ @info = OggInfo.new(fn, @encoding)
95
+ default_fill_musicbrainz_fields
96
+ default_tag_fill
97
+ @bitrate = @info.bitrate/1000
98
+ @tracknum = @info.tag.tracknumber.to_i
99
+ @length = @info.length.to_i
100
+ @date = @info.tag["date"]
101
+ @vbr = true
102
+ @info.close
103
+
104
+ when 'mpc'
105
+ fill_ape_tag(fn)
106
+
107
+ mpc_info = MpcInfo.new(fn)
108
+ @bitrate = mpc_info.infos['bitrate']/1000
109
+ @length = mpc_info.infos['length']
110
+
111
+ when 'ape'
112
+ fill_ape_tag(fn)
113
+
114
+ when 'wma'
115
+ @info = WmaInfo.new(fn, :encoding => @encoding)
116
+ @artist = @info.tags["Author"]
117
+ @album = @info.tags["AlbumTitle"]
118
+ @title = @info.tags["Title"]
119
+ @tracknum = @info.tags["TrackNumber"].to_i
120
+ @date = @info.tags["Year"]
121
+ @bitrate = @info.info["bitrate"]
122
+ @length = @info.info["playtime_seconds"]
123
+ MUSICBRAINZ_FIELDS.each do |key, original_key|
124
+ #next if key == "trmid" # no trmid with WMA since picard cannot analyze it
125
+ @musicbrainz_infos[key] = @info.info["MusicBrainz/" + original_key.tr(" ", "")]
126
+ end
127
+
128
+ when 'aac', 'mp4', 'm4a'
129
+ @info = MP4Info.open(fn)
130
+ @artist = @info.ART
131
+ @album = @info.ALB
132
+ @title = @info.NAM
133
+ @tracknum = ( t = @info.TRKN ) ? t.first : 0
134
+ @date = @info.DAY
135
+ @bitrate = @info.BITRATE
136
+ @length = @info.SECS
137
+ mapping = MUSICBRAINZ_FIELDS.invert
138
+
139
+ `faad -i #{fn.shell_escape} 2>&1 `.grep(/^MusicBrainz (.+)$/) do
140
+ name, value = $1.split(/: /, 2)
141
+ key = mapping[name]
142
+ @musicbrainz_infos[key] = value
143
+ end
144
+
145
+ when 'flac'
146
+ @info = FlacInfo.new(fn)
147
+ tags = convert_tags_encoding(@info.tags, "UTF-8")
148
+ @artist = tags["ARTIST"]
149
+ @album = tags["ALBUM"]
150
+ @title = tags["TITLE"]
151
+ @tracknum = tags["TRACKNUMBER"].to_i
152
+ @date = tags["DATE"]
153
+ @length = @info.streaminfo["total_samples"] / @info.streaminfo["samplerate"].to_f
154
+ @bitrate = File.size(fn).to_f*8/@length/1024
155
+ #default_fill_musicbrainz_fields
156
+
157
+ else
158
+ raise(AudioInfoError, "unsupported extension '.#{@extension}'")
159
+ end
160
+
161
+ if @tracknum == 0
162
+ @tracknum = nil
163
+ end
164
+
165
+ @musicbrainz_infos.delete_if { |k, v| v.nil? }
166
+ @hash = { "artist" => @artist,
167
+ "album" => @album,
168
+ "title" => @title,
169
+ "tracknum" => @tracknum,
170
+ "date" => @date,
171
+ "length" => @length,
172
+ "bitrate" => @bitrate,
173
+ }
174
+
175
+ rescue Exception, Mp3InfoError, OggInfoError, ApeTagError => e
176
+ raise AudioInfoError, e.to_s, e.backtrace
177
+ end
178
+
179
+ @needs_commit = false
180
+
181
+ end
182
+
183
+ # set the title of the file
184
+ def title=(v)
185
+ @needs_commit = true
186
+ @title = v
187
+ end
188
+
189
+ # set the artist of the file
190
+ def artist=(v)
191
+ @needs_commit = true
192
+ @artist = v
193
+ end
194
+
195
+ # set the album of the file
196
+ def album=(v)
197
+ @needs_commit = true
198
+ @album = v
199
+ end
200
+
201
+ # hash-like access to tag
202
+ def [](key)
203
+ @hash[key]
204
+ end
205
+
206
+ # convert tags to hash
207
+ def to_h
208
+ @hash
209
+ end
210
+
211
+ # close the file and commits changes to disk
212
+ def close
213
+ if @needs_commit
214
+ case @info
215
+ when Mp3Info
216
+ Mp3Info.open(@path) do |info|
217
+ info.tag.artist = @artist
218
+ info.tag.title = @title
219
+ info.tag.album = @album
220
+ end
221
+ when OggInfo
222
+ OggInfo.open(@path) do |ogg|
223
+ { "artist" => @artist,
224
+ "album" => @album,
225
+ "title" => @title }.each do |k,v|
226
+ ogg.tag[k] = v
227
+ end
228
+ end
229
+
230
+ else
231
+ raise(AudioInfoError, "implement me")
232
+ end
233
+
234
+ end
235
+ @needs_commit
236
+ end
237
+ =begin
238
+ {"musicbrainz_albumstatus"=>"official",
239
+ "artist"=>"Jill Scott",
240
+ "replaygain_track_gain"=>"-3.29 dB",
241
+ "tracknumber"=>"1",
242
+ "title"=>"A long walk (A touch of Jazz Mix)..Jazzanova Love Beats...",
243
+ "musicbrainz_sortname"=>"Scott, Jill",
244
+ "musicbrainz_artistid"=>"b1fb6a18-1626-4011-80fb-eaf83dfebcb6",
245
+ "musicbrainz_albumid"=>"cb2ad8c7-4a02-4e46-ae9a-c7c2463c7235",
246
+ "replaygain_track_peak"=>"0.82040048",
247
+ "musicbrainz_albumtype"=>"compilation",
248
+ "album"=>"...Mixing (Jazzanova)",
249
+ "musicbrainz_trmid"=>"1ecec0a6-c7c3-4179-abea-ef12dabc7cbd",
250
+ "musicbrainz_trackid"=>"0a368e63-dddf-441f-849c-ca23f9cb2d49",
251
+ "musicbrainz_albumartistid"=>"89ad4ac3-39f7-470e-963a-56509c546377"}>
252
+ =end
253
+
254
+ # check if the file is correctly tagged by MusicBrainz
255
+ def mb_tagged?
256
+ ! @musicbrainz_infos.empty?
257
+ end
258
+
259
+ private
260
+
261
+ def sanitize(input)
262
+ s = input.is_a?(Array) ? input.first : input
263
+ s.gsub("\000", "")
264
+ end
265
+
266
+ def default_fill_musicbrainz_fields
267
+ MUSICBRAINZ_FIELDS.keys.each do |field|
268
+ val = @info.tag["musicbrainz_#{field}"]
269
+ @musicbrainz_infos[field] = val if val
270
+ end
271
+ end
272
+
273
+ def default_tag_fill(tag = @info.tag)
274
+ %w{artist album title}.each do |v|
275
+ instance_variable_set( "@#{v}".to_sym, sanitize(tag[v].to_s) )
276
+ end
277
+ end
278
+
279
+ def fill_ape_tag(fn)
280
+ begin
281
+ @info = ApeTag.new(fn)
282
+ tags = convert_tags_encoding(@info.tag, "UTF-8")
283
+ default_tag_fill(tags)
284
+ default_fill_musicbrainz_fields
285
+ @date = @info.tag["year"]
286
+ @tracknum = 0
287
+
288
+ if track = @info.tag['track']
289
+ @tracknum = @info.tag['track'].split("/").first.to_i
290
+ end
291
+ rescue ApeTagError
292
+ end
293
+ end
294
+
295
+ def convert_tags_encoding(tags_orig, from_encoding)
296
+ tags = {}
297
+ Iconv.open(@encoding, from_encoding) do |ic|
298
+ tags_orig.inject(tags) do |hash, (k, v)|
299
+ hash[ic.iconv(k)] = ic.iconv(v)
300
+ hash
301
+ end
302
+ end
303
+ tags
304
+ end
305
+ end
@@ -0,0 +1,151 @@
1
+ require "audioinfo"
2
+
3
+ class AudioInfo::Album
4
+
5
+ IMAGE_EXTENSIONS = %w{jpg jpeg gif png}
6
+
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
10
+
11
+ attr_reader :files, :files_on_error, :discnum, :multicd, :basename, :infos, :path
12
+
13
+ # return the list of images in the album directory, with "folder.*" in first
14
+ def self.images(path)
15
+ arr = Dir.glob( File.join(path, "*.{#{IMAGE_EXTENSIONS.join(",")}}"), File::FNM_CASEFOLD).collect do |f|
16
+ File.expand_path(f)
17
+ end
18
+ # move "folder.*" image on top of the array
19
+ if folder = arr.detect { |f| f =~ /folder\.[^.]+$/ }
20
+ arr.delete(folder)
21
+ arr.unshift(folder)
22
+ end
23
+ arr
24
+ end
25
+
26
+ # strip the "multicd" string from the given +name+
27
+ def self.basename(name)
28
+ name.sub(MULTICD_REGEXP, '')
29
+ end
30
+
31
+ # return the number of the disc in the box or 0
32
+ def self.discnum(name)
33
+ if name =~ MULTICD_REGEXP
34
+ $3.to_i
35
+ else
36
+ 0
37
+ end
38
+ end
39
+
40
+ # open the Album with +path+. +fast_lookup+ will only check
41
+ # first and last file of the directory
42
+ def initialize(path, fast_lookup = false)
43
+ @path = path
44
+ @multicd = false
45
+ @basename = @path
46
+ exts = AudioInfo::SUPPORTED_EXTENSIONS.join(",")
47
+
48
+ # need to escape the glob path
49
+ glob_escaped_path = @path.gsub(/([{}?*\[\]])/) { |s| '\\' << s }
50
+
51
+ file_names = Dir.glob( File.join(glob_escaped_path, "*.{#{exts}}") , File::FNM_CASEFOLD).sort
52
+
53
+ if fast_lookup
54
+ file_names = [file_names.first, file_names.last]
55
+ end
56
+
57
+ @files_on_error = []
58
+
59
+ @files = file_names.collect do |f|
60
+ begin
61
+ AudioInfo.new(f)
62
+ rescue AudioInfoError
63
+ @files_on_error << f
64
+ nil
65
+ end
66
+ end.compact
67
+
68
+ if @files_on_error.empty?
69
+ @files_on_error = nil
70
+ end
71
+
72
+ @infos = {}
73
+ @infos["album"] = @files.collect { |i| i.album }.uniq
74
+ @infos["album"] = @infos["album"].first if @infos["album"].size == 1
75
+ artists = @files.collect { |i| i.artist }.uniq
76
+ @infos["artist"] = artists.size > 1 ? "various" : artists.first
77
+ @discnum = self.class.discnum(@infos["album"])
78
+
79
+ if not @discnum.zero?
80
+ @multicd = true
81
+ @basename = self.class.basename(@infos["album"])
82
+ end
83
+ end
84
+
85
+ # is the album empty?
86
+ def empty?
87
+ @files.empty?
88
+ end
89
+
90
+ # are all the files of the album MusicBrainz tagged ?
91
+ def mb_tagged?
92
+ return false if @files.empty?
93
+ mb = true
94
+ @files.each do |f|
95
+ mb &&= f.mb_tagged?
96
+ end
97
+ mb
98
+ end
99
+
100
+ # return an array of images with "folder.*" in first
101
+ def images
102
+ self.class.images(@path)
103
+ end
104
+
105
+ # title of the album
106
+ def title
107
+ albums = @files.collect { |f| f.album }.uniq
108
+ #if albums.size > 1
109
+ # "#{albums.first} others candidates: '" + albums[1..-1].join("', '") + "'"
110
+ #else
111
+ albums.first
112
+ #end
113
+ end
114
+
115
+ # mbid (MusicBrainz ID) of the album
116
+ def mbid
117
+ return nil unless mb_tagged?
118
+ @files.collect { |f| f.musicbrainz_infos["albumid"] }.uniq.first
119
+ end
120
+
121
+ # is the album multi-artist?
122
+ def va?
123
+ @files.collect { |f| f.artist }.uniq.size > 1
124
+ end
125
+
126
+ # pretty print
127
+ def to_s
128
+ out = StringIO.new
129
+ out.puts(@path)
130
+ out.print "'#{title}'"
131
+
132
+ unless va?
133
+ out.print " by '#{@files.first.artist}' "
134
+ end
135
+
136
+ out.puts
137
+
138
+ @files.sort_by { |f| f.tracknum }.each do |f|
139
+ out.printf("%02d %s %3d %s", f.tracknum, f.extension, f.bitrate, f.title)
140
+ if va?
141
+ out.print(" "+f.artist)
142
+ end
143
+ out.puts
144
+ end
145
+
146
+ out.string
147
+ end
148
+
149
+ end
150
+
151
+
@@ -0,0 +1,53 @@
1
+ # see http://www.personal.uni-jena.de/~pfk/mpp/sv8/apetag.html for specs
2
+
3
+ class ApeTagError < StandardError ; end
4
+
5
+ class ApeTag
6
+ attr_reader :tag, :version
7
+
8
+ def initialize(filename)
9
+ @tag = {}
10
+
11
+ begin
12
+ @file = File.new(filename, "rb")
13
+ @file.seek(-32, IO::SEEK_END)
14
+
15
+ preamble, version, tagsize, itemcount, flags =
16
+ @file.read(24).unpack("A8VVVV")
17
+ @version = version/1000
18
+
19
+ raise(ApeTagError, "cannot find preamble") if preamble != 'APETAGEX'
20
+ @file.seek(-tagsize, IO::SEEK_END)
21
+ itemcount.times do |i|
22
+ len, flags = @file.read(8).unpack("VV")
23
+ key = ""
24
+ loop do
25
+ c = @file.getc
26
+ break if c == 0
27
+ key << c
28
+ end
29
+ #ugly FIX
30
+ @tag[key.downcase] = @file.read(len) unless len > 100_000
31
+ end
32
+ ensure
33
+ @file.close
34
+ end
35
+ end
36
+ end
37
+
38
+ if $0 == __FILE__
39
+ while filename = ARGV.shift
40
+ puts "Getting info from #{filename}"
41
+ begin
42
+ ape = ApeTag.new(filename)
43
+ rescue ApeTagError
44
+ puts "error: doesn't appear to be an ape tagged file"
45
+ else
46
+ puts ape
47
+ ape.tag.each do |key, value|
48
+ puts "#{key} => #{value}"
49
+ end
50
+ end
51
+ puts
52
+ end
53
+ end
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "stringio"
4
+ require "mp3info/id3v2"
5
+
6
+ class MpcInfoError < StandardError; end
7
+
8
+ class MpcInfo
9
+
10
+ PROFILES_NAMES = [
11
+ 'no profile',
12
+ 'Experimental',
13
+ 'unused',
14
+ 'unused',
15
+ 'unused',
16
+ 'below Telephone (q = 0.0)',
17
+ 'below Telephone (q = 1.0)',
18
+ 'Telephone (q = 2.0)',
19
+ 'Thumb (q = 3.0)',
20
+ 'Radio (q = 4.0)',
21
+ 'Standard (q = 5.0)',
22
+ 'Extreme (q = 6.0)',
23
+ 'Insane (q = 7.0)',
24
+ 'BrainDead (q = 8.0)',
25
+ 'above BrainDead (q = 9.0)',
26
+ 'above BrainDead (q = 10.0)'
27
+ ]
28
+
29
+ FREQUENCIES = [ 44100, 48000, 37800, 32000 ]
30
+
31
+ attr_reader :infos
32
+ attr_reader :id3v2_tag
33
+
34
+ def initialize(filename)
35
+ @file = File.open(filename, "rb")
36
+
37
+ @infos = {}
38
+ @infos['raw'] = {}
39
+ parse_infos
40
+ end
41
+
42
+ private
43
+
44
+ def parse_infos
45
+ mpc_header = @file.read(3)
46
+
47
+ if mpc_header == "MP+"
48
+ # this is SV7+
49
+
50
+ header = StringIO.new(@file.read(25))
51
+ header_size = 28
52
+ #stream_version_byte = header.read(4).unpack("V").first
53
+ stream_version_byte = header.read(1)[0] #.unpack("c").first
54
+
55
+ @infos['stream_major_version'] = (stream_version_byte & 0x0F)
56
+ @infos['stream_minor_version'] = (stream_version_byte & 0xF0) >> 4
57
+
58
+ @infos['frame_count'] = read32(header)
59
+ if @infos['stream_major_version'] != 7
60
+ raise(MpcInfoError, "Only Musepack SV7 supported")
61
+ end
62
+
63
+ flags_dword1 = read32(header)
64
+
65
+ @infos['intensity_stereo'] = ((flags_dword1 & 0x80000000) >> 31) == 1
66
+ @infos['mid_side_stereo'] = ((flags_dword1 & 0x40000000) >> 30) == 1
67
+ @infos['max_subband'] = (flags_dword1 & 0x3F000000) >> 24
68
+ @infos['raw']['profile'] = (flags_dword1 & 0x00F00000) >> 20
69
+ @infos['begin_loud'] = ((flags_dword1 & 0x00080000) >> 19) == 1
70
+ @infos['end_loud'] = ((flags_dword1 & 0x00040000) >> 18) == 1
71
+ @infos['raw']['sample_rate'] = (flags_dword1 & 0x00030000) >> 16
72
+ @infos['max_level'] = (flags_dword1 & 0x0000FFFF)
73
+
74
+ @infos['raw']['title_peak'] = read16(header)
75
+ @infos['raw']['title_gain'] = read16(header)
76
+
77
+ @infos['raw']['album_peak'] = read16(header)
78
+ @infos['raw']['album_gain'] = read16(header)
79
+
80
+ flags_dword2 = read32(header)
81
+ @infos['true_gapless'] = ((flags_dword2 & 0x80000000) >> 31) == 1
82
+ @infos['last_frame_length'] = (flags_dword2 & 0x7FF00000) >> 20
83
+
84
+
85
+ not_sure_what = read32(header, 3)
86
+ @infos['raw']['encoder_version'] = read8(header)
87
+
88
+ @infos['profile'] = PROFILES_NAMES[ @infos['raw']['profile'] ] || 'invalid'
89
+ @infos['sample_rate'] = FREQUENCIES[ @infos['raw']['sample_rate'] ]
90
+
91
+ if @infos['sample_rate'] == 0
92
+ raise(MpcInfoError, 'Corrupt MPC file: frequency == zero')
93
+ end
94
+
95
+ sample_rate = @infos['sample_rate'];
96
+ channels = 2 #appears to be hardcoded
97
+ @infos['samples'] = (((@infos['frame_count'] - 1) * 1152) + @infos['last_frame_length']) * channels
98
+ @infos['length'] = (((@infos['frame_count'] - 1) * 1152) + @infos['last_frame_length']) * channels
99
+
100
+ @infos['length'] = (@infos['samples'] / channels) / @infos['sample_rate'].to_f
101
+ if @infos['length'] == 0
102
+ raise(MpcInfoError, 'Corrupt MPC file: playtime_seconds == zero')
103
+ end
104
+
105
+ # add size of file header to avdataoffset - calc bitrate correctly + MD5 data
106
+ avdataoffset = header_size
107
+
108
+ # FIXME is $ThisFileInfo['avdataend'] == File.size ????
109
+ @infos['bitrate'] = ((@file.stat.size - avdataoffset) * 8) / @infos['length']
110
+
111
+ @infos['title_peak'] = @infos['raw']['title_peak']
112
+ @infos['title_peak_db'] = @infos['title_peak'].zero? ? 0 : peak_db(@infos['title_peak'])
113
+ if @infos['raw']['title_gain'] < 0
114
+ @infos['title_gain_db'] = (32768 + @infos['raw']['title_gain']) / -100.0
115
+ else
116
+ @infos['title_gain_db'] = @infos['raw']['title_gain'] / 100.0
117
+ end
118
+
119
+ @infos['album_peak'] = @infos['raw']['album_peak'];
120
+ @infos['album_peak_db'] = @infos['album_peak'].zero? ? 0 : peak_db(@infos['album_peak'])
121
+
122
+ if @infos['raw']['album_gain'] < 0
123
+ @infos['album_gain_db'] = (32768 + @infos['raw']['album_gain']) / -100.0
124
+ else
125
+ @infos['album_gain_db'] = @infos['raw']['album_gain'] / 100.0
126
+ end
127
+ @infos['encoder_version'] = encoder_version(@infos['raw']['encoder_version'])
128
+
129
+ =begin
130
+ #FIXME
131
+ $ThisFileInfo['replay_gain']['track']['adjustment'] = @infos['title_gain_db'];
132
+ $ThisFileInfo['replay_gain']['album']['adjustment'] = @infos['album_gain_db'];
133
+ if @infos['title_peak'] > 0
134
+ #$ThisFileInfo['replay_gain']['track']['peak'] = @infos['title_peak']
135
+ elsif round(@infos['max_level'] * 1.18) > 0)
136
+ # ThisFileInfo['replay_gain']['track']['peak'] = getid3_lib::CastAsInt(round(@infos['max_level'] * 1.18)); // why? I don't know - see mppdec.c
137
+ end
138
+
139
+ if @infos['album_peak'] > 0
140
+ #$ThisFileInfo['replay_gain']['album']['peak'] = @infos['album_peak'];
141
+ end
142
+
143
+ #ThisFileInfo['audio']['encoder'] = '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
+ =end
147
+ elsif mpc_header =~ /^[\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]/s
148
+ # this is SV4 - SV6, handle seperately
149
+ header_size = 8
150
+ elsif mpc_header == "ID3"
151
+ @id3v2_tag = ID3v2.new
152
+ @id3v2_tag.from_io(@file)
153
+ parse_infos
154
+ else
155
+ raise(MpcInfoError, "cannot find MPC header")
156
+ end
157
+ end
158
+
159
+ def read8(io)
160
+ io.read(1)[0]
161
+ end
162
+
163
+ def read16(io)
164
+ io.read(2).unpack("v").first
165
+ end
166
+
167
+ def read32(io, size = 4)
168
+ io.read(size).unpack("V").first
169
+ end
170
+
171
+ def peak_db(i)
172
+ ((Math.log10(i) / Math.log10(2)) - 15) * 6
173
+ end
174
+
175
+ def encoder_version(encoderversion)
176
+ # Encoder version * 100 (106 = 1.06)
177
+ # EncoderVersion % 10 == 0 Release (1.0)
178
+ # EncoderVersion % 2 == 0 Beta (1.06)
179
+ # EncoderVersion % 2 == 1 Alpha (1.05a...z)
180
+
181
+ if encoderversion == 0
182
+ # very old version, not known exactly which
183
+ 'Buschmann v1.7.0-v1.7.9 or Klemm v0.90-v1.05';
184
+ elsif encoderversion % 10 == 0
185
+ # release version
186
+ sprintf("%.2f", encoderversion/100.0)
187
+ elsif encoderversion % 2 == 0
188
+ sprintf("%.2f beta", encoderversion / 100.0)
189
+ else
190
+ sprintf("%.2f alpha", encoderversion/100.0)
191
+ end
192
+ end
193
+
194
+ end
195
+
196
+ if __FILE__ == $0
197
+ require "pp"
198
+
199
+ mpcinfo = MpcInfo.new(ARGV[0])
200
+ pp mpcinfo.infos.sort
201
+
202
+ end
data/test/mpcinfo.rb ADDED
@@ -0,0 +1,8 @@
1
+ require File.dirname(__FILE__)+"/../lib/audioinfo/mpcinfo"
2
+
3
+ require "pp"
4
+
5
+ fn = "file.mpc"
6
+ mpc = MpcInfo.new(fn)
7
+ pp mpc.id3v2_tag
8
+ pp mpc.infos
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-audioinfo
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - Guillaume Pierronnet
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-03-28 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: ruby-mp3info
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.6.3
23
+ version:
24
+ - !ruby/object:Gem::Dependency
25
+ name: ruby-ogginfo
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 0.3.1
32
+ version:
33
+ - !ruby/object:Gem::Dependency
34
+ name: MP4Info
35
+ version_requirement:
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.3.1
41
+ version:
42
+ - !ruby/object:Gem::Dependency
43
+ name: wmainfo-rb
44
+ version_requirement:
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0.5"
50
+ version:
51
+ - !ruby/object:Gem::Dependency
52
+ name: flacinfo-rb
53
+ version_requirement:
54
+ version_requirements: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ - !ruby/object:Gem::Dependency
61
+ name: hoe
62
+ version_requirement:
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 1.5.1
68
+ version:
69
+ description: "ruby-audioinfo glue together various audio ruby libraries and presents a unified API to the developper. Currently, supported formats are: mp3, ogg, mpc, ape, wma, flac, aac, mp4, m4a."
70
+ email: moumar@rubyforge.org
71
+ executables: []
72
+
73
+ extensions: []
74
+
75
+ extra_rdoc_files:
76
+ - History.txt
77
+ - Manifest.txt
78
+ - README.txt
79
+ files:
80
+ - History.txt
81
+ - Manifest.txt
82
+ - README.txt
83
+ - Rakefile
84
+ - lib/audioinfo.rb
85
+ - lib/audioinfo/album.rb
86
+ - lib/audioinfo/apetag.rb
87
+ - lib/audioinfo/mpcinfo.rb
88
+ - test/mpcinfo.rb
89
+ has_rdoc: true
90
+ homepage: http://ruby-audioinfo.rubyforge.org
91
+ post_install_message:
92
+ rdoc_options:
93
+ - --main
94
+ - README.txt
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: "0"
102
+ version:
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: "0"
108
+ version:
109
+ requirements: []
110
+
111
+ rubyforge_project: ruby-audioinfo
112
+ rubygems_version: 1.0.1
113
+ signing_key:
114
+ specification_version: 2
115
+ summary: "ruby-audioinfo glue together various audio ruby libraries and presents a single API to the developper. Currently, supported formats are: mp3, ogg, mpc, ape, wma, flac, aac, mp4, m4a."
116
+ test_files: []
117
+