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.
@@ -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