ruby-mp3info 0.6.5 → 0.6.6
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 +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
|