#!/usr/local/bin/perl # # CheckFlacs v.2b (c) 2008 Martin Hassel, code(cat)lacasahassel(dog)net # cat is at and dog is dot ;-) # # Bug reports, fixes and extensions are most welcome through the adress above # # Browses recursively all (sub)folders starting with the one you run the script # from and (optionally) checks for various "errors", which currently are: # -m minimum size for cover art, size taken from a file 'cover.*' # The only option taking a value, in pixels, used for both width and height # -c lack of embedded cover art in flac files # -r lack of replay gain tags in flac files # -l lack of Exact Audio Copy generated '*.log' file # -e flac files ripped with errors, status taken from EAC '*.log' # -i output meta info for each flac file # -t output tag fields for each flac file # -a always output flac folder name even if there are no errors # -s write (my)sql statement data to file checkflacs.sql # # todo? - report on: # -p suspicious positions (with rip errors), status taken from EAC '*.log' # -v flac files not verified by AcurateRip, status taken from EAC '*.log' # -n flac files not in AcurateRip, status taken from EAC '*.log' # # This program is free software; you can redistribute it and/or modify it under # the terms of either the Artistic License (which comes with Perl 5) or the # GNU General Public License as published by the Free Software Foundation; # either version 2 of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # A full copy of the GNU General Public License can be retrieved from # http://www.gnu.org/copyleft/gpl.html if(!@ARGV) { print " CheckFlacs v.2a (c) 2008 Martin Hassel Browses recursively all (sub)folders starting with the one you run the script from and (optionally) checks for various \"errors\", which currently are: -m minimum size for cover art, size taken from a file 'cover.*' The only option taking a value, in pixels, used for both width and height -c lack of embedded cover art in flac files -r lack of replay gain tags in flac files -l lack of Exact Audio Copy generated '*.log' file -e flac files ripped with errors, status taken from EAC '*.log' -i output meta info for each flac file -t output tag fields for each flac file -a always output flac folder name even if there are no errors -s write (my)sql statement data to file checkflacs.sql "; exit; } # http://search.cpan.org/~daniel/Audio-FLAC-Header-2.3/ # or use the one distributed with SqueezeCenter by editing the path below BEGIN { push @INC,"/usr/share/squeezecenter/lib"; } use Audio::FLAC::Header; # http://search.cpan.org/~rjray/Image-Size-3.1.1/ use Image::Size; # http://search.cpan.org/~tels/Math-BigInt-1.89/ use Math::BigFloat; use Getopt::Std; getopts("m:crleitas"); $minsize = $opt_m?$opt_m:0; $checkembeddedcover = $opt_c?$opt_c:0; $checkreplaygain = $opt_r?$opt_r:0; $checkeaclog = $opt_l?$opt_l:0; $checkriperrors = $opt_e?$opt_e:0; $outputinfo = $opt_i?$opt_i:0; $outputtags = $opt_t?$opt_t:0; $allflacfolders = $opt_a?$opt_a:0; $writesql = $opt_s?$opt_s:0; # start in current directory &recursive("."); # optionally write (my)sql statement data to file if($writesql) { open SQL, ">checkflacs.sql"; print SQL "DROP TABLE IF EXISTS album; CREATE TABLE album ( albumid smallint(5) unsigned NOT NULL auto_increment, albumtitle varchar(50) NOT NULL, albumyear year(4) NOT NULL, albumartist smallint(5) unsigned NOT NULL, PRIMARY KEY (albumid) ); DROP TABLE IF EXISTS artist; CREATE TABLE artist ( artistid smallint(5) unsigned NOT NULL auto_increment, artistname varchar(50) NOT NULL, PRIMARY KEY (artistid) ); DROP TABLE IF EXISTS genre; CREATE TABLE genre ( genreid smallint(5) unsigned NOT NULL auto_increment, genreset varchar(50) NOT NULL, PRIMARY KEY (genreid) ); DROP TABLE IF EXISTS track; CREATE TABLE track ( albumid smallint(5) unsigned NOT NULL, tracknumber smallint(5) unsigned NOT NULL, tracktitle varchar(50) NOT NULL, trackartist smallint(5) unsigned NOT NULL, genreid smallint(5) unsigned NOT NULL, PRIMARY KEY (albumid,tracknumber) ); \n"; foreach $artistname (sort { $artists{$a} <=> $artists{$b} } keys %artists) { $artistid = $artists{$artistname}; print SQL "INSERT IGNORE artist (artistid,artistname) VALUES ($artistid,'".quotemeta(fixoddchars($artistname))."');\n"; } print SQL "\n"; foreach $key (sort { $albums{$a} <=> $albums{$b} } keys %albums) { $albumid = $albums{$key}; ($albumtitle,$albumartist,$albumyear) = split(/\|/,$key); print SQL "INSERT IGNORE album (albumid,albumtitle,albumyear,albumartist) VALUES ($albumid,'". quotemeta(fixoddchars($albumtitle))."',$albumyear,$albumartist);\n"; } print SQL "\n"; foreach $genreset (sort { $genres{$a} <=> $genres{$b} } keys %genres) { $genreid = $genres{$genreset}; print SQL "INSERT IGNORE genre (genreid,genreset) VALUES ($genreid,'".quotemeta(fixoddchars($genreset))."');\n"; } print SQL "\n"; while( ($key,$value) = each(%tracks) ) { ($albumid,$tracknumber) = split(/\|/,$key); ($tracktitle,$trackartist,$genreid) = split(/\|/,$value); print SQL "INSERT IGNORE track (albumid,tracknumber,tracktitle,trackartist,genreid) VALUES ($albumid,$tracknumber,'". quotemeta(fixoddchars($tracktitle))."',$trackartist,$genreid);\n"; } close SQL; } # output final statistics if($checkriperrors) { Math::BigFloat->accuracy(2); print "\n".(Math::BigFloat->new($notracksfailed/$notracks)*100). "% failed tracks ($notracksfailed of $notracks; based on EAC log files)\n"; } # recursively traverse a directory tree sub recursive { local($dir) = @_; &searchdir($dir); foreach $dir (<*>) { if(-d $dir) { chdir $dir; &recursive($dir); chdir ".."; chop($reldir); while($reldir =~ /[^\/]$/) { $reldir =~ s/[^\/]$//; } } } } # process current directory sub searchdir { local($dir)=@_; $reldir .= "$dir/"; while($reldir =~ /^[.\/]/) { $reldir =~ s/^[.\/]//; } opendir (DIR,".") || die ("Can't open "."\n"); @files = readdir DIR; close DIR; $dirstat = "$reldir\n"; $replaygain = $flacfound = 0; $embeddedcover = $coverfound = 0; $tracksfailed = $eaclogfound = 0; foreach $file (sort @files) { unless(-d $file or $file =~ /.pl$/i) { # check for EAC generated log file if($file =~ /.log$/i) { if($checkeaclog) { $eaclogfound = 1; } open(LOG,$file) or die "Cannot open EAC log file for reading: $!"; $eaclogfile = join("",); close(LOG); @tracks = split(/\nTrack/,$eaclogfile); shift(@tracks); foreach $track (@tracks) { # check for tracks with ripping errors if($track =~ /Copy finished/ and $checkriperrors) { $track =~ /Filename.*(\d{2,} - .*?)wav/gsi; $failedtracks .= " ".$1."flac\n"; $tracksfailed = 1; $notracksfailed++; } } } # check for undersized cover art (as given by $minsize) if($file =~ /^cover/i and $minsize) { $coverfound = 1; ($width, $heigth) = imgsize($file); if($width<$minsize or $heigth<$minsize) { $dirstat .= "---> '$file' undersized (w$width,h$heigth)\n"; } } elsif($file =~ /.flac$/i) { $flacfound = 1; $notracks++; my $flac = Audio::FLAC::Header->new($file); # check for embedded cover art if($flac->picture() and $checkembeddedcover) { $embeddedcover = 1; } # check for replaygain tags if(( ($flac->tags("replaygain_album_gain") and $flac->tags("replaygain_track_gain")) or ($flac->tags("REPLAYGAIN_ALBUM_GAIN") and $flac->tags("REPLAYGAIN_TRACK_GAIN")) ) and $checkreplaygain) { $replaygain = 1; } # output info fields for current flac file if($outputinfo) { $dirstat .= "--- info for: $file\n"; my $flacInfo = $flac->info(); foreach (keys %$flacInfo) { $dirstat .= "$_: $flacInfo->{$_}\n"; } } # output tag fields for current flac file if($outputtags) { $dirstat .= "--- tags for: $file\n"; my $flacTags = $flac->tags(); foreach (sort keys %$flacTags) { $dirstat .= "$_: $flacTags->{$_}\n"; } } # collect sql data if($writesql) { if(!$artists{$flac->tags("ARTIST")}) { $artists{$flac->tags("ARTIST")} = ++$artistid; } $albumartist = ($flac->tags("ALBUM ARTIST")?$flac->tags("ALBUM ARTIST"):"null"); if(!$artists{$albumartist}) { $artists{$albumartist} = ++$artistid; } if(!$albums{$flac->tags("ALBUM")."|".$artists{$albumartist}."|".$flac->tags("DATE")}) { $albums{$flac->tags("ALBUM")."|".$artists{$albumartist}."|".$flac->tags("DATE")} = ++$albumid; } if(!$genres{$flac->tags("GENRE")}) { $genres{$flac->tags("GENRE")} = ++$genreid; } $tracks{$albumid."|".$flac->tags("TRACKNUMBER")} = $flac->tags("TITLE")."|".$artists{$flac->tags("ARTIST")}."|".$genres{$flac->tags("GENRE")}; } } } } if($minsize and $flacfound and not $coverfound) { $dirstat .= "---> Missing cover art file\n"; } if($checkembeddedcover and $flacfound and not $embeddedcover) { $dirstat .= "---> Flac file(s) lack embedded cover art\n"; } if($checkreplaygain and $flacfound and not $replaygain) { $dirstat .= "---> Flac file(s) lack replaygain info\n"; } if($checkeaclog and $flacfound and not $eaclogfound) { $dirstat .= "---> Missing Exact Audio Copy log file\n"; } if($checkriperrors and $flacfound and $tracksfailed) { $dirstat .= "---> Failed track(s):\n$failedtracks"; $failedtracks = ""; } # output (error) report for current flac folder if($flacfound and ($dirstat ne "$reldir\n" or $allflacfolders)) { print "$dirstat"; } } # More to follow... sub fixoddchars { $string = shift; $string =~ s/Ã¥/å/gsi; $string =~ s/Ã…/Å/gsi; $string =~ s/ä/ä/gsi; # $string =~ s//Ä/gsi; $string =~ s/ö/ö/gsi; # $string =~ s//Ö/gsi; $string =~ s/á/á/gsi; $string =~ s/¡/·/gsi; return $string; }