#!/usr/bin/perl #use reed::Displayer; use constant { DEBUG => 1, ## DEBUG prints all sorts of interesting garbage }; %Bitrates = ( '1' => { '1' => [qw( 0 32 64 96 128 160 192 224 256 288 320 352 384 416 448 0 )], '2' => [qw( 0 32 48 56 64 80 96 112 128 160 192 224 256 320 384 0 )], '3' => [qw( 0 32 40 48 56 64 80 96 112 128 160 192 224 256 320 0 )], }, '2' => { '1' => [qw( 0 32 48 56 64 80 96 112 128 144 160 176 192 224 256 0 )], '2' => [qw( 0 8 16 24 32 40 48 56 64 80 96 112 128 144 160 0 )], '3' => [qw( 0 8 16 24 32 40 48 56 64 80 96 112 128 144 160 0 )], }, '2.5' => { '1' => [qw( 0 32 48 56 64 80 96 112 128 144 160 176 192 224 256 0 )], '2' => [qw( 0 8 16 24 32 40 48 56 64 80 96 112 128 144 160 0 )], '3' => [qw( 0 8 16 24 32 40 48 56 64 80 96 112 128 144 160 0 )], } ); %BlockTypes = ( '000' => 'Long', '010' => 'Start', '100' => 'Short', '110' => 'Stop', '001' => 'Mixed', '011' => 'Mixed (Start)', '101' => 'Mixed (Short)', '111' => 'Mixed (Stop)', 'xxx' => 'Long (*)', ); #$InFile = 'Dali.mp3'; #$InFile = 'Nores.mp3'; #$InFile = 'Res.mp3'; $InFile = $ARGV[0]; open($IN, $InFile) or die "ERROR: Can't open file '$InFile' for reading!\n$!\n"; binmode($IN); @Frames = getFrames($IN); ######################## ## Print out the data ## ######################## # bitrate if(1) { print 'bitrate={'; for my $frame (0 .. $#Frames) { print $Frames[$frame]{'bitrate'}, ($frame == $#Frames ? '' : ','); } print '};', "\n\n"; } # Bits per frame if(1) { print 'bpf={'; for my $frame (0 .. $#Frames) { print $Frames[$frame]{'length'} * 8, ($frame == $#Frames ? '' : ','); } print '};', "\n\n"; } # Bits used if(1) { print 'bitsused={'; for my $frame (0 .. $#Frames) { print $Frames[$frame]{'used'}, ($frame == $#Frames ? '' : ','); } print '};', "\n\n"; } # Current reservoir if(1) { print 'reservoir={'; for my $frame (0 .. $#Frames) { print $Frames[$frame]{'reservoir'}, ($frame == $#Frames ? '' : ','); } print '};', "\n\n"; } my $a = 0; my $b = 0; my $c = 0; foreach my $frame (@Frames) { $a ++; $b += $frame->{'used'} + 36 * 8; $c += $frame->{'bitrate'}; } $Bitrate = $b / $a * 44100 / (144000 * 8); $FrameBitrate = $c / $a; #die "$Bitrate\n$FrameBitrate"; ########## ## 5UB5 ## ########## # This was just hacked out of my PAR2 parser # (hence the references to "packets") sub getFrames { my ($in, $readSize) = @_; my @FrameInfo; $readSize ||= 65536; my $fileSize = -s $in; my $buffer = ''; my $tellBegin = 0; my $tellEnd = 0; while(1) { LOOPBEGIN: print "Get a bunch of data from the file\n" if DEBUG; my $pos1 = pos($buffer); read($in, $buffer, $readSize, length($buffer)); pos($buffer) = $pos1; $tellEnd = tell($in); print "Begin reading at $tellBegin - $tellEnd\n" if DEBUG; IFBEGIN: if($buffer =~ m/\xFF/g) { my $foundPos = pos($buffer) - 1; my $packetLocation = $foundPos + $tellBegin; print " Found a possible header at $foundPos ($packetLocation)\n" if DEBUG; if($tellEnd - $packetLocation < 36) { print " The buffer is not long enough!\n" if DEBUG; my $pos1 = pos($buffer); read($in, $buffer, $readSize, length($buffer)); pos($buffer) = $pos1; $tellEnd = tell($in); print " Now buffer's ", length($buffer), " bytes long ($tellBegin - $tellEnd)\n" if DEBUG; if($tellEnd - $packetLocation < 36) { print " That's STILL not long enough, but the file ended! Oh, the humanity!\n" if DEBUG; last; } } my ($sync1, $sync2, $sync3, $id1, $id2, $layer1, $layer2, $protected) = split(//, unpack("B8", substr($buffer, $foundPos + 1, 1))); my $bitrateq = hex(unpack("H", substr($buffer, $foundPos + 2, 1))); my (undef, undef, undef, undef, $sample1, $sample2, $pad, $private) = split(//, unpack("B8", substr($buffer, $foundPos + 2, 1))); my ($channel1, $channel2, $ext1, $ext2, $copyright, $orig, $emph1, $emph2) = split(//, unpack("B8", substr($buffer, $foundPos + 3, 1))); ## Looks like the file's protected if this bit is set to *0*. $protected = ! $protected; # if(DEBUG) { #print < $bitrate, 'channel' => $channelMode, 'padding' => $pad, 'length' => $frameLength}); # READ SIDE-INFO if($layer == 3) { if($protected) { if($channelMode eq 'Mono') { if($id == 1) { # MPEG1 Layer 3 mono PROTECTED mpeg1mono(substr($buffer, $foundPos + 6, 17)); } else { mpeg3mono(substr($buffer, $foundPos + 6, 9)); } } else { if($id == 1) { # MPEG1 Layer 3 stereo PROTECTED my $temp = mpeg1stereo(substr($buffer, $foundPos + 6, 32)); $FrameInfo[-1]{'reservoir'} = $temp->[0]; $FrameInfo[-1]{'used'} = $temp->[1]; $FrameInfo[-1]{'block'} = [[[$temp->[2][0], $temp->[3][0]], [$temp->[2][2], $temp->[3][2]]],[[$temp->[2][1], $temp->[3][1]], [$temp->[2][3], $temp->[3][3]]]]; } else { mpeg3stereo(substr($buffer, $foundPos + 6, 17)); } } } else { if($channelMode eq 'Mono') { if($id == 1) { # MPEG1 Layer 3 mono mpeg1mono(substr($buffer, $foundPos + 4, 17)); } else { mpeg3mono(substr($buffer, $foundPos + 4, 9)); } } else { if($id == 1) { # MPEG1 Layer 3 stereo (probably the most common) my $temp = mpeg1stereo(substr($buffer, $foundPos + 4, 32)); $FrameInfo[-1]{'reservoir'} = $temp->[0]; $FrameInfo[-1]{'used'} = $temp->[1]; $FrameInfo[-1]{'block'} = [[[$temp->[2][0], $temp->[3][0]], [$temp->[2][2], $temp->[3][2]]],[[$temp->[2][1], $temp->[3][1]], [$temp->[2][3], $temp->[3][3]]]]; } else { mpeg3stereo(substr($buffer, $foundPos + 4, 17)); } } } } #if($foundPos == 4179) { # Displayer(\@FrameInfo); # die "a horrible death"; #} pos($buffer) = $foundPos + $frameLength; goto IFBEGIN; } else { # Found nothing print " No possible headers found\n" if DEBUG; $buffer = ''; $tellBegin = $tellEnd; if($tellEnd == $fileSize) { # The end of the file! print " And the end of the file has been reached!\n" if DEBUG; last; } else { # goto! print " Redo\n" if DEBUG; goto LOOPBEGIN; } } } return wantarray ? (@FrameInfo) : [@FrameInfo]; } sub mpeg1stereo { my $sideInfo = $_[0]; # Displayer unpack("B*", $sideInfo); die; my $food = unpack("B*", $sideInfo); my @out = unpack("A9A3A4A4A12A9A8A4AA22AAAA12A9A8A4AA22AAAA12A9A8A4AA22AAAA12A9A8A4AA22AAA", $food); # Displayer(@out); my $startOffset = unpack("n", pack("B*", '0000000' . $out[0])); my @bits = ( unpack("n", pack("B*", '0000' . $out[4])), unpack("n", pack("B*", '0000' . $out[13])), unpack("n", pack("B*", '0000' . $out[22])), unpack("n", pack("B*", '0000' . $out[31])), ); my $bitsUsed = $bits[0] + $bits[1] + $bits[2] + $bits[3]; my @blockTypes = ( ($out[ 8]) ? ($BlockTypes{substr($out[ 9], 0, 3)}) : ($BlockTypes{'xxx'}), ($out[17]) ? ($BlockTypes{substr($out[18], 0, 3)}) : ($BlockTypes{'xxx'}), ($out[26]) ? ($BlockTypes{substr($out[27], 0, 3)}) : ($BlockTypes{'xxx'}), ($out[35]) ? ($BlockTypes{substr($out[36], 0, 3)}) : ($BlockTypes{'xxx'}), ); # print "****$startOffset****\n"; # print "****$bitsUsed****\n"; # print "****(", join(",", @blockTypes), ")****\n"; return [$startOffset, $bitsUsed, \@blockTypes, \@bits]; } sub mpeg1mono { my $sideInfo = $_[0]; } sub mpeg2stereo { my $sideInfo = $_[0]; } sub mpeg2mono { my $sideInfo = $_[0]; } __END__ 0 Long 1 Start 2 Short 3 Stop ## Side info: # 17/32 BYTES = 136/256 bits # 9: main_data_begin # ?: private_bits # 4: SCFI Band # 59: Side Information Granule # 12: part2_3 length (length of side info + main data in bits?) # 9: Big values # 8: Global gain # 4: Scalefactor compress (9 for MPEG2) # 1: Window switch flag # if 1: # 2: Block type # 1: Mix block flag # 5x2: Table Select [region] # 3x3: sub_block_gain [window] # if 0: # 5x3: Table select [region] # 4: Region 0 count # 3: Region 1 count # 1: Pre flag # 1: Scale factor scale # 1: Count1 table select # MPEG1 mono: # [main data] [5 privates] [SCFI] [Gr0] [Gr1] # MPEG1 stereo: # [main data] [3 privates] [SCFI0] [SCFI1] [Gr0ch1] [Gr0ch2] [Gr1ch1] [Gr1ch2] # MPEG2 mono: # [main data] [1 privates] [Gr*] # MPEG2 stereo: # [main data] [2 privates] [Gr*] # 32 bytes for MPEG1 stereo # 17 bytes for MPEG1 mono, MPEG2 stereo # 9 bytes for MPEG2 mono NORES @ 3761 0: 000000000 4: 001110010101 13: 000000000000 22: 010100110011 31: 000000000000 281 @ 4179 0: 000000000 4: 001100101111 13: 001011000010 22: 001011111100 31: 001010110010 371.875 RES 0: 110011111 4: 011010100111 13: 000000000000 22: 011010000011 31: 000000000000 421.25 0: 101110111 4: 011011101010 13: 011010001110 22: 010000001001 31: 001110110010 678.375