ruby-mp3info 0.6.12 → 0.6.13
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 +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
|