ruby-mp3info 0.8.3 → 0.8.4

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