PreshBlog

retag-by-filename.pl

Just a quick script I whipped up to retag audio files based on their filenames, using a user-supplied regular expression.

Man, I love Perl 5.10.0’s new toys; the named regex captures in particular made this possible, otherwise you’d have to give the script a regex and a list of the order in which the track, title, artist + comment appear, or something cumbersome.

The script is used by passing it a list of files to operate on, and a pattern to match the filenames against.

For example:

./retag-by-filename.pl \
    --pattern='^ (?<track> \d+) \.\s (?<title> .+?) (?: \s +\[ (?<comment> .+ ) \])? \
    \s-\s (?<artist> .+) \.mp3' *.mp3

Yes, the regex gets a bit hairy (especcially thanks to the comment part, which optionally appeared within square brackets after the title).

The filename pattern this matches is, for example:

01. Track Title - Artist.mp3
02. Another Title [A comment] - Some Other Artist.mp3

To ensure you’ve got your pattern right, you can use the --dry-run option, which will cause the script to parse filenames and show what it parsed them to, but not actually attempt to retag the files.

The script is all of about 50 lines:

#!/usr/bin/perl
 
# $Id: retag-by-filename.pl 516 2009-01-31 21:03:53Z davidp $
 
use strict;
use 5.010;
use Music::Tag;
use Getopt::Lucid;
use File::Basename;
 
# Given a regex (with named captures) and a list of files, retag the files
# based on the pattern.
# Example:
# --pattern='(?<track> \d+ ) \s (?<title> .+) - (?<artist> .+ ) \.mp3' *
# Note: patterns have /xmsi modifier applied to them
 
my @option_specs = (
    Getopt::Lucid::Switch('verbose|v'),
    Getopt::Lucid::Switch('dryrun|dry-run|d'),
    Getopt::Lucid::Param('pattern|p')->required(),
);
my $opt = Getopt::Lucid->getopt(\@option_specs);
 
my $pattern = qr/$opt->{options}{pattern}/xmsi
    or die "Failed to parse pattern";
 
# Now, look for files in the directory, and see what to retag them to:
for my $file (@{ $opt->{target} }) {
    my $filename = File::Basename::basename($file);
 
    if ($filename =~ $pattern) {
        say "[$+{track}] $+{title} by $+{artist} ($+{comment})";
 
        next if ($opt->{options}{dryrun});
 
        my $tags = Music::Tag->new($file,
            { quiet => ! $opt->{options}{verbose} });
        $tags->get_tag or warn "Failed to read tags for $filename" && next;
        for my $tag(qw(track title artist comment)) {
            say "Setting $tag" if $opt->{options}{verbose};
            $tags->$tag( $+{$tag} || '');
        }
        $tags->set_tag or warn "Failed to set tags for $filename" && next;
        $tags->close;
 
    } else {
        warn "$file does not match $pattern";
    }
}

Hope this might be useful to someone :)


Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!