ruby-audioinfo 0.1

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.
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
+