Perl rocks

I’m doing a bit of tidying-up of my online music library for consistency..  editing tags, renaming files, that kind of thing.  My library consists mainly of FLAC files ripped from my CD collection.  My music player of choice on my Ubuntu box is Banshee.  Banshee has an “Edit Metadata” feature which looks very handy on the surface, but it appears to have a bug where it doesn’t actually save the metadata edits back to the file.  It does, however, update the metadata in Banshee’s internal database, so in Banshee, it appears that the changes have “taken”, but when I play the music files elsewhere it becomes apparent that the changes haven’t been saved out to the files.  Of course, I didn’t discover this problem until I had made edits to 250 files or so.  Nothing against Banshee here of course..  it’s free and no warranty was ever implied or expected.  But, I did have some files to fix.

Fortunately, as I mentioned earlier, the edits I made were saved in Banshee’s internal SQLite database.  So all I really needed to do was whip something up to compare the database with the actual tags in the files.  First, I dumped Banshee’s SQLite database to a flat file:

sqlite3 banshee.db 'select * from Tracks'

Then I wrote a quick Perl script that extracted the FLAC tags from each of the files in the database dump and compared them to the corresponding fields in the SQLite table:

#!/usr/bin/perl

use URI::Escape;
use Audio::FLAC::Header;

print "Key\tPath\tFLAC tag\tDB tag\n";
while (<>) {
    chop;
    my %dbTags = ();
    my($path);
    ($path, $dbTags{ARTIST}, $dbTags{ALBUM}, $dbTags{TITLE},
     $dbTags{GENRE}, $dbTags{DATE}) =
        (split(/\|/))[1, 3, 5, 9, 10, 11];
    next unless ($path =~ /^file:\/\//);
    next unless ($path =~ /\.flac$/);
    next if ($path =~ /\/untagged\//);

    $path =~ s/^file:\/\///;
    $path = uri_unescape($path);
    if (! -f $path) {
        print STDERR "Can't find $path\n";
        next;
    }

    my $flac = Audio::FLAC::Header->new($path);
    my $tags = $flac->tags();

    # Strip extra date info..
    $tags->{DATE} = substr($tags->{DATE}, 0, 4);

    for (keys %dbTags) {
        if ($tags->{$_} ne $dbTags{$_}) {
            print "$_\t$path\t$tags->{$_}\t$dbTags{$_}\n";
        }
    }
}

exit 0;

This script outputs a tab-separated file that shows all of the discrepancies, suitable for loading into your spreadsheet of choice.  I only had to make a small number of edits, so I made the changes manually with EasyTag.  But if I had wanted to take this farther, I could have had the Audio::FLAC::Header module actually save the corrections to the files.

Yet another reason to love Perl.