ruby-mp3info 0.8.3 → 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 80ba2cacb2d0a83f95af04c66b595c8f62b3aa08
4
- data.tar.gz: 44c530109c47f779e6c5d59a1251caedb19b62a5
3
+ metadata.gz: 2e73d57ec360910c42245abd65123968dc3a12c3
4
+ data.tar.gz: 41a47932af670ac092a1929ae18b796dc4b3a514
5
5
  SHA512:
6
- metadata.gz: a4c86af54616f804a9a64eb3cab55c4b327981b7ddbe24343bd097d78be691d599e3ece9f935cda00c24f8c0a88f20bc04011303c6911b67c6c716442995d5c4
7
- data.tar.gz: ad7a47b2b0027c184040cc4361bfd2d6ee674ba6292d1cbb451a59e42c42867f92c007201a6be5524f77eea99ea1ffc2d9619cbeef0fc58764022ba9f2bb1b33
6
+ metadata.gz: fc2a8e453af0620cc2159d8dd7b960df38196a3abceaa5716b845920b8f444feac7fbf99bbbb6e79038d2b9346c9934cc35d517ef8e22607f198f20284836db7
7
+ data.tar.gz: 4c89f8648c5075417f2d21199516037dfe09acccbca2f88de865aaf7077e1d8ccafef7ea5fdbb94e4666db4a5bf0bef07b3169e38732bf5dc5f6253915255aea
data/History.txt CHANGED
@@ -1,3 +1,7 @@
1
+ === 0.8.4 / 2014-04-26
2
+
3
+ * more robust frame scanning when unsynced (thanks to emonsqueeze for the bug report)
4
+
1
5
  === 0.8.3 / 2014-01-18
2
6
 
3
7
  * fix for ruby 2.1.0 (thanks to smashwilson)
data/lib/mp3info.rb CHANGED
@@ -17,22 +17,22 @@ end
17
17
 
18
18
  class Mp3Info
19
19
 
20
- VERSION = "0.8.3"
20
+ VERSION = "0.8.4"
21
21
 
22
22
  LAYER = [ nil, 3, 2, 1]
23
23
  BITRATE = {
24
- 1 =>
24
+ 1 =>
25
25
  [
26
26
  [32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448],
27
27
  [32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384],
28
28
  [32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320] ],
29
- 2 =>
29
+ 2 =>
30
30
  [
31
31
  [32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256],
32
32
  [8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160],
33
33
  [8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160]
34
34
  ],
35
- 2.5 =>
35
+ 2.5 =>
36
36
  [
37
37
  [32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256],
38
38
  [8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160],
@@ -253,49 +253,43 @@ class Mp3Info
253
253
  @tag1 = @tag = @tag1_orig = @tag_orig = {}
254
254
  @tag1.extend(HashKeys)
255
255
  @tag2 = ID3v2.new(@id3v2_options)
256
-
257
- begin
258
- if @tag_parsing_enabled
259
- parse_tags
260
- @tag1_orig = @tag1.dup
261
256
 
262
- if hastag1?
263
- @tag = @tag1.dup
264
- end
257
+ if @tag_parsing_enabled
258
+ parse_tags
259
+ @tag1_orig = @tag1.dup
265
260
 
266
- if hastag2?
267
- @tag = {}
268
- # creation of a sort of "universal" tag, regardless of the tag version
269
- tag2_mapping = @tag2.version =~ /^2\.2/ ? TAG_MAPPING_2_2 : TAG_MAPPING_2_3
270
- tag2_mapping.each do |key, tag2_name|
271
- tag_value = (@tag2[tag2_name].is_a?(Array) ? @tag2[tag2_name].first : @tag2[tag2_name])
272
- next unless tag_value
273
- @tag[key] = tag_value.is_a?(Array) ? tag_value.first : tag_value
274
-
275
- if %w{year tracknum}.include?(key)
276
- @tag[key] = tag_value.to_i
277
- end
278
- # this is a special case with id3v2.2, which uses
279
- # old fashionned id3v1 genres
280
- if tag2_name == "TCO" && tag_value =~ /^\((\d+)\)$/
281
- @tag["genre_s"] = GENRES[$1.to_i]
282
- end
261
+ if hastag1?
262
+ @tag = @tag1.dup
263
+ end
264
+
265
+ if hastag2?
266
+ @tag = {}
267
+ # creation of a sort of "universal" tag, regardless of the tag version
268
+ tag2_mapping = @tag2.version =~ /^2\.2/ ? TAG_MAPPING_2_2 : TAG_MAPPING_2_3
269
+ tag2_mapping.each do |key, tag2_name|
270
+ tag_value = (@tag2[tag2_name].is_a?(Array) ? @tag2[tag2_name].first : @tag2[tag2_name])
271
+ next unless tag_value
272
+ @tag[key] = tag_value.is_a?(Array) ? tag_value.first : tag_value
273
+
274
+ if %w{year tracknum}.include?(key)
275
+ @tag[key] = tag_value.to_i
276
+ end
277
+ # this is a special case with id3v2.2, which uses
278
+ # old fashionned id3v1 genres
279
+ if tag2_name == "TCO" && tag_value =~ /^\((\d+)\)$/
280
+ @tag["genre_s"] = GENRES[$1.to_i]
283
281
  end
284
282
  end
285
-
286
- @tag.extend(HashKeys)
287
- @tag_orig = @tag.dup
288
283
  end
289
284
 
290
- if @mp3_parsing_enabled
291
- parse_mp3
292
- end
285
+ @tag.extend(HashKeys)
286
+ @tag_orig = @tag.dup
287
+ end
293
288
 
294
- ensure
295
- if @io_is_a_file
296
- @io.close
297
- end
289
+ if @mp3_parsing_enabled
290
+ parse_mp3
298
291
  end
292
+
299
293
  end
300
294
 
301
295
  # "block version" of Mp3Info::new()
@@ -365,7 +359,7 @@ class Mp3Info
365
359
  end
366
360
 
367
361
  # return the length in seconds of one frame
368
- def frame_length
362
+ def get_frame_length
369
363
  SAMPLES_PER_FRAME[@layer][@mpeg_version] / Float(@samplerate)
370
364
  end
371
365
 
@@ -483,17 +477,7 @@ class Mp3Info
483
477
  def each_frame
484
478
  @io.seek(@first_frame_pos, File::SEEK_SET)
485
479
  loop do
486
- head = @io.read(4).unpack("N").first
487
- begin
488
- frame = Mp3Info.get_frames_infos(head)
489
- rescue Mp3InfoInternalError
490
- begin
491
- frame = find_next_frame
492
- rescue Mp3InfoError
493
- break
494
- end
495
- end
496
-
480
+ frame = find_next_frame
497
481
  yield frame
498
482
  @io.seek(frame[:size] -4, File::SEEK_CUR)
499
483
  #puts "frame #{frame_count} len #{frame[:length]} br #{frame[:bitrate]} @io.pos #{@io.pos}"
@@ -502,7 +486,7 @@ class Mp3Info
502
486
  end
503
487
 
504
488
  private
505
-
489
+
506
490
  def Mp3Info.get_frames_infos(head)
507
491
  # be sure we are in sync
508
492
  if ((head & 0xffe00000) != 0xffe00000) || # 11 bit MPEG frame sync
@@ -510,11 +494,11 @@ private
510
494
  ((head & 0x0000f000) == 0x0000f000) || # 4 bit bitrate
511
495
  ((head & 0x0000f000) == 0x00000000) || # free format bitstream
512
496
  ((head & 0x00000c00) == 0x00000c00) || # 2 bit frequency
513
- ((head & 0xffff0000) == 0xfffe0000)
514
- raise Mp3InfoInternalError
497
+ ((head & 0xffff0000) == 0xfffe0000)
498
+ raise Mp3InfoInternalError, "unsynced frame"
515
499
  end
516
500
  mpeg_version = [2.5, nil, 2, 1][bits(head, 20,19)]
517
-
501
+
518
502
  layer = LAYER[bits(head, 18,17)]
519
503
  raise Mp3InfoInternalError if layer == nil || mpeg_version == nil
520
504
 
@@ -522,12 +506,12 @@ private
522
506
  samplerate = SAMPLERATE[mpeg_version][bits(head, 11,10)]
523
507
  padding = (head[9] == 1)
524
508
  if layer == 1
525
- size = (12 * bitrate*1000.0 / samplerate + (padding ? 1 : 0))*4
509
+ size = (12 * bitrate*1000.0 / samplerate + (padding ? 1 : 0))*4
526
510
  else # layer 2 and 3
527
511
  size = 144 * (bitrate*1000.0 / samplerate) + (padding ? 1 : 0)
528
512
  end
529
513
  size = size.to_i
530
- channel_num = Mp3Info.bits(head, 7,6)
514
+ channel_num = Mp3Info.bits(head, 7, 6)
531
515
  { :layer => layer,
532
516
  :bitrate => bitrate,
533
517
  :samplerate => samplerate,
@@ -553,26 +537,26 @@ private
553
537
  @io.seek(0)
554
538
  f3 = @io.read(3)
555
539
  # v1 tag at beginning
556
- if f3 == "TAG"
557
- gettag1
540
+ if f3 == "TAG"
541
+ gettag1
558
542
  @tag1_parsed = true
559
543
  end
560
544
 
561
545
  @tag2.from_io(@io) if f3 == "ID3" # v2 tag at beginning
562
-
546
+
563
547
  unless @tag1_parsed # v1 tag at end
564
548
  # this preserves the file pos if tag2 found, since gettag2 leaves
565
549
  # the file at the best guess as to the first MPEG frame
566
550
  pos = (@tag2.io_position || 0)
567
551
  # seek to where id3v1 tag should be
568
- @io.seek(-TAG1_SIZE, IO::SEEK_END)
552
+ @io.seek(-TAG1_SIZE, IO::SEEK_END)
569
553
  if @io.read(3) == "TAG"
570
554
  gettag1
571
555
  end
572
556
  @io.seek(pos)
573
557
  end
574
558
  end
575
-
559
+
576
560
  ### gets id3v1 tag information from @io
577
561
  ### assumes @io is pointing to char after "TAG" id
578
562
  def gettag1
@@ -635,20 +619,20 @@ private
635
619
  frame_count += 1
636
620
  break if frame_limit && (frame_count >= frame_limit)
637
621
  end
638
-
622
+
639
623
  average_bitrate = bitrate_sum/frame_count.to_f
640
- length = (frame_count-1) * frame_length
624
+ length = frame_count * get_frame_length
641
625
  [average_bitrate, length]
642
626
  end
643
627
 
644
628
  def parse_mp3
645
629
  ### extracts MPEG info from MPEG header and stores it in the hash @mpeg
646
630
  ### head (fixnum) = valid 4 byte MPEG header
647
-
631
+
648
632
  found = false
649
633
 
650
634
  5.times do
651
- @header = find_next_frame()
635
+ @header = find_next_frame()
652
636
  @first_frame_pos = @io.pos - 4
653
637
  [ :mpeg_version, :layer, :channel_mode,
654
638
  :channel_num, :bitrate, :samplerate ].each do |var_name|
@@ -661,12 +645,12 @@ private
661
645
 
662
646
  raise(Mp3InfoError, "Cannot find good frame") unless found
663
647
 
664
- seek = @mpeg_version == 1 ?
665
- (@channel_num == 3 ? 17 : 32) :
648
+ seek = @mpeg_version == 1 ?
649
+ (@channel_num == 3 ? 17 : 32) :
666
650
  (@channel_num == 3 ? 9 : 17)
667
651
 
668
652
  @io.seek(seek, IO::SEEK_CUR)
669
-
653
+
670
654
  vbr_head = @io.read(4)
671
655
  if vbr_head == "Xing"
672
656
  puts "Xing header (VBR) detected" if $DEBUG
@@ -680,21 +664,23 @@ private
680
664
  @io.seek(100, IO::SEEK_CUR) if flags[0] == 1
681
665
  #@vbr_quality = @io.get32bits if flags[3] == 1
682
666
 
683
- samples_per_frame = SAMPLES_PER_FRAME[@layer][@mpeg_version]
684
- @length = frame_count * samples_per_frame / Float(@samplerate)
667
+ @length = frame_count * get_frame_length
685
668
 
686
669
  @bitrate = (((stream_size/frame_count)*@samplerate)/144) / 1024
687
670
  @vbr = true
688
671
  else
689
672
  # for cbr, calculate duration with the given bitrate
690
-
673
+
691
674
  stream_size = @io_size - (hastag1? ? TAG1_SIZE : 0) - (@tag2.io_position || 0)
692
675
  @length = ((stream_size << 3)/1000.0)/@bitrate
693
676
  # read the first 100 frames and decide if the mp3 is vbr and needs full scan
694
- average_bitrate = frame_scan(100).first
695
- if @bitrate != average_bitrate
696
- @vbr = true
697
- @bitrate, @length = frame_scan
677
+ begin
678
+ average_bitrate, _ = frame_scan(100)
679
+ if @bitrate != average_bitrate
680
+ @vbr = true
681
+ @bitrate, @length = frame_scan
682
+ end
683
+ rescue Mp3InfoError
698
684
  end
699
685
  end
700
686
  end
@@ -347,7 +347,7 @@ class Mp3InfoTest < Test::Unit::TestCase
347
347
  mp3.tag.artist = "toto"
348
348
  mp3.tag.comments = "comments"
349
349
  mp3.flush
350
- expected_tag = {
350
+ expected_tag = {
351
351
  "artist" => "toto",
352
352
  "genre_s" => "Hip Hop/Rap",
353
353
  "title" => "Intro",
@@ -495,6 +495,23 @@ class Mp3InfoTest < Test::Unit::TestCase
495
495
  end
496
496
  end
497
497
 
498
+ def test_parsing_unsynced_file
499
+ load_fixture_to_temp_file("vbr")
500
+ File.open("/tmp/test.mp3", "w") do |tf|
501
+ tf.write File.read(TEMP_FILE, 96512)
502
+ tf.write "\0\0"
503
+ tf.write File.read(TEMP_FILE, nil, 96512)
504
+ end
505
+
506
+ Mp3Info.open("/tmp/test.mp3") do |info|
507
+ assert_nothing_raised do
508
+ info.each_frame { |frame| frame }
509
+ end
510
+ assert(info.vbr)
511
+ assert_in_delta(174.210612, info.length, 0.000001)
512
+ end
513
+ end
514
+
498
515
  def compute_audio_content_mp3_digest(mp3)
499
516
  pos, size = mp3.audio_content
500
517
  data = File.open(mp3.filename) do |f|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-mp3info
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.3
4
+ version: 0.8.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Guillaume Pierronnet
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-18 00:00:00.000000000 Z
11
+ date: 2014-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rdoc