Mass tagging, mass renaming
The main functionality of autotag.pl is to identify MP3 files. In the
course of that process, however, minor adjustments often need to be made
to large groups of files. Enter the Four Autotagging Horsemen.
Stripping comments is a very simple process.
I get a hash tag with get_tag()
, empty the
COMM and WXXX fields, and write it back with set_tag()
. In fact, comment
stripping could have been done through mass tagging, but it's used so
often that I felt I needed a separate option for it.
Guessing track numbers is also quite simple. Get the hash tag, use
guess_track_number()
on the file and the hash tag, ask for confirmation,
and write the tag back to the file.
Mass tagging operates on multiple keys (e.g. TALB) on a series of
files. You say, for instance,
autotag.pl -mt "TALB=Best" *.mp3
and all the files that have the mp3 extension will be assigned that
TALB value in their ID3v2 tag. Mass-tagging is very nice when, for
example, you have a directory full of music by an artist and want to tag
all that music with the artist's name. Only supported tag elements can be
mass-tagged. Again, I get the hash tag, make my changes, and write it
back. The goal is to make it simple and easy to maintain.
Listing 9. Mass tagging, comment stripping, and guessing track numbers
# {{{ handle the one-shot options
if ($config->GUESS_TRACK_NUMBERS_ONLY() ||
$config->STRIP_COMMENT_ONLY() ||
scalar keys %{$config->MASS_TAG_ONLY()})
{
foreach my $file (@ARGV)
{
my $tag = get_tag($file, 1);
unless (defined $tag)
{
warn "No ID3 TAG info in '$file', skipping";
next;
}
next if $config->DRYRUN();
# delegate stripping comments to the mass tagging function
if ($config->STRIP_COMMENT_ONLY())
{
$config->MASS_TAG_ONLY()->{COMM} = '';
$config->MASS_TAG_ONLY()->{WXXX} = '';
}
if (scalar keys %{$config->MASS_TAG_ONLY()})
{
foreach (keys %{$config->MASS_TAG_ONLY()})
{
unless (exists $supported_frames{$_})
{
warn "Unsupported tag element $_ requested for mass tagging, skipping";
next;
}
$tag->{$_} = $config->MASS_TAG_ONLY()->{$_};
}
set_tag($file, $tag);
}
else
{
my $track_number_guess = guess_track_number($file, $tag);
next if $config->DRYRUN();
if (defined $track_number_guess &&
read_yes_no("Is track number $track_number_guess OK for '$file'?", 1))
{
$tag->{TRCK} = $track_number_guess;
set_tag ($file, $tag);
}
else
{
warn "Could not guess a track number for file $file, sorry";
}
}
}
exit 0;
}
# }}}
|
Ah, the mass renaming option. I left it for last because it's the
most complex one. For each renaming parameter, I make each "%" in the tag
value appear as "{{{%}}}" because otherwise, those "%" characters, when
followed by one of the special renaming parameters, could be
misinterpreted. Take "100%true" for instance, for the track name, and
see how it would become "100%TRACKNAMErue" instead, where TRACKNAME is the
track name I get from the hash tag.
Mass renaming also eliminates bad characters, and replaces certain
characters with "_" to ensure a reasonable file name. Finally, unless the
-c
(accept_all
) option is given from the command line, autotag.pl will ask
if it's okay to rename the file.
Listing 10. Mass renaming
# {{{ handle the -rename_only option
if ($config->RENAME_ONLY())
{
foreach my $file (@ARGV)
{
my $tag = get_tag($file, 1);
# the extra parameter will ask us about upgrading V1 to V2
unless (defined $tag)
{
warn "No ID3 TAG info in '$file', skipping";
next;
}
my %map = (
'%c' => 'COMM',
'%s' => 'TIT2',
'%a' => 'TPE1',
'%t' => 'TALB',
'%n' => 'TRCK',
);
my $name = $config->RENAME_FORMAT();
foreach my $key (keys %map)
{
my $tagkey = $map{$key};
my $replacement = '';
if (exists $tag->{$tagkey})
{
$replacement = substr $tag->{$tagkey}, 0, $config->RENAME_MAX_CHARS();
# limit to N characters
if ($tagkey eq 'TRCK' && $replacement =~ m/^\d$/)
{
$replacement = "0$replacement";
}
}
$replacement =~ s/%/{{{%}}}/g;
# this is how we preserve %a in the fields, for example
$name =~ s/$key/$replacement/;
}
$name =~ s/{{{%}}}/%/g; # turn the {{{%}}} back into % in the fields
print "The name after % expansion is $name\n" if $config->DEBUG();
foreach my $char (map { quotemeta } @{$config->RENAME_BADCHARS()})
{
$name =~ s/$char//g;
}
print "The name after character removals is $name\n" if $config->DEBUG();
my $newchar = quotemeta $config->RENAME_REPLACEMENT();
foreach my $char (map { quotemeta } @{$config->RENAME_REPLACECHARS()})
{
$name =~ s/$char/$newchar/eg;
}
print "The name after character replacements is $name\n" if $config->DEBUG();
if ($name eq $file)
{
# do nothing
print "Renaming $file is unnecessary, it already answers to our high standards\n"
if $config->DEBUG();
}
elsif (-e $name)
{
warn "Could not use name $name, it's already taken by an existing
file or directory $file";
}
elsif ($config->ACCEPT_ALL() || read_yes_no("Is name $name OK for '$file'?", 1))
{
next if $config->DRYRUN();
print "Renaming $file -> $name\n";
rename($file, $name);
}
else
{
# do nothing
}
}
exit 0;
}
# }}}
|