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.
@@ -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
@@ -1,4 +1,5 @@
1
- # $Id: mp3info.rb 82 2008-04-19 10:59:02Z moumar $
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.5"
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
- V1_V2_TAG_MAPPING = {
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
- $stderr.puts("#{self.class}::new() does not take block; use #{self.class}::open() instead") if block_given?
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
- #creation of a sort of "universal" tag, regardless of the tag version
197
- V1_V2_TAG_MAPPING.each do |key1, key2|
198
- t2 = @tag2[key2]
199
- next unless t2
200
- @tag[key1] = t2.is_a?(Array) ? t2.first : t2
201
-
202
- if key1 == "tracknum"
203
- val = @tag2[key2].is_a?(Array) ? @tag2[key2].first : @tag2[key2]
204
- @tag[key1] = val.to_i
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
- # every single frame
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
- V1_V2_TAG_MAPPING.each do |key1, key2|
364
- @tag2[key2] = @tag[key1] if @tag[key1]
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
- =begin
499
- # FIXME remove that
500
- pos = @file.pos
501
- p @file.read
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[-2] == 0
512
- @tag1["tracknum"] = comments[-1].to_i
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
- #@tag1["comments"] = comments.sub!(/\0.*$/, '')
516
- @tag1["comments"] = comments.strip
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.getc == 0xff
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[0] << 16) + (data[1] << 8) + data[2]
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(n, b, a)
562
+ def bits(number, b, a)
567
563
  t = 0
568
- b.downto(a) { |i| t += t + n[i] }
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
- (getc << 24) + (getc << 16) + (getc << 8) + getc
34
+ (getbyte << 24) + (getbyte << 16) + (getbyte << 8) + getbyte
19
35
  end
20
36
 
21
37
  def get_syncsafe
22
- (getc << 21) + (getc << 14) + (getc << 7) + getc
23
- end
38
+ (getbyte << 21) + (getbyte << 14) + (getbyte << 7) + getbyte
39
+ end
24
40
  end
25
41
  end
@@ -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
- "2.#{@version_maj}.#{@version_min}"
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.getc == 0; end
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
- tag << [data.size].pack("N") #+1 because of the language encoding byte
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
- p tag_str if $DEBUG
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[0] == ?T || name == "COMM" ) && version == 3
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)[0]
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[0] # language encoding (see TEXT_ENCODINGS constant)
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)[0]
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[0] == 0 or name == "MP3e" #bug caused by old tagging application "mp3ext" ( http://www.mutschler.de/mp3ext/ )
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[0] == 0
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.getc << 16) + (@io.getc << 8) + @io.getc
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].to_a
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.getc == 0xff
346
+ until @io.getbyte == 0xff
327
347
  raise EOFError if @io.eof?
328
348
  end
329
349
  @io.seek(-1, IO::SEEK_CUR)
@@ -1,16 +1,18 @@
1
- #!/usr/bin/ruby -w
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($0), "test_mp3info.mp3")
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 | lame --preset cbr 128 -r -s 44.1 --bitwidth 16 - - | ruby -rbase64 -e 'print Base64.encode64($stdin.read)'
38
- @valid_mp3 = Base64.decode64 <<EOF
39
- //uQZAAAAAAAaQYAAAAAAA0gwAAAAAABpBwAAAAAADSDgAAATEFNRTMuOTNV
40
- VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
41
- VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
42
- VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
43
- VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
44
- VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
45
- VVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuOTNVVVVVVVVVVVVVVVVVVVVVVVVV
46
- VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
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
- temp_file_vbr = File.join(File.dirname($0), "test_vbr_length.mp3")
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(temp_file_vbr) do |info|
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| assert(info.tag1 == @tag) }
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
- assert_equal(tag, write_temp_file(tag))
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
- File.rename(TEMP_FILE, fn)
344
+ FileUtils.cp(TEMP_FILE, fn)
338
345
  begin
339
- mp3 = Mp3Info.new(fn)
340
- mp3.tag.title = fn
341
- mp3.close
342
- mp3.reload
343
- assert_equal fn, mp3.tag.title
344
- mp3.close
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 "all\xc3\xa9", mp3.tag2['TEST']
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
- assert_equal "all\xe9", mp3.tag2['TEST']
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
- assert_equal "all\xe9", mp3.tag2['TEST']
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.5
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-04-19 00:00:00 +02:00
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.1
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