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 +4 -4
- data/History.txt +4 -0
- data/lib/mp3info.rb +62 -76
- data/test/test_ruby-mp3info.rb +18 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2e73d57ec360910c42245abd65123968dc3a12c3
|
4
|
+
data.tar.gz: 41a47932af670ac092a1929ae18b796dc4b3a514
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc2a8e453af0620cc2159d8dd7b960df38196a3abceaa5716b845920b8f444feac7fbf99bbbb6e79038d2b9346c9934cc35d517ef8e22607f198f20284836db7
|
7
|
+
data.tar.gz: 4c89f8648c5075417f2d21199516037dfe09acccbca2f88de865aaf7077e1d8ccafef7ea5fdbb94e4666db4a5bf0bef07b3169e38732bf5dc5f6253915255aea
|
data/History.txt
CHANGED
data/lib/mp3info.rb
CHANGED
@@ -17,22 +17,22 @@ end
|
|
17
17
|
|
18
18
|
class Mp3Info
|
19
19
|
|
20
|
-
VERSION = "0.8.
|
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
|
-
|
263
|
-
|
264
|
-
|
257
|
+
if @tag_parsing_enabled
|
258
|
+
parse_tags
|
259
|
+
@tag1_orig = @tag1.dup
|
265
260
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
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
|
-
|
291
|
-
|
292
|
-
|
285
|
+
@tag.extend(HashKeys)
|
286
|
+
@tag_orig = @tag.dup
|
287
|
+
end
|
293
288
|
|
294
|
-
|
295
|
-
|
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
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
695
|
-
|
696
|
-
@
|
697
|
-
|
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
|
data/test/test_ruby-mp3info.rb
CHANGED
@@ -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.
|
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-
|
11
|
+
date: 2014-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rdoc
|