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