ruby-mp3info 0.6.12 → 0.6.13
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +8 -0
- data/lib/mp3info.rb +67 -55
- data/lib/mp3info/id3v2.rb +95 -30
- data/test/test_ruby-mp3info.rb +10 -8
- metadata +3 -3
data/History.txt
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
=== 0.6.13 / 2009-05-26
|
2
|
+
|
3
|
+
* fixed bad mapping of artist inside id3 2.2
|
4
|
+
* adding fusil fuzzer tests
|
5
|
+
* Improved support for id3v2.2 to id3v2.3 field mapping
|
6
|
+
* each_frame() iterator
|
7
|
+
* removed @bitrate & @length computation based on @tag2[TLEN]
|
8
|
+
|
1
9
|
=== 0.6.12 / 2009-02-23
|
2
10
|
|
3
11
|
* fixed bug when @tag2["TLEN"] == 0
|
data/lib/mp3info.rb
CHANGED
@@ -18,7 +18,7 @@ end
|
|
18
18
|
|
19
19
|
class Mp3Info
|
20
20
|
|
21
|
-
VERSION = "0.6.
|
21
|
+
VERSION = "0.6.13"
|
22
22
|
|
23
23
|
LAYER = [ nil, 3, 2, 1]
|
24
24
|
BITRATE = {
|
@@ -82,7 +82,7 @@ class Mp3Info
|
|
82
82
|
# for id3v2.2
|
83
83
|
TAG_MAPPING_2_2 = {
|
84
84
|
"title" => "TT2",
|
85
|
-
"artist" => "
|
85
|
+
"artist" => "TP1",
|
86
86
|
"album" => "TAL",
|
87
87
|
"year" => "TYE",
|
88
88
|
"tracknum" => "TRK",
|
@@ -259,7 +259,7 @@ class Mp3Info
|
|
259
259
|
5.times do
|
260
260
|
head = find_next_frame()
|
261
261
|
@first_frame_pos = @file.pos - 4
|
262
|
-
current_frame = get_frames_infos(head)
|
262
|
+
current_frame = Mp3Info.get_frames_infos(head)
|
263
263
|
@mpeg_version = current_frame[:mpeg_version]
|
264
264
|
@layer = current_frame[:layer]
|
265
265
|
@header[:error_protection] = head[16] == 0 ? true : false
|
@@ -267,11 +267,11 @@ class Mp3Info
|
|
267
267
|
@samplerate = current_frame[:samplerate]
|
268
268
|
@header[:padding] = current_frame[:padding]
|
269
269
|
@header[:private] = head[8] == 0 ? true : false
|
270
|
-
@channel_mode = CHANNEL_MODE[@channel_num = bits(head, 7,6)]
|
271
|
-
@header[:mode_extension] = bits(head, 5,4)
|
270
|
+
@channel_mode = CHANNEL_MODE[@channel_num = Mp3Info.bits(head, 7,6)]
|
271
|
+
@header[:mode_extension] = Mp3Info.bits(head, 5,4)
|
272
272
|
@header[:copyright] = (head[3] == 1 ? true : false)
|
273
273
|
@header[:original] = (head[2] == 1 ? true : false)
|
274
|
-
@header[:emphasis] = bits(head, 1,0)
|
274
|
+
@header[:emphasis] = Mp3Info.bits(head, 1,0)
|
275
275
|
@vbr = false
|
276
276
|
found = true
|
277
277
|
break
|
@@ -307,50 +307,21 @@ class Mp3Info
|
|
307
307
|
# for cbr, calculate duration with the given bitrate
|
308
308
|
stream_size = @file.stat.size - (hastag1? ? TAG1_SIZE : 0) - (@tag2.io_position || 0)
|
309
309
|
@length = ((stream_size << 3)/1000.0)/@bitrate
|
310
|
-
full_scan_occured = false
|
311
310
|
# read the first 100 frames and decide if the mp3 is vbr and needs full scan
|
312
311
|
begin
|
313
312
|
bitrate, length = frame_scan(100)
|
314
313
|
if @bitrate != bitrate
|
315
314
|
@vbr = true
|
316
315
|
@bitrate, @length = frame_scan
|
317
|
-
full_scan_occured = true
|
318
316
|
end
|
319
317
|
rescue Mp3InfoInternalError
|
320
318
|
end
|
321
|
-
if (tlen = @tag2["TLEN"]) && !full_scan_occured
|
322
|
-
# but if another duration is given and it isn't close (within 5%)
|
323
|
-
# assume the mp3 is vbr and go with the given duration
|
324
|
-
@length = (tlen.is_a?(Array) ? tlen.last : tlen).to_i/1000
|
325
|
-
@bitrate = (stream_size / @bitrate) / 1024
|
326
|
-
end
|
327
319
|
end
|
328
320
|
ensure
|
329
321
|
@file.close
|
330
322
|
end
|
331
323
|
end
|
332
324
|
|
333
|
-
def get_frames_infos(head)
|
334
|
-
# be sure we are in sync
|
335
|
-
if ((head & 0xffe00000) != 0xffe00000) || # 11 bit MPEG frame sync
|
336
|
-
((head & 0x00060000) == 0x00060000) || # 2 bit layer type
|
337
|
-
((head & 0x0000f000) == 0x0000f000) || # 4 bit bitrate
|
338
|
-
((head & 0x0000f000) == 0x00000000) || # free format bitstream
|
339
|
-
((head & 0x00000c00) == 0x00000c00) || # 2 bit frequency
|
340
|
-
((head & 0xffff0000) == 0xfffe0000)
|
341
|
-
raise Mp3InfoInternalError
|
342
|
-
end
|
343
|
-
mpeg_version = [2.5, nil, 2, 1][bits(head, 20,19)]
|
344
|
-
|
345
|
-
layer = LAYER[bits(head, 18,17)]
|
346
|
-
raise Mp3InfoInternalError if layer == nil || mpeg_version == nil
|
347
|
-
{ :layer => layer,
|
348
|
-
:bitrate => BITRATE[mpeg_version][layer-1][bits(head, 15,12)-1],
|
349
|
-
:samplerate => SAMPLERATE[mpeg_version][bits(head, 11,10)],
|
350
|
-
:mpeg_version => mpeg_version,
|
351
|
-
:padding => (head[9] == 1) }
|
352
|
-
end
|
353
|
-
|
354
325
|
# "block version" of Mp3Info::new()
|
355
326
|
def self.open(*params)
|
356
327
|
m = self.new(*params)
|
@@ -415,7 +386,12 @@ class Mp3Info
|
|
415
386
|
end
|
416
387
|
[pos, length]
|
417
388
|
end
|
418
|
-
|
389
|
+
|
390
|
+
# return the length in seconds of one frame
|
391
|
+
def frame_length
|
392
|
+
SAMPLES_PER_FRAME[@layer][@mpeg_version] / Float(@samplerate)
|
393
|
+
end
|
394
|
+
|
419
395
|
# Flush pending modifications to tags and close the file
|
420
396
|
def close
|
421
397
|
puts "close" if $DEBUG
|
@@ -518,9 +494,58 @@ class Mp3Info
|
|
518
494
|
s
|
519
495
|
end
|
520
496
|
|
497
|
+
# iterates over each mpeg frame over the file, allowing you to
|
498
|
+
# write some funny things, like an mpeg lossless cutter, or frame
|
499
|
+
# counter, or whatever you like ;) +frame+ is a hash with the following keys:
|
500
|
+
# :layer, :bitrate, :samplerate, :mpeg_version, :padding and :size (in bytes)
|
501
|
+
def each_frame
|
502
|
+
File.open(@filename, 'r') do |file|
|
503
|
+
file.seek(@first_frame_pos, File::SEEK_SET)
|
504
|
+
loop do
|
505
|
+
head = file.read(4).unpack("N").first
|
506
|
+
frame = Mp3Info.get_frames_infos(head)
|
507
|
+
file.seek(frame[:size] -4, File::SEEK_CUR)
|
508
|
+
yield frame
|
509
|
+
#puts "frame #{frame_count} len #{frame[:length]} br #{frame[:bitrate]} @file.pos #{@file.pos}"
|
510
|
+
break if file.eof?
|
511
|
+
end
|
512
|
+
end
|
513
|
+
end
|
521
514
|
|
522
515
|
private
|
523
516
|
|
517
|
+
def Mp3Info.get_frames_infos(head)
|
518
|
+
# be sure we are in sync
|
519
|
+
if ((head & 0xffe00000) != 0xffe00000) || # 11 bit MPEG frame sync
|
520
|
+
((head & 0x00060000) == 0x00060000) || # 2 bit layer type
|
521
|
+
((head & 0x0000f000) == 0x0000f000) || # 4 bit bitrate
|
522
|
+
((head & 0x0000f000) == 0x00000000) || # free format bitstream
|
523
|
+
((head & 0x00000c00) == 0x00000c00) || # 2 bit frequency
|
524
|
+
((head & 0xffff0000) == 0xfffe0000)
|
525
|
+
raise Mp3InfoInternalError
|
526
|
+
end
|
527
|
+
mpeg_version = [2.5, nil, 2, 1][bits(head, 20,19)]
|
528
|
+
|
529
|
+
layer = LAYER[bits(head, 18,17)]
|
530
|
+
raise Mp3InfoInternalError if layer == nil || mpeg_version == nil
|
531
|
+
|
532
|
+
bitrate = BITRATE[mpeg_version][layer-1][bits(head, 15,12)-1]
|
533
|
+
samplerate = SAMPLERATE[mpeg_version][bits(head, 11,10)]
|
534
|
+
padding = (head[9] == 1)
|
535
|
+
if layer == 1
|
536
|
+
size = (12 * bitrate*1000.0 / samplerate + (padding ? 1 : 0))*4
|
537
|
+
else # layer 2 and 3
|
538
|
+
size = 144 * (bitrate*1000.0 / samplerate) + (padding ? 1 : 0)
|
539
|
+
end
|
540
|
+
size = size.to_i
|
541
|
+
{ :layer => layer,
|
542
|
+
:bitrate => bitrate,
|
543
|
+
:samplerate => samplerate,
|
544
|
+
:mpeg_version => mpeg_version,
|
545
|
+
:padding => padding,
|
546
|
+
:size => size }
|
547
|
+
end
|
548
|
+
|
524
549
|
### parses the id3 tags of the currently open @file
|
525
550
|
def parse_tags
|
526
551
|
return if @file.stat.size < TAG1_SIZE # file is too small
|
@@ -587,7 +612,7 @@ private
|
|
587
612
|
raise(Mp3InfoError, "end of file reached") if @file.eof?
|
588
613
|
head = 0xff000000 + (data.getbyte(0) << 16) + (data.getbyte(1) << 8) + data.getbyte(2)
|
589
614
|
begin
|
590
|
-
get_frames_infos(head)
|
615
|
+
Mp3Info.get_frames_infos(head)
|
591
616
|
return head
|
592
617
|
rescue Mp3InfoInternalError
|
593
618
|
@file.seek(-3, IO::SEEK_CUR)
|
@@ -603,34 +628,21 @@ private
|
|
603
628
|
|
604
629
|
def frame_scan(frame_limit = nil)
|
605
630
|
frame_count = bitrate_sum = 0
|
606
|
-
|
607
|
-
|
608
|
-
loop do
|
609
|
-
head = @file.read(4).unpack("N").first
|
610
|
-
current_frame = get_frames_infos(head)
|
611
|
-
if current_frame[:layer] == 1
|
612
|
-
frame_length = (12 * current_frame[:bitrate]*1000.0 / current_frame[:samplerate] + (current_frame[:padding] ? 1 : 0))*4
|
613
|
-
else # layer 2 and 3
|
614
|
-
frame_length = 144 * (current_frame[:bitrate]*1000.0 / current_frame[:samplerate]) + (current_frame[:padding] ? 1 : 0)
|
615
|
-
end
|
616
|
-
frame_length = frame_length.to_i
|
617
|
-
bitrate_sum += current_frame[:bitrate]
|
631
|
+
each_frame do |frame|
|
632
|
+
bitrate_sum += frame[:bitrate]
|
618
633
|
frame_count += 1
|
619
|
-
@file.seek(frame_length -4, File::SEEK_CUR)
|
620
|
-
#puts "frame #{frame_count} len #{frame_length} br #{current_frame[:bitrate]} @file.pos #{@file.pos}"
|
621
|
-
break if @file.eof?
|
622
634
|
break if frame_limit && (frame_count >= frame_limit)
|
623
635
|
end
|
624
|
-
|
636
|
+
|
625
637
|
average_bitrate = bitrate_sum/frame_count.to_f
|
626
|
-
length = (frame_count-1) *
|
638
|
+
length = (frame_count-1) * frame_length
|
627
639
|
[average_bitrate, length]
|
628
640
|
end
|
629
641
|
|
630
642
|
|
631
643
|
### returns the selected bit range (b, a) as a number
|
632
644
|
### NOTE: b > a if not, returns 0
|
633
|
-
def bits(number, b, a)
|
645
|
+
def self.bits(number, b, a)
|
634
646
|
t = 0
|
635
647
|
b.downto(a) { |i| t += t + number[i] }
|
636
648
|
t
|
data/lib/mp3info/id3v2.rb
CHANGED
@@ -5,7 +5,10 @@ require "mp3info/extension_modules"
|
|
5
5
|
|
6
6
|
class ID3v2Error < StandardError ; end
|
7
7
|
|
8
|
-
# This class
|
8
|
+
# This class can be used to decode id3v2 tags from files, like .mp3 or .ape for example.
|
9
|
+
# It works like a hash, where key represents the tag name as 3 or 4 upper case letters
|
10
|
+
# (respectively related to 2.2 and 2.3+ tag) and value represented as array or raw value.
|
11
|
+
# Written version is always 2.3.
|
9
12
|
class ID3v2 < DelegateClass(Hash)
|
10
13
|
|
11
14
|
# Major version used when writing tags
|
@@ -88,6 +91,69 @@ class ID3v2 < DelegateClass(Hash)
|
|
88
91
|
"WXXX" => "User defined URL link frame"
|
89
92
|
}
|
90
93
|
|
94
|
+
# Translate V2 to V3 tags
|
95
|
+
TAG_MAPPING_2_2_to_2_3 = {
|
96
|
+
"BUF" => "RBUF",
|
97
|
+
"COM" => "COMM",
|
98
|
+
"CRA" => "AENC",
|
99
|
+
"EQU" => "EQUA",
|
100
|
+
"ETC" => "ETCO",
|
101
|
+
"GEO" => "GEOB",
|
102
|
+
"MCI" => "MCDI",
|
103
|
+
"MLL" => "MLLT",
|
104
|
+
"PIC" => "APIC",
|
105
|
+
"POP" => "POPM",
|
106
|
+
"REV" => "RVRB",
|
107
|
+
"RVA" => "RVAD",
|
108
|
+
"SLT" => "SYLT",
|
109
|
+
"STC" => "SYTC",
|
110
|
+
"TAL" => "TALB",
|
111
|
+
"TBP" => "TBPM",
|
112
|
+
"TCM" => "TCOM",
|
113
|
+
"TCO" => "TCON",
|
114
|
+
"TCR" => "TCOP",
|
115
|
+
"TDA" => "TDAT",
|
116
|
+
"TDY" => "TDLY",
|
117
|
+
"TEN" => "TENC",
|
118
|
+
"TFT" => "TFLT",
|
119
|
+
"TIM" => "TIME",
|
120
|
+
"TKE" => "TKEY",
|
121
|
+
"TLA" => "TLAN",
|
122
|
+
"TLE" => "TLEN",
|
123
|
+
"TMT" => "TMED",
|
124
|
+
"TOA" => "TOPE",
|
125
|
+
"TOF" => "TOFN",
|
126
|
+
"TOL" => "TOLY",
|
127
|
+
"TOR" => "TORY",
|
128
|
+
"TOT" => "TOAL",
|
129
|
+
"TP1" => "TPE1",
|
130
|
+
"TP2" => "TPE2",
|
131
|
+
"TP3" => "TPE3",
|
132
|
+
"TP4" => "TPE4",
|
133
|
+
"TPA" => "TPOS",
|
134
|
+
"TPB" => "TPUB",
|
135
|
+
"TRC" => "TSRC",
|
136
|
+
"TRD" => "TRDA",
|
137
|
+
"TRK" => "TRCK",
|
138
|
+
"TSI" => "TSIZ",
|
139
|
+
"TSS" => "TSSE",
|
140
|
+
"TT1" => "TIT1",
|
141
|
+
"TT2" => "TIT2",
|
142
|
+
"TT3" => "TIT3",
|
143
|
+
"TXT" => "TEXT",
|
144
|
+
"TXX" => "TXXX",
|
145
|
+
"TYE" => "TYER",
|
146
|
+
"UFI" => "UFID",
|
147
|
+
"ULT" => "USLT",
|
148
|
+
"WAF" => "WOAF",
|
149
|
+
"WAR" => "WOAR",
|
150
|
+
"WAS" => "WOAS",
|
151
|
+
"WCM" => "WCOM",
|
152
|
+
"WCP" => "WCOP",
|
153
|
+
"WPB" => "WPB",
|
154
|
+
"WXX" => "WXXX",
|
155
|
+
}
|
156
|
+
|
91
157
|
# See id3v2.4.0-structure document, at section 4.
|
92
158
|
TEXT_ENCODINGS = ["iso-8859-1", "utf-16", "utf-16be", "utf-8"]
|
93
159
|
|
@@ -188,20 +254,28 @@ class ID3v2 < DelegateClass(Hash)
|
|
188
254
|
@hash.each do |k, v|
|
189
255
|
next unless v
|
190
256
|
next if v.respond_to?("empty?") and v.empty?
|
257
|
+
|
258
|
+
# Automagically translate V2 to V3 tags
|
259
|
+
k = TAG_MAPPING_2_2_to_2_3[k] if TAG_MAPPING_2_2_to_2_3.has_key?(k)
|
260
|
+
|
191
261
|
# doesn't encode id3v2.2 tags, which have 3 characters
|
192
262
|
next if k.size != 4
|
193
|
-
|
194
|
-
#
|
263
|
+
|
264
|
+
# Output one flag for each array element, or one only if it's not an array
|
265
|
+
[v].flatten.each do |value|
|
266
|
+
data = encode_tag(k, value.to_s, WRITE_VERSION)
|
267
|
+
#data << "\x00"*2 #End of tag
|
195
268
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
269
|
+
tag << k[0,4] #4 characte max for a tag's key
|
270
|
+
#tag << to_syncsafe(data.size) #+1 because of the language encoding byte
|
271
|
+
size = data.size
|
272
|
+
if RUBY_VERSION >= "1.9.0"
|
273
|
+
size = data.dup.force_encoding("binary").size
|
274
|
+
end
|
275
|
+
tag << [size].pack("N") #+1 because of the language encoding byte
|
276
|
+
tag << "\x00"*2 #flags
|
277
|
+
tag << data
|
201
278
|
end
|
202
|
-
tag << [size].pack("N") #+1 because of the language encoding byte
|
203
|
-
tag << "\x00"*2 #flags
|
204
|
-
tag << data
|
205
279
|
end
|
206
280
|
|
207
281
|
tag_str = "ID3"
|
@@ -236,6 +310,7 @@ class ID3v2 < DelegateClass(Hash)
|
|
236
310
|
|
237
311
|
case name
|
238
312
|
when "COMM"
|
313
|
+
puts "encode COMM: enc: #{text_encoding_index}, lang: #{@options[:lang]}, str: #{transcoded_value.dump}" if $DEBUG
|
239
314
|
[ text_encoding_index , @options[:lang], 0, transcoded_value ].pack("ca3ca*")
|
240
315
|
when /^T/
|
241
316
|
text_encoding_index.chr + transcoded_value
|
@@ -248,7 +323,7 @@ class ID3v2 < DelegateClass(Hash)
|
|
248
323
|
def decode_tag(name, raw_value)
|
249
324
|
puts("decode_tag(#{name.inspect}, #{raw_value.inspect})") if $DEBUG
|
250
325
|
case name
|
251
|
-
when
|
326
|
+
when /^COM/
|
252
327
|
#FIXME improve this
|
253
328
|
encoding, lang, str = raw_value.unpack("ca3a*")
|
254
329
|
out = raw_value.split(0.chr).last
|
@@ -257,15 +332,15 @@ class ID3v2 < DelegateClass(Hash)
|
|
257
332
|
out = raw_value[1..-1]
|
258
333
|
# we need to convert the string in order to match
|
259
334
|
# the requested encoding
|
260
|
-
if out && encoding != @text_encoding_index
|
335
|
+
if encoding && TEXT_ENCODINGS[encoding] && out && encoding != @text_encoding_index
|
261
336
|
begin
|
262
|
-
Iconv.iconv(@options[:encoding], TEXT_ENCODINGS[encoding], out).first
|
337
|
+
out = Iconv.iconv(@options[:encoding], TEXT_ENCODINGS[encoding], out).first
|
263
338
|
rescue Iconv::Failure
|
264
|
-
return out
|
265
339
|
end
|
266
|
-
else
|
267
|
-
return out
|
268
340
|
end
|
341
|
+
# remove padding zeros for textual tags
|
342
|
+
out.sub!(/\0*$/, '')
|
343
|
+
return out
|
269
344
|
else
|
270
345
|
return raw_value
|
271
346
|
end
|
@@ -276,7 +351,7 @@ class ID3v2 < DelegateClass(Hash)
|
|
276
351
|
def read_id3v2_3_frames
|
277
352
|
loop do # there are 2 ways to end the loop
|
278
353
|
name = @io.read(4)
|
279
|
-
if name.getbyte(0) == 0
|
354
|
+
if name.nil? || name.getbyte(0) == 0 || name == "MP3e" #bug caused by old tagging application "mp3ext" ( http://www.mutschler.de/mp3ext/ )
|
280
355
|
@io.seek(-4, IO::SEEK_CUR) # 1. find a padding zero,
|
281
356
|
seek_to_v2_end
|
282
357
|
break
|
@@ -299,7 +374,7 @@ class ID3v2 < DelegateClass(Hash)
|
|
299
374
|
def read_id3v2_2_frames
|
300
375
|
loop do
|
301
376
|
name = @io.read(3)
|
302
|
-
if name.getbyte(0) == 0
|
377
|
+
if name.nil? || name.getbyte(0) == 0
|
303
378
|
@io.seek(-3, IO::SEEK_CUR)
|
304
379
|
seek_to_v2_end
|
305
380
|
break
|
@@ -323,10 +398,6 @@ class ID3v2 < DelegateClass(Hash)
|
|
323
398
|
|
324
399
|
data_io = @io.read(size)
|
325
400
|
data = decode_tag(name, data_io)
|
326
|
-
# remove padding zeros for textual tags
|
327
|
-
if data && name =~ /^T/
|
328
|
-
data.sub!(/\0*$/, '')
|
329
|
-
end
|
330
401
|
|
331
402
|
if self["TPOS"] =~ /(\d+)\s*\/\s*(\d+)/
|
332
403
|
self["disc_number"] = $1.to_i
|
@@ -348,7 +419,7 @@ class ID3v2 < DelegateClass(Hash)
|
|
348
419
|
### frame, which should be first 0xff byte after id3v2 padding zero's
|
349
420
|
def seek_to_v2_end
|
350
421
|
until @io.getbyte == 0xff
|
351
|
-
raise
|
422
|
+
raise ID3v2Error, "got EOF before finding id3v2 end" if @io.eof?
|
352
423
|
end
|
353
424
|
@io.seek(-1, IO::SEEK_CUR)
|
354
425
|
end
|
@@ -357,11 +428,5 @@ class ID3v2 < DelegateClass(Hash)
|
|
357
428
|
def to_syncsafe(num)
|
358
429
|
( (num<<3) & 0x7f000000 ) + ( (num<<2) & 0x7f0000 ) + ( (num<<1) & 0x7f00 ) + ( num & 0x7f )
|
359
430
|
end
|
360
|
-
|
361
|
-
# def method_missing(meth, *args)
|
362
|
-
# m = meth.id2name
|
363
|
-
# return nil if TAGS.has_key?(m) and self[m].nil?
|
364
|
-
# super
|
365
|
-
# end
|
366
431
|
end
|
367
432
|
|
data/test/test_ruby-mp3info.rb
CHANGED
@@ -302,9 +302,11 @@ class Mp3InfoTest < Test::Unit::TestCase
|
|
302
302
|
expected_tag = {
|
303
303
|
"genre_s" => "Hip Hop/Rap",
|
304
304
|
"title" => "Intro",
|
305
|
-
"comments" => "\000engiTunPGAP\0000\000\000",
|
305
|
+
#"comments" => "\000engiTunPGAP\0000\000\000",
|
306
|
+
"comments" => "0",
|
306
307
|
"year" => 2006,
|
307
308
|
"album" => "Air Max",
|
309
|
+
"artist" => "Grems Aka Supermicro",
|
308
310
|
"tracknum" => 1 }
|
309
311
|
# test universal tag
|
310
312
|
assert_equal expected_tag, mp3.tag
|
@@ -318,13 +320,13 @@ class Mp3InfoTest < Test::Unit::TestCase
|
|
318
320
|
mp3.tag.comments = "comments"
|
319
321
|
mp3.flush
|
320
322
|
expected_tag = {
|
321
|
-
"artist"=>"toto",
|
322
|
-
"genre_s"=>"Hip Hop/Rap",
|
323
|
-
"title"=>"Intro",
|
324
|
-
"comments"=>"comments",
|
325
|
-
"year"=>2006,
|
326
|
-
"album"=>"Air Max",
|
327
|
-
"tracknum"=>1}
|
323
|
+
"artist" => "toto",
|
324
|
+
"genre_s" => "Hip Hop/Rap",
|
325
|
+
"title" => "Intro",
|
326
|
+
"comments" => "comments",
|
327
|
+
"year" => 2006,
|
328
|
+
"album" => "Air Max",
|
329
|
+
"tracknum" => 1}
|
328
330
|
|
329
331
|
assert_equal expected_tag, mp3.tag
|
330
332
|
end
|
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.13
|
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: 2009-
|
12
|
+
date: 2009-05-26 00:00:00 +02:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -20,7 +20,7 @@ dependencies:
|
|
20
20
|
requirements:
|
21
21
|
- - ">="
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 1.
|
23
|
+
version: 1.9.0
|
24
24
|
version:
|
25
25
|
description: "* written in pure ruby * read low-level informations like bitrate, length, samplerate, etc... * read, write, remove id3v1 and id3v2 tags * correctly read VBR files (with or without Xing header) * 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"
|
26
26
|
email: moumar@rubyforge.org
|