#!/usr/bin/perl use strict; use warnings; use MPEG::ID3v2Tag; use IO::Scalar; my $nmode=0; my $qmode=0; my $force=0; foreach my $file (@ARGV) { my $bytes; my @bytes; my $next="none"; if($file eq '-n') { $nmode=1; next; } if($file eq '-q') { $qmode=1; next; } if($file eq '-f') { $force=1; next; } next unless -s $file; open F, $file or die; my $first; my $count=0; for(;;) { my $start = tell F; read(F,$bytes,3) == 3 or die "read failed on $file: $!"; unless($bytes eq 'ID3') { seek 'F',-3,1; last; } read(F,$bytes,2) == 2 or die; @bytes = unpack 'C2', $bytes; my $version = join('.',2,@bytes); seek F,1,1 or die; # flags read(F,$bytes,4); my $off = 10; @bytes = unpack 'C4', $bytes; foreach my $i (0..3) { $off += $bytes[3-$i] * (1<<(7*$i)); } seek F,$start+$off,0 or die; read(F,$bytes,2)==2 or die; my @bytes = unpack 'C2',$bytes; $next = sprintf "Unknown %x %x",@bytes; $next = "MP3 Data" if($bytes[0] == 0xff && ($bytes[1]&0xf0) == 0xf0); $next = "ID3 Tag" if($bytes[0] == ord('I') && $bytes[1] == ord('D')); seek F,$start,0 or die; if(defined $first) { seek F, $off, 1 or die; } else { read(F,$first,$off)==$off or die; } print "$file: Found ID3v2 ($version) tag at $start ($off bytes, $next next)\n" unless($qmode); $count++; } my $garbage = !$force && ($next ne "MP3 Data"); if($garbage) { read(F,$bytes,4)==4 or die; if($bytes eq "\xd0\0\0\0") { # some weird LAME thing.. seems harmless $garbage = 0; seek F,-4,1 or die; } elsif($bytes eq 'RIFF') { print "RIFF!\n"; for my $off (54, 66, 68) { seek F,$off,1 or die; read(F,$bytes,2)==2 or die; my @bytes = unpack 'C2',$bytes; $garbage=!($bytes[0] == 0xff && ($bytes[1]&0xf0) == 0xf0); seek F,-$off-2,1 or die; last if(!$garbage); } } else { seek F,-4,1 or die; my ($c,$c2); while(($c = getc(F)) eq "\0") { }; if($c && ($c2=getc(F))) { my @bytes = unpack('C2',$c . $c2); $garbage=!($bytes[0] == 0xff && ($bytes[1]&0xf0) == 0xf0); seek F,-2,1 or die; } } } my $deunsync=0; if(defined $first) { my $fh = new IO::Scalar \$first; my $tag = MPEG::ID3v2Tag->parse($fh); $fh->close; if($tag && $tag->flag_unsynchronization) { $deunsync=1; unless($nmode) { my $orig = length $first; $tag->flag_unsynchronization(0); $first = $tag->as_string; my $now = length $first; print "De-unsynchronized tag... (was $orig bytes, now $now bytes)\n"; } } } if($nmode) { my $msg = ""; $msg .= " has no id3tag" if($count==0); $msg .= " has extra id3tags" if($count > 1); $msg .= " needs deunsync" if($deunsync); $msg .= " has garbage (won't fix)" if($msg && $garbage); $msg .= " has garbage (but fixed)" if(!$msg && $garbage); $msg ||= "OK"; $msg =~ s/^ //; print "$file: $msg\n"; next; } if(!$garbage && ($count > 1 || $deunsync)) { print "Fixing: $file\n"; open NEW, ">$file.new" or die; print NEW $first if(defined $first); while(read F,$bytes,4096) { print NEW $bytes; } close NEW or die; close F or die; rename "$file.new", $file or die; } }