ruby-mp3info 0.6.5 → 0.6.6
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +7 -0
- data/lib/mp3info.rb +51 -55
- data/lib/mp3info/extension_modules.rb +20 -4
- data/lib/mp3info/id3v2.rb +37 -17
- data/test/test_ruby-mp3info.rb +96 -80
- metadata +3 -3
data/History.txt
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
=== 0.6.6 / 2008-05-27
|
2
|
+
|
3
|
+
* avoid reading tag that are too big (> 50Mb)
|
4
|
+
* ruby 1.9 support (thanks to Dave Thomas)
|
5
|
+
* FIXED: bug #20311 'Multiple APIC frames may be stored incorrectly'
|
6
|
+
* FIXED: bug #20312 'doesn't use v2.2 frames for extracting meta data'
|
7
|
+
|
1
8
|
=== 0.6.5 / 2008-04-19
|
2
9
|
|
3
10
|
* added Mp3Info#audio_content method, to return "audio-only" boundaries from mp3, i.e. data without tags
|
data/lib/mp3info.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
#
|
1
|
+
# coding:utf-8
|
2
|
+
# $Id: mp3info.rb 88 2008-05-26 22:22:31Z moumar $
|
2
3
|
# License:: Ruby
|
3
4
|
# Author:: Guillaume Pierronnet (mailto:moumar_AT__rubyforge_DOT_org)
|
4
5
|
# Website:: http://ruby-mp3info.rubyforge.org/
|
@@ -17,7 +18,7 @@ end
|
|
17
18
|
|
18
19
|
class Mp3Info
|
19
20
|
|
20
|
-
VERSION = "0.6.
|
21
|
+
VERSION = "0.6.6"
|
21
22
|
|
22
23
|
LAYER = [ nil, 3, 2, 1]
|
23
24
|
BITRATE = [
|
@@ -67,7 +68,21 @@ class Mp3Info
|
|
67
68
|
|
68
69
|
TAG1_SIZE = 128
|
69
70
|
#MAX_FRAME_COUNT = 6 #number of frame to read for encoder detection
|
70
|
-
|
71
|
+
|
72
|
+
# map to fill the "universal" tag (#tag attribute)
|
73
|
+
# for id3v2.2
|
74
|
+
TAG_MAPPING_2_2 = {
|
75
|
+
"title" => "TT2",
|
76
|
+
"artist" => "TP2",
|
77
|
+
"album" => "TAL",
|
78
|
+
"year" => "TYE",
|
79
|
+
"tracknum" => "TRK",
|
80
|
+
"comments" => "COM",
|
81
|
+
"genre_s" => "TCO"
|
82
|
+
}
|
83
|
+
|
84
|
+
# for id3v2.3 and 2.4
|
85
|
+
TAG_MAPPING_2_3 = {
|
71
86
|
"title" => "TIT2",
|
72
87
|
"artist" => "TPE1",
|
73
88
|
"album" => "TALB",
|
@@ -144,7 +159,6 @@ class Mp3Info
|
|
144
159
|
}
|
145
160
|
end
|
146
161
|
|
147
|
-
|
148
162
|
# Remove id3v1 tag from +filename+
|
149
163
|
def self.removetag1(filename)
|
150
164
|
if self.hastag1?(filename)
|
@@ -163,7 +177,7 @@ class Mp3Info
|
|
163
177
|
# Instantiate Mp3Info object with name +filename+ and an
|
164
178
|
# options hash for ID3v2#new underlying object
|
165
179
|
def initialize(filename, id3v2_options = {})
|
166
|
-
|
180
|
+
warn("#{self.class}::new() does not take block; use #{self.class}::open() instead") if block_given?
|
167
181
|
@filename = filename
|
168
182
|
@id3v2_options = id3v2_options
|
169
183
|
reload
|
@@ -193,16 +207,21 @@ class Mp3Info
|
|
193
207
|
|
194
208
|
if hastag2?
|
195
209
|
@tag = {}
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
@tag[
|
210
|
+
# creation of a sort of "universal" tag, regardless of the tag version
|
211
|
+
tag2_mapping = @tag2.version =~ /^2\.2/ ? TAG_MAPPING_2_2 : TAG_MAPPING_2_3
|
212
|
+
tag2_mapping.each do |key, tag2_name|
|
213
|
+
tag_value = (@tag2[tag2_name].is_a?(Array) ? @tag2[tag2_name].first : @tag2[tag2_name])
|
214
|
+
next unless tag_value
|
215
|
+
@tag[key] = tag_value.is_a?(Array) ? tag_value.first : tag_value
|
216
|
+
|
217
|
+
if %w{year tracknum}.include?(key)
|
218
|
+
@tag[key] = tag_value.to_i
|
205
219
|
end
|
220
|
+
# this is a special case with id3v2.2, which uses
|
221
|
+
# old fashionned id3v1 genres
|
222
|
+
if tag2_name == "TCO" && tag_value =~ /^\((\d+)\)$/
|
223
|
+
@tag["genre_s"] = GENRES[$1.to_i]
|
224
|
+
end
|
206
225
|
end
|
207
226
|
end
|
208
227
|
|
@@ -270,7 +289,7 @@ class Mp3Info
|
|
270
289
|
percent_diff = ((@length.to_i-tlen)/tlen.to_f)
|
271
290
|
if percent_diff.abs > 0.05
|
272
291
|
# without the xing header, this is the best guess without reading
|
273
|
-
#
|
292
|
+
# every single frame
|
274
293
|
@vbr = true
|
275
294
|
@length = @tag2["TLEN"].to_i/1000
|
276
295
|
@bitrate = (@streamsize / @bitrate) >> 10
|
@@ -359,9 +378,11 @@ class Mp3Info
|
|
359
378
|
@tag1[k] = v
|
360
379
|
end
|
361
380
|
end
|
362
|
-
|
363
|
-
|
364
|
-
|
381
|
+
|
382
|
+
# ruby-mp3info can only write v2.3 tags
|
383
|
+
TAG_MAPPING_2_3.each do |key, tag2_name|
|
384
|
+
@tag2.delete(TAG_MAPPING_2_2[key])
|
385
|
+
@tag2[tag2_name] = @tag[key] if @tag[key]
|
365
386
|
end
|
366
387
|
end
|
367
388
|
|
@@ -402,7 +423,6 @@ class Mp3Info
|
|
402
423
|
raise(Mp3InfoError, "file is not writable") unless File.writable?(@filename)
|
403
424
|
tempfile_name = nil
|
404
425
|
File.open(@filename, 'rb+') do |file|
|
405
|
-
|
406
426
|
#if tag2 already exists, seek to end of it
|
407
427
|
if @tag2.valid?
|
408
428
|
file.seek(@tag2.io_position)
|
@@ -474,47 +494,23 @@ private
|
|
474
494
|
@file.seek(pos)
|
475
495
|
end
|
476
496
|
end
|
477
|
-
|
478
|
-
### reads in id3 field strings, stripping out non-printable chars
|
479
|
-
### len (fixnum) = number of chars in field
|
480
|
-
### returns string
|
481
|
-
def read_id3_string(len)
|
482
|
-
#FIXME handle unicode strings
|
483
|
-
#return @file.read(len)
|
484
|
-
s = ""
|
485
|
-
len.times do
|
486
|
-
c = @file.getc
|
487
|
-
# only append printable characters
|
488
|
-
s << c if c >= 32 and c < 254
|
489
|
-
end
|
490
|
-
return s.strip
|
491
|
-
#return (s[0..2] == "eng" ? s[3..-1] : s)
|
492
|
-
end
|
493
497
|
|
494
498
|
### gets id3v1 tag information from @file
|
495
499
|
### assumes @file is pointing to char after "TAG" id
|
496
500
|
def gettag1
|
497
501
|
@tag1_parsed = true
|
498
|
-
=
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
@file.seek(pos)
|
503
|
-
# FIXME remove that
|
504
|
-
=end
|
505
|
-
@tag1["title"] = read_id3_string(30)
|
506
|
-
@tag1["artist"] = read_id3_string(30)
|
507
|
-
@tag1["album"] = read_id3_string(30)
|
508
|
-
year_t = read_id3_string(4).to_i
|
502
|
+
@tag1["title"] = @file.read(30).sub(/\0*$/, '')
|
503
|
+
@tag1["artist"] = @file.read(30).sub(/\0*$/, '')
|
504
|
+
@tag1["album"] = @file.read(30).sub(/\0*$/, '')
|
505
|
+
year_t = @file.read(4).to_i
|
509
506
|
@tag1["year"] = year_t unless year_t == 0
|
510
507
|
comments = @file.read(30)
|
511
|
-
if comments
|
512
|
-
@tag1["tracknum"] = comments
|
508
|
+
if comments.getbyte(-2) == 0
|
509
|
+
@tag1["tracknum"] = comments.getbyte(-1).to_i
|
513
510
|
comments.chop! #remove the last char
|
514
511
|
end
|
515
|
-
|
516
|
-
@tag1["
|
517
|
-
@tag1["genre"] = @file.getc
|
512
|
+
@tag1["comments"] = comments.sub(/\0*$/, '')
|
513
|
+
@tag1["genre"] = @file.getbyte
|
518
514
|
@tag1["genre_s"] = GENRES[@tag1["genre"]] || ""
|
519
515
|
|
520
516
|
# clear empty tags
|
@@ -534,10 +530,10 @@ private
|
|
534
530
|
#dummyproof = @file.stat.size - @file.pos => WAS TOO MUCH
|
535
531
|
dummyproof = [ @file.stat.size - @file.pos, 2000000 ].min
|
536
532
|
dummyproof.times do |i|
|
537
|
-
if @file.
|
533
|
+
if @file.getbyte == 0xff
|
538
534
|
data = @file.read(3)
|
539
535
|
raise(Mp3InfoError, "end of file reached") if @file.eof?
|
540
|
-
head = 0xff000000 + (data
|
536
|
+
head = 0xff000000 + (data.getbyte(0) << 16) + (data.getbyte(1) << 8) + data.getbyte(2)
|
541
537
|
if check_head(head)
|
542
538
|
return head
|
543
539
|
else
|
@@ -563,9 +559,9 @@ private
|
|
563
559
|
|
564
560
|
### returns the selected bit range (b, a) as a number
|
565
561
|
### NOTE: b > a if not, returns 0
|
566
|
-
def bits(
|
562
|
+
def bits(number, b, a)
|
567
563
|
t = 0
|
568
|
-
b.downto(a) { |i| t += t +
|
564
|
+
b.downto(a) { |i| t += t + number[i] }
|
569
565
|
t
|
570
566
|
end
|
571
567
|
end
|
@@ -11,15 +11,31 @@ class Mp3Info
|
|
11
11
|
self[m]
|
12
12
|
end
|
13
13
|
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class ::String
|
17
|
+
if RUBY_VERSION < "1.9.0"
|
18
|
+
alias getbyte []
|
19
|
+
else
|
20
|
+
def getbyte(i)
|
21
|
+
self[i].ord
|
22
|
+
end
|
23
|
+
end
|
14
24
|
end
|
15
25
|
|
16
|
-
module Mp3FileMethods #:nodoc:
|
26
|
+
module Mp3FileMethods #:nodoc:
|
27
|
+
if RUBY_VERSION < "1.9.0"
|
28
|
+
def getbyte
|
29
|
+
getc
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
17
33
|
def get32bits
|
18
|
-
(
|
34
|
+
(getbyte << 24) + (getbyte << 16) + (getbyte << 8) + getbyte
|
19
35
|
end
|
20
36
|
|
21
37
|
def get_syncsafe
|
22
|
-
(
|
23
|
-
end
|
38
|
+
(getbyte << 21) + (getbyte << 14) + (getbyte << 7) + getbyte
|
39
|
+
end
|
24
40
|
end
|
25
41
|
end
|
data/lib/mp3info/id3v2.rb
CHANGED
@@ -126,7 +126,7 @@ class ID3v2 < DelegateClass(Hash)
|
|
126
126
|
@version_maj = @version_min = nil
|
127
127
|
end
|
128
128
|
|
129
|
-
# does this tag correctly read ?
|
129
|
+
# does this tag has been correctly read ?
|
130
130
|
def valid?
|
131
131
|
@valid
|
132
132
|
end
|
@@ -136,9 +136,14 @@ class ID3v2 < DelegateClass(Hash)
|
|
136
136
|
@hash_orig != @hash
|
137
137
|
end
|
138
138
|
|
139
|
-
# full version of this tag (like 2.3.0)
|
139
|
+
# full version of this tag (like "2.3.0") or nil
|
140
|
+
# if tag was not correctly read
|
140
141
|
def version
|
141
|
-
|
142
|
+
if valid?
|
143
|
+
"2.#{@version_maj}.#{@version_min}"
|
144
|
+
else
|
145
|
+
nil
|
146
|
+
end
|
142
147
|
end
|
143
148
|
|
144
149
|
### gets id3v2 tag information from io object (must support #seek() method)
|
@@ -164,7 +169,7 @@ class ID3v2 < DelegateClass(Hash)
|
|
164
169
|
@io.seek(original_pos + @tag_length, IO::SEEK_SET)
|
165
170
|
|
166
171
|
# skip padding zeros at the end of the tag
|
167
|
-
while @io.
|
172
|
+
while @io.getbyte == 0; end
|
168
173
|
|
169
174
|
@io.seek(-1, IO::SEEK_CUR)
|
170
175
|
@io_position = @io.pos
|
@@ -185,12 +190,18 @@ class ID3v2 < DelegateClass(Hash)
|
|
185
190
|
@hash.each do |k, v|
|
186
191
|
next unless v
|
187
192
|
next if v.respond_to?("empty?") and v.empty?
|
193
|
+
# doesn't encode id3v2.2 tags, which have 3 characters
|
194
|
+
next if k.size != 4
|
188
195
|
data = encode_tag(k, v.to_s, WRITE_VERSION)
|
189
196
|
#data << "\x00"*2 #End of tag
|
190
197
|
|
191
198
|
tag << k[0,4] #4 characte max for a tag's key
|
192
199
|
#tag << to_syncsafe(data.size) #+1 because of the language encoding byte
|
193
|
-
|
200
|
+
size = data.size
|
201
|
+
if RUBY_VERSION >= "1.9.0"
|
202
|
+
size = data.dup.force_encoding("binary").size
|
203
|
+
end
|
204
|
+
tag << [size].pack("N") #+1 because of the language encoding byte
|
194
205
|
tag << "\x00"*2 #flags
|
195
206
|
tag << data
|
196
207
|
end
|
@@ -200,7 +211,7 @@ class ID3v2 < DelegateClass(Hash)
|
|
200
211
|
tag_str << [ WRITE_VERSION, 0, "0000" ].pack("CCB4")
|
201
212
|
tag_str << [to_syncsafe(tag.size)].pack("N")
|
202
213
|
tag_str << tag
|
203
|
-
|
214
|
+
puts "tag in binary format: #{tag_str.inspect}" if $DEBUG
|
204
215
|
tag_str
|
205
216
|
end
|
206
217
|
|
@@ -210,11 +221,11 @@ class ID3v2 < DelegateClass(Hash)
|
|
210
221
|
puts "encode_tag(#{name.inspect}, #{value.inspect}, #{version})" if $DEBUG
|
211
222
|
|
212
223
|
text_encoding_index = @text_encoding_index
|
213
|
-
if (name
|
224
|
+
if (name.index("T") == 0 || name == "COMM" ) && version == 3
|
214
225
|
# in id3v2.3 tags, there is only 2 encodings possible
|
215
226
|
transcoded_value = value
|
216
227
|
if text_encoding_index >= 2
|
217
|
-
transcoded_value = Iconv.iconv(TEXT_ENCODINGS[1], TEXT_ENCODINGS[text_encoding_index], value)
|
228
|
+
transcoded_value = Iconv.iconv(TEXT_ENCODINGS[1], TEXT_ENCODINGS[text_encoding_index], value).first
|
218
229
|
text_encoding_index = 1
|
219
230
|
end
|
220
231
|
end
|
@@ -235,16 +246,16 @@ class ID3v2 < DelegateClass(Hash)
|
|
235
246
|
case name
|
236
247
|
when "COMM"
|
237
248
|
#FIXME improve this
|
238
|
-
encoding, lang, str = raw_value.unpack("ca3a*")
|
249
|
+
encoding, lang, str = raw_value.unpack("ca3a*")
|
239
250
|
out = raw_value.split(0.chr).last
|
240
251
|
when /^T/
|
241
|
-
encoding = raw_value
|
252
|
+
encoding = raw_value.getbyte(0) # language encoding (see TEXT_ENCODINGS constant)
|
242
253
|
out = raw_value[1..-1]
|
243
254
|
# we need to convert the string in order to match
|
244
255
|
# the requested encoding
|
245
256
|
if out && encoding != @text_encoding_index
|
246
257
|
begin
|
247
|
-
Iconv.iconv(@options[:encoding], TEXT_ENCODINGS[encoding], out)
|
258
|
+
Iconv.iconv(@options[:encoding], TEXT_ENCODINGS[encoding], out).first
|
248
259
|
rescue Iconv::Failure
|
249
260
|
out
|
250
261
|
end
|
@@ -261,11 +272,11 @@ class ID3v2 < DelegateClass(Hash)
|
|
261
272
|
def read_id3v2_3_frames
|
262
273
|
loop do # there are 2 ways to end the loop
|
263
274
|
name = @io.read(4)
|
264
|
-
if name
|
275
|
+
if name.getbyte(0) == 0 or name == "MP3e" #bug caused by old tagging application "mp3ext" ( http://www.mutschler.de/mp3ext/ )
|
265
276
|
@io.seek(-4, IO::SEEK_CUR) # 1. find a padding zero,
|
266
277
|
seek_to_v2_end
|
267
278
|
break
|
268
|
-
else
|
279
|
+
else
|
269
280
|
if @version_maj == 4
|
270
281
|
size = @io.get_syncsafe
|
271
282
|
else
|
@@ -284,12 +295,12 @@ class ID3v2 < DelegateClass(Hash)
|
|
284
295
|
def read_id3v2_2_frames
|
285
296
|
loop do
|
286
297
|
name = @io.read(3)
|
287
|
-
if name
|
298
|
+
if name.getbyte(0) == 0
|
288
299
|
@io.seek(-3, IO::SEEK_CUR)
|
289
300
|
seek_to_v2_end
|
290
301
|
break
|
291
302
|
else
|
292
|
-
size = (@io.
|
303
|
+
size = (@io.getbyte << 16) + (@io.getbyte << 8) + @io.getbyte
|
293
304
|
add_value_to_tag2(name, size)
|
294
305
|
break if @io.pos >= @tag_length
|
295
306
|
end
|
@@ -301,8 +312,17 @@ class ID3v2 < DelegateClass(Hash)
|
|
301
312
|
### create an array if the key already exists in the tag
|
302
313
|
def add_value_to_tag2(name, size)
|
303
314
|
puts "add_value_to_tag2" if $DEBUG
|
315
|
+
|
316
|
+
if size > 50_000_000
|
317
|
+
raise ID3v2Error, "tag size too big for tag '#{name}'"
|
318
|
+
end
|
319
|
+
|
304
320
|
data_io = @io.read(size)
|
305
321
|
data = decode_tag(name, data_io)
|
322
|
+
# remove padding zeros for textual tags
|
323
|
+
if name =~ /^T/
|
324
|
+
data.sub!(/\0*$/, '')
|
325
|
+
end
|
306
326
|
|
307
327
|
if self["TPOS"] =~ /(\d+)\s*\/\s*(\d+)/
|
308
328
|
self["disc_number"] = $1.to_i
|
@@ -311,7 +331,7 @@ class ID3v2 < DelegateClass(Hash)
|
|
311
331
|
|
312
332
|
if self.keys.include?(name)
|
313
333
|
unless self[name].is_a?(Array)
|
314
|
-
self[name] = self[name]
|
334
|
+
self[name] = [ self[name] ]
|
315
335
|
end
|
316
336
|
self[name] << data
|
317
337
|
else
|
@@ -323,7 +343,7 @@ class ID3v2 < DelegateClass(Hash)
|
|
323
343
|
### runs thru @file one char at a time looking for best guess of first MPEG
|
324
344
|
### frame, which should be first 0xff byte after id3v2 padding zero's
|
325
345
|
def seek_to_v2_end
|
326
|
-
until @io.
|
346
|
+
until @io.getbyte == 0xff
|
327
347
|
raise EOFError if @io.eof?
|
328
348
|
end
|
329
349
|
@io.seek(-1, IO::SEEK_CUR)
|
data/test/test_ruby-mp3info.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
|
-
#!/usr/bin/
|
1
|
+
#!/usr/bin/env ruby1.9
|
2
|
+
# coding:utf-8
|
2
3
|
|
3
4
|
$:.unshift("lib/")
|
4
5
|
|
5
6
|
require "test/unit"
|
6
|
-
require "base64"
|
7
7
|
require "mp3info"
|
8
8
|
require "fileutils"
|
9
9
|
require "tempfile"
|
10
|
+
require "zlib"
|
11
|
+
require "yaml"
|
10
12
|
|
11
13
|
class Mp3InfoTest < Test::Unit::TestCase
|
12
14
|
|
13
|
-
TEMP_FILE = File.join(File.dirname(
|
15
|
+
TEMP_FILE = File.join(File.dirname(__FILE__), "test_mp3info.mp3")
|
14
16
|
|
15
17
|
DUMMY_TAG2 = {
|
16
18
|
"COMM" => "comments",
|
@@ -32,58 +34,20 @@ class Mp3InfoTest < Test::Unit::TestCase
|
|
32
34
|
"genre" => 233
|
33
35
|
}
|
34
36
|
|
37
|
+
FIXTURES = YAML::load_file( File.join(File.dirname(__FILE__), "fixtures.yml") )
|
38
|
+
|
35
39
|
def setup
|
36
|
-
# Command to create a dummy MP3
|
37
|
-
# dd if=/dev/zero bs=1024 count=15 |
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
48
|
-
VVVVVVVVVVVVVVVV//uSZL6P8AAAaQAAAAAAAA0gAAAAAAABpAAAAAAAADSA
|
49
|
-
AAAAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
50
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
51
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
52
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
53
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
54
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjkzVVVVVVVV
|
55
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
56
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
57
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kmT/j/AAAGkAAAAAAAANIAAA
|
58
|
-
AAAAAaQAAAAAAAA0gAAAAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
59
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
60
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
61
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
62
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
63
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVM
|
64
|
-
QU1FMy45M1VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
65
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
66
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5Jk/4/w
|
67
|
-
AABpAAAAAAAADSAAAAAAAAGkAAAAAAAANIAAAABVVVVVVVVVVVVVVVVVVVVV
|
68
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
69
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
70
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
71
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
72
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
73
|
-
VVVVVVVVVVVVVVVVTEFNRTMuOTNVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
74
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
75
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
76
|
-
VVVVVVVV//uSZP+P8AAAaQAAAAAAAA0gAAAAAAABpAAAAAAAADSAAAAAVVVV
|
77
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
78
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
79
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
80
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
81
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
82
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
83
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
84
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
85
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVQ==
|
86
|
-
EOF
|
40
|
+
# Command to create a gzip'ed dummy MP3
|
41
|
+
# $ dd if=/dev/zero bs=1024 count=15 | \
|
42
|
+
# lame --quiet --preset cbr 128 -r -s 44.1 --bitwidth 16 - - | \
|
43
|
+
# ruby -rbase64 -rzlib -ryaml -e 'print(Zlib::Deflate.deflate($stdin.read)'
|
44
|
+
# vbr:
|
45
|
+
# $ dd if=/dev/zero of=#{tempfile.path} bs=1024 count=30000 |
|
46
|
+
# system("lame -h -v -b 112 -r -s 44.1 --bitwidth 16 - /tmp/vbr.mp3
|
47
|
+
@valid_mp3, @valid_mp3_2_2, @vbr_mp3 = %w{empty_mp3 2_2_tagged vbr}.collect do |fixture_key|
|
48
|
+
Zlib::Inflate.inflate(FIXTURES[fixture_key])
|
49
|
+
end
|
50
|
+
|
87
51
|
@tag = {
|
88
52
|
"title" => "title",
|
89
53
|
"artist" => "artist",
|
@@ -135,18 +99,9 @@ EOF
|
|
135
99
|
end
|
136
100
|
|
137
101
|
def test_vbr_mp3_length
|
138
|
-
|
139
|
-
|
140
|
-
unless File.exists?(temp_file_vbr)
|
141
|
-
tempfile = Tempfile.new("ruby-mp3info_test")
|
142
|
-
tempfile.close
|
143
|
-
system("dd if=/dev/zero of=#{tempfile.path} bs=1024 count=30000")
|
144
|
-
raise "cannot find lame binary in path" unless system("which lame")
|
145
|
-
system("lame -h -v -b 112 -r -s 44.1 --bitwidth 16 #{tempfile.path} #{temp_file_vbr}")
|
146
|
-
tempfile.close!
|
147
|
-
end
|
102
|
+
File.open(TEMP_FILE, "w") { |f| f.write(@vbr_mp3) }
|
148
103
|
|
149
|
-
Mp3Info.open(
|
104
|
+
Mp3Info.open(TEMP_FILE) do |info|
|
150
105
|
assert(info.vbr)
|
151
106
|
assert_equal(1152, info.samples_per_frame)
|
152
107
|
assert_in_delta(174.210612, info.length, 0.000001)
|
@@ -162,7 +117,7 @@ EOF
|
|
162
117
|
|
163
118
|
def test_writetag1
|
164
119
|
Mp3Info.open(TEMP_FILE) { |info| info.tag1 = @tag }
|
165
|
-
Mp3Info.open(TEMP_FILE) { |info|
|
120
|
+
Mp3Info.open(TEMP_FILE) { |info| assert_equal(info.tag1, @tag) }
|
166
121
|
end
|
167
122
|
|
168
123
|
def test_valid_tag1_1
|
@@ -266,7 +221,7 @@ EOF
|
|
266
221
|
return if `which id3v2`.empty?
|
267
222
|
start = false
|
268
223
|
id3v2_output = {}
|
269
|
-
`id3v2 -l #{TEMP_FILE}`.each do |line|
|
224
|
+
`id3v2 -l #{TEMP_FILE}`.split(/\n/).each do |line|
|
270
225
|
if line =~ /^id3v2 tag info/
|
271
226
|
start = true
|
272
227
|
next
|
@@ -293,7 +248,9 @@ EOF
|
|
293
248
|
["PRIV", "APIC"].each do |k|
|
294
249
|
tag[k] = random_string(50)
|
295
250
|
end
|
296
|
-
|
251
|
+
|
252
|
+
got_tag = write_temp_file(tag)
|
253
|
+
assert_equal(tag, got_tag)
|
297
254
|
end
|
298
255
|
|
299
256
|
def test_id3v2_bigtag
|
@@ -318,6 +275,56 @@ EOF
|
|
318
275
|
end
|
319
276
|
end
|
320
277
|
|
278
|
+
def test_reading2_2_tags
|
279
|
+
File.open(TEMP_FILE, "w") { |f| f.write(@valid_mp3_2_2) }
|
280
|
+
|
281
|
+
Mp3Info.open(TEMP_FILE) do |mp3|
|
282
|
+
assert_equal "2.2.0", mp3.tag2.version
|
283
|
+
expected_tag = {
|
284
|
+
"TCO" => "Hip Hop/Rap",
|
285
|
+
"TP1" => "Grems Aka Supermicro",
|
286
|
+
"TT2" => "Intro",
|
287
|
+
"TAL" => "Air Max",
|
288
|
+
"TEN" => "iTunes v7.0.2.16",
|
289
|
+
"TYE" => "2006",
|
290
|
+
"TRK" => "1/17",
|
291
|
+
"TPA" => "1/1" }
|
292
|
+
tag = mp3.tag2.dup
|
293
|
+
assert_equal 4, tag["COM"].size
|
294
|
+
tag.delete("COM")
|
295
|
+
assert_equal expected_tag, tag
|
296
|
+
|
297
|
+
expected_tag = {
|
298
|
+
"genre_s" => "Hip Hop/Rap",
|
299
|
+
"title" => "Intro",
|
300
|
+
"comments" => "\000engiTunPGAP\0000\000\000",
|
301
|
+
"year" => 2006,
|
302
|
+
"album" => "Air Max",
|
303
|
+
"tracknum" => 1 }
|
304
|
+
# test universal tag
|
305
|
+
assert_equal expected_tag, mp3.tag
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def test_writing_universal_tag_from_2_2_tags
|
310
|
+
File.open(TEMP_FILE, "w") { |f| f.write(@valid_mp3_2_2) }
|
311
|
+
Mp3Info.open(TEMP_FILE) do |mp3|
|
312
|
+
mp3.tag.artist = "toto"
|
313
|
+
mp3.tag.comments = "comments"
|
314
|
+
mp3.flush
|
315
|
+
expected_tag = {
|
316
|
+
"artist"=>"toto",
|
317
|
+
"genre_s"=>"Hip Hop/Rap",
|
318
|
+
"title"=>"Intro",
|
319
|
+
"comments"=>"comments",
|
320
|
+
"year"=>2006,
|
321
|
+
"album"=>"Air Max",
|
322
|
+
"tracknum"=>1}
|
323
|
+
|
324
|
+
assert_equal expected_tag, mp3.tag
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
321
328
|
def test_remove_tag
|
322
329
|
Mp3Info.open(TEMP_FILE) do |mp3|
|
323
330
|
tag = mp3.tag
|
@@ -334,14 +341,17 @@ EOF
|
|
334
341
|
|
335
342
|
def test_good_parsing_of_a_pathname
|
336
343
|
fn = "Freak On `(Stone´s Club Mix).mp3"
|
337
|
-
|
344
|
+
FileUtils.cp(TEMP_FILE, fn)
|
338
345
|
begin
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
346
|
+
Mp3Info.open(fn) do |mp3|
|
347
|
+
mp3.tag.title = fn
|
348
|
+
mp3.flush
|
349
|
+
if RUBY_VERSION < "1.9.0"
|
350
|
+
assert_equal fn, mp3.tag.title
|
351
|
+
else
|
352
|
+
assert_equal fn, mp3.tag.title.force_encoding("utf-8")
|
353
|
+
end
|
354
|
+
end
|
345
355
|
ensure
|
346
356
|
File.delete(fn)
|
347
357
|
end
|
@@ -363,11 +373,15 @@ EOF
|
|
363
373
|
end
|
364
374
|
|
365
375
|
Mp3Info.open(TEMP_FILE, :encoding => "utf-8") do |mp3|
|
366
|
-
assert_equal "
|
376
|
+
assert_equal "allé", mp3.tag2['TEST']
|
367
377
|
end
|
368
378
|
|
369
379
|
Mp3Info.open(TEMP_FILE, :encoding => "iso-8859-1") do |mp3|
|
370
|
-
|
380
|
+
if RUBY_VERSION < "1.9.0"
|
381
|
+
assert_equal "all\xe9", mp3.tag2['TEST']
|
382
|
+
else
|
383
|
+
assert_equal "all\xe9".force_encoding("binary"), mp3.tag2['TEST']
|
384
|
+
end
|
371
385
|
end
|
372
386
|
end
|
373
387
|
|
@@ -377,7 +391,11 @@ EOF
|
|
377
391
|
end
|
378
392
|
|
379
393
|
Mp3Info.open(TEMP_FILE, :encoding => "iso-8859-1") do |mp3|
|
380
|
-
|
394
|
+
if RUBY_VERSION < "1.9.0"
|
395
|
+
assert_equal "all\xe9", mp3.tag2['TEST']
|
396
|
+
else
|
397
|
+
assert_equal "all\xe9".force_encoding("iso-8859-1"), mp3.tag2['TEST']
|
398
|
+
end
|
381
399
|
end
|
382
400
|
end
|
383
401
|
|
@@ -422,8 +440,6 @@ EOF
|
|
422
440
|
end
|
423
441
|
Digest::MD5.new.update(data).hexdigest
|
424
442
|
end
|
425
|
-
def compute_md5(content)
|
426
|
-
end
|
427
443
|
|
428
444
|
def write_temp_file(tag)
|
429
445
|
Mp3Info.open(TEMP_FILE) do |mp3|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-mp3info
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Guillaume Pierronnet
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-
|
12
|
+
date: 2008-05-27 00:00:00 +02:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
requirements:
|
20
20
|
- - ">="
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: 1.5.
|
22
|
+
version: 1.5.3
|
23
23
|
version:
|
24
24
|
description: "* written in pure ruby * read low-level informations like bitrate, length, samplerate, etc... * read, write, remove id3v1 and id3v2 tags * only 2.3 version is supported for writings id3v2 tags == SYNOPSIS: a good exercise is to read the test.rb to understand how the library works deeper require \"mp3info\" # read and display infos & tags Mp3Info.open(\"myfile.mp3\") do |mp3info| puts mp3info end # read/write tag1 and tag2 with Mp3Info#tag attribute # when reading tag2 have priority over tag1 # when writing, each tag is written. Mp3Info.open(\"myfile.mp3\") do |mp3| puts mp3.tag.title puts mp3.tag.artist puts mp3.tag.album puts mp3.tag.tracknum mp3.tag.title = \"track title\" mp3.tag.artist = \"artist name\" end Mp3Info.open(\"myfile.mp3\") do |mp3| # you can access four letter v2 tags like this puts mp3.tag2.TIT2 mp3.tag2.TIT2 = \"new TIT2\" # or like that mp3.tag2[\"TIT2\"] # at this time, only COMM tag is processed after reading and before writing # according to ID3v2#options hash mp3.tag2.options[:lang] = \"FRE\" mp3.tag2.COMM = \"my comment in french, correctly handled when reading and writing\" end"
|
25
25
|
email: moumar@rubyforge.org
|