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='(? \d+ ) \s (? .+) - (?<artist> .+ ) \.mp3' *
# Note: patterns have /xmsi modifier applied to them

my @option_specs = (
    <a href="http://search.cpan.org/perldoc?Getopt::Lucid::Switch" title="CPAN Getopt::Lucid::Switch" 
target="_blank">Getopt::Lucid::Switch</a>('verbose|v'),
    <a href="http://search.cpan.org/perldoc?Getopt::Lucid::Switch" title="CPAN Getopt::Lucid::Switch" 
target="_blank">Getopt::Lucid::Switch</a>('dryrun|dry-run|d'),
    <a href="http://search.cpan.org/perldoc?Getopt::Lucid::Param" title="CPAN Getopt::Lucid::Param" 
target="_blank">Getopt::Lucid::Param</a>('pattern|p')->required(),
);
my $opt = <a href="http://search.cpan.org/perldoc?Getopt::Lucid" title="CPAN Getopt::Lucid" 
target="_blank">Getopt::Lucid</a>->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 = <a href="http://search.cpan.org/perldoc?File::Basename::basename" title="CPAN File::Basename::basename" 
target="_blank">File::Basename::basename</a>($file);
    
    if ($filename =~ $pattern) {
        say "[$+{track}] $+{title} by $+{artist} ($+{comment})";

        next if ($opt->{options}{dryrun});

        my $tags = <a href="http://search.cpan.org/perldoc?Music::Tag" title="CPAN Music::Tag" 
target="_blank">Music::Tag</a>->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";
    }
}
</pre>
<p>Hope this might be useful to someone :)</p>
	</div><!-- .entry-content -->
</article><!-- #post-403 -->

<div id="comments" class="comments-area">

	
		<div id="respond" class="comment-respond">
		<h3 id="reply-title" class="comment-reply-title">Leave a Reply</h3><form action="https://www.preshweb.co.uk/wp-comments-post.php" method="post" id="commentform" class="comment-form" novalidate><p class="comment-notes"><span id="email-notes">Your email address will not be published.</span> <span class="required-field-message">Required fields are marked <span class="required">*</span></span></p><p class="comment-form-comment"><label for="comment">Comment <span class="required">*</span></label> <textarea id="comment" name="comment" cols="45" rows="8" maxlength="65525" required></textarea></p><p class="comment-form-author"><label for="author">Name <span class="required">*</span></label> <input id="author" name="author" type="text" value="" size="30" maxlength="245" autocomplete="name" required /></p>
<p class="comment-form-email"><label for="email">Email <span class="required">*</span></label> <input id="email" name="email" type="email" value="" size="30" maxlength="100" aria-describedby="email-notes" autocomplete="email" required /></p>
<p class="comment-form-url"><label for="url">Website</label> <input id="url" name="url" type="url" value="" size="30" maxlength="200" autocomplete="url" /></p>
<p class="comment-form-cookies-consent"><input id="wp-comment-cookies-consent" name="wp-comment-cookies-consent" type="checkbox" value="yes" /> <label for="wp-comment-cookies-consent">Save my name, email, and website in this browser for the next time I comment.</label></p>
<p class="form-submit"><input name="submit" type="submit" id="submit" class="submit" value="Post Comment" /> <input type='hidden' name='comment_post_ID' value='403' id='comment_post_ID' />
<input type='hidden' name='comment_parent' id='comment_parent' value='0' />
</p><p style="display: none;"><input type="hidden" id="akismet_comment_nonce" name="akismet_comment_nonce" value="99fb1b8279" /></p>	<script type='text/javascript'>
	<!--
	ref = escape( document[ 'referrer' ] );
	document.write("<input type='hidden' name='ref' value='"+ref+"'>");
	// -->
	</script>
	<p style="display: none !important;"><label>Δ<textarea name="ak_hp_textarea" cols="45" rows="8" maxlength="100"></textarea></label><input type="hidden" id="ak_js_1" name="ak_js" value="63"/><script>document.getElementById( "ak_js_1" ).setAttribute( "value", ( new Date() ).getTime() );</script></p></form>	</div><!-- #respond -->
	
</div><!-- #comments -->

		</div><!-- #content -->
	</div><!-- #primary -->
	</div><!-- #main-content -->

<div id="secondary">
		<h2 class="site-description">David Precious – professional Perl developer, motorcyclist and beer drinker</h2>
	
	
		<div id="primary-sidebar" class="primary-sidebar widget-area" role="complementary">
		<aside id="search-2" class="widget widget_search"><form role="search" method="get" class="search-form" action="https://www.preshweb.co.uk/">
				<label>
					<span class="screen-reader-text">Search for:</span>
					<input type="search" class="search-field" placeholder="Search …" value="" name="s" />
				</label>
				<input type="submit" class="search-submit" value="Search" />
			</form></aside>
		<aside id="recent-posts-2" class="widget widget_recent_entries">
		<h1 class="widget-title">Recent Posts</h1><nav aria-label="Recent Posts">
		<ul>
											<li>
					<a href="https://www.preshweb.co.uk/2023/09/catalyst-auto-html-xss-scrubbing/">Catalyst auto HTML/XSS scrubbing</a>
									</li>
											<li>
					<a href="https://www.preshweb.co.uk/2014/01/dbi-reading-mysql-connection-details-from-my-cnf/">DBI reading MySQL connection details from .my.cnf</a>
									</li>
											<li>
					<a href="https://www.preshweb.co.uk/2013/10/how-to-lose-a-customer-asda/">How to lose a customer, ASDA</a>
									</li>
											<li>
					<a href="https://www.preshweb.co.uk/2013/03/using-ssl-client-certs-with-perls-lwpuseragent/">Using SSL client certs with Perl’s LWP::UserAgent</a>
									</li>
											<li>
					<a href="https://www.preshweb.co.uk/2012/12/vodafone-why-you-no-activate-new-contract-sim/">Vodafone, why you no activate new contract SIM?</a>
									</li>
											<li>
					<a href="https://www.preshweb.co.uk/2012/10/we-shouldnt-arrest-criminals-anymore/">We shouldn’t arrest criminals anymore?</a>
									</li>
											<li>
					<a href="https://www.preshweb.co.uk/2012/09/samsung-exploit-auto-dial-wipe-code-frame/">Testing Samsung auto-dial shortcode exploit on Galaxy Note</a>
									</li>
											<li>
					<a href="https://www.preshweb.co.uk/2012/09/extract-part-of-a-subversion-repo-into-a-git-repo/">Extract part of a Subversion repo into a Git repo</a>
									</li>
											<li>
					<a href="https://www.preshweb.co.uk/2012/08/olympic-goatse-ftw/">Olympic Goatse ftw.</a>
									</li>
											<li>
					<a href="https://www.preshweb.co.uk/2012/06/us-wants-to-extradite-uk-student-on-bogus-claims/">US wants to extradite UK student Richard O’Dwyer on bogus claims</a>
									</li>
											<li>
					<a href="https://www.preshweb.co.uk/2012/06/charles-carreon-sues-charities-i-think-hes-a-dick/">Charles Carreon sues charities.  I think he’s a dick.</a>
									</li>
											<li>
					<a href="https://www.preshweb.co.uk/2012/01/dancer-talk-at-yapcna-2012/">Dancer talk at YAPC::NA 2012</a>
									</li>
											<li>
					<a href="https://www.preshweb.co.uk/2012/01/vlc-getting-proxy-settings-via-gconf/">VLC getting proxy settings via gconf</a>
									</li>
											<li>
					<a href="https://www.preshweb.co.uk/2011/12/perl-advent-calendars-for-2011/">Perl Advent Calendars for 2011</a>
									</li>
											<li>
					<a href="https://www.preshweb.co.uk/2011/11/how-on-earth-do-i-contact-adsense-support/">How on Earth do I contact Adsense support?</a>
									</li>
					</ul>

		</nav></aside><aside id="recent-comments-2" class="widget widget_recent_comments"><h1 class="widget-title">Recent Comments</h1><nav aria-label="Recent Comments"><ul id="recentcomments"><li class="recentcomments"><span class="comment-author-link">lucky</span> on <a href="https://www.preshweb.co.uk/2011/09/solving-trouble-codes-p0480-p0482-on-vectra-z22se/comment-page-1/#comment-469132">Solving trouble codes P0480 / P0482 on Vectra Z22SE</a></li><li class="recentcomments"><span class="comment-author-link">David Lee Crites</span> on <a href="https://www.preshweb.co.uk/2011/06/dancerpluginsimplecrud-0-10-released/comment-page-1/#comment-468738">Dancer::Plugin::SimpleCRUD 0.10 released</a></li><li class="recentcomments"><span class="comment-author-link"><a href="http://www.icounsellor.co.uk/" class="url" rel="ugc external nofollow">Dean Richardson</a></span> on <a href="https://www.preshweb.co.uk/2007/07/49mb-mailbox-size-limit-postfix-procmail/comment-page-1/#comment-310279">49MB mailbox size limit (Postfix & procmail)</a></li><li class="recentcomments"><span class="comment-author-link"><a href="http://www.preshweb.co.uk/" class="url" rel="ugc">bigpresh</a></span> on <a href="https://www.preshweb.co.uk/2009/09/suspicious-vodafone-bonus-text-message/comment-page-1/#comment-305143">Suspicious Vodafone bonus text message</a></li><li class="recentcomments"><span class="comment-author-link"><a href="http://www.ameldesigns.com" class="url" rel="ugc external nofollow">Amel Ryan</a></span> on <a href="https://www.preshweb.co.uk/2012/06/charles-carreon-sues-charities-i-think-hes-a-dick/comment-page-1/#comment-305070">Charles Carreon sues charities.  I think he’s a dick.</a></li><li class="recentcomments"><span class="comment-author-link"><a href="http://apnaindia.com/entertainment/" class="url" rel="ugc external nofollow">shiny</a></span> on <a href="https://www.preshweb.co.uk/2013/10/how-to-lose-a-customer-asda/comment-page-1/#comment-303538">How to lose a customer, ASDA</a></li></ul></nav></aside><aside id="categories-75924251" class="widget widget_categories"><h1 class="widget-title">Categories</h1><nav aria-label="Categories">
			<ul>
					<li class="cat-item cat-item-72"><a href="https://www.preshweb.co.uk/category/non-geeky/gaming/bfbc2/">Battlefield – Bad Company 2</a> (1)
</li>
	<li class="cat-item cat-item-77"><a href="https://www.preshweb.co.uk/category/non-geeky/gaming/battlefield-3/">Battlefield 3</a> (2)
</li>
	<li class="cat-item cat-item-76"><a href="https://www.preshweb.co.uk/category/non-geeky/cycling/">Cycling</a> (1)
</li>
	<li class="cat-item cat-item-65"><a href="https://www.preshweb.co.uk/category/geeky/perl/dancer-perl/">Dancer web framework</a> (18)
</li>
	<li class="cat-item cat-item-27"><a href="https://www.preshweb.co.uk/category/non-geeky/gaming/">Gaming</a> (13)
</li>
	<li class="cat-item cat-item-70"><a href="https://www.preshweb.co.uk/category/geeky/">Geeky</a> (11)
</li>
	<li class="cat-item cat-item-16"><a href="https://www.preshweb.co.uk/category/random/humour/">Humour</a> (69)
</li>
	<li class="cat-item cat-item-14"><a href="https://www.preshweb.co.uk/category/geeky/security/">Information Security</a> (7)
</li>
	<li class="cat-item cat-item-6"><a href="https://www.preshweb.co.uk/category/geeky/linux/">Linux</a> (46)
</li>
	<li class="cat-item cat-item-13"><a href="https://www.preshweb.co.uk/category/non-geeky/motorcycles/">Motorcycles</a> (12)
</li>
	<li class="cat-item cat-item-75"><a href="https://www.preshweb.co.uk/category/non-geeky/">Non-geeky</a> (16)
</li>
	<li class="cat-item cat-item-10"><a href="https://www.preshweb.co.uk/category/geeky/perl/">Perl</a> (63)
</li>
	<li class="cat-item cat-item-23"><a href="https://www.preshweb.co.uk/category/non-geeky/personal/">Personal</a> (12)
</li>
	<li class="cat-item cat-item-71"><a href="https://www.preshweb.co.uk/category/non-geeky/photography-non-geeky/">Photography</a> (1)
</li>
	<li class="cat-item cat-item-9"><a href="https://www.preshweb.co.uk/category/geeky/programming/">Programming</a> (52)
</li>
	<li class="cat-item cat-item-73"><a href="https://www.preshweb.co.uk/category/random/">Random</a> (3)
</li>
	<li class="cat-item cat-item-20"><a href="https://www.preshweb.co.uk/category/reviews/">reviews</a> (14)
</li>
	<li class="cat-item cat-item-5"><a href="https://www.preshweb.co.uk/category/geeky/sysadmin/">System Administration</a> (48)
</li>
	<li class="cat-item cat-item-1"><a href="https://www.preshweb.co.uk/category/uncategorized/">Uncategorized</a> (79)
</li>
	<li class="cat-item cat-item-74"><a href="https://www.preshweb.co.uk/category/random/video-random/">Videos</a> (7)
</li>
	<li class="cat-item cat-item-3"><a href="https://www.preshweb.co.uk/category/rant/">Wibbles</a> (43)
</li>
	<li class="cat-item cat-item-25"><a href="https://www.preshweb.co.uk/category/random/humour/wtf/">WTF</a> (22)
</li>
			</ul>

			</nav></aside><aside id="pages-2" class="widget widget_pages"><h1 class="widget-title">Pages</h1><nav aria-label="Pages">
			<ul>
				<li class="page_item page-item-2 page_item_has_children"><a href="https://www.preshweb.co.uk/about/">About</a>
<ul class='children'>
	<li class="page_item page-item-491"><a href="https://www.preshweb.co.uk/about/public-key/">Public key</a></li>
</ul>
</li>
<li class="page_item page-item-174"><a href="https://www.preshweb.co.uk/k2archives/">Archives</a></li>
<li class="page_item page-item-151 page_item_has_children current_page_ancestor current_page_parent"><a href="https://www.preshweb.co.uk/coding/">Coding</a>
<ul class='children'>
	<li class="page_item page-item-403 current_page_item"><a href="https://www.preshweb.co.uk/coding/retag-by-filenamepl/" aria-current="page">retag-by-filename.pl</a></li>
</ul>
</li>
<li class="page_item page-item-105"><a href="https://www.preshweb.co.uk/reading-list/">Reading list</a></li>
<li class="page_item page-item-196"><a href="https://www.preshweb.co.uk/subscribe/">Subscribe</a></li>
			</ul>

			</nav></aside><aside id="linkcat-2" class="widget widget_links"><h1 class="widget-title">Blogroll</h1>
	<ul class='xoxo blogroll'>
<li><a href="http://ocaoimh.ie/">Donncha O Caoimh</a></li>
<li><a href="http://www.ronanweb.co.uk/" rel="friend met co-worker colleague" title="James Ronan">James Ronan</a></li>
<li><a href="http://londonfashionvictim.co.uk/" title="See Shoreditch twats in their natural habitat">London Fashion Victims</a></li>
<li><a href="http://blog.dixo.net/" rel="friend met">Paul Dixon</a></li>
<li><a href="http://www.perl-tutorial.org/" title="Perl tutorial collection for those looking to learn Perl">Perl Tutorial hub</a></li>
<li><a href="http://skington.livejournal.com/" rel="friend met co-worker colleague">Sam Kington</a></li>
<li><a href="http://www.bagofspoons.net/" rel="friend met">Steve Clark</a></li>

	</ul>
</aside>
	</div><!-- #primary-sidebar -->
	</div><!-- #secondary -->

		</div><!-- #main -->

		<footer id="colophon" class="site-footer">

			
			<div class="site-info">
												<a href="https://wordpress.org/" class="imprint">
					Proudly powered by WordPress				</a>
			</div><!-- .site-info -->
		</footer><!-- #colophon -->
	</div><!-- #page -->

	
<!-- Increase Socialability 1.3 - http://www.preblogging.com/increase-sociability/ -->
 <script defer src='https://www.preshweb.co.uk/wp-content/plugins/akismet/_inc/akismet-frontend.js?ver=1695071631' id='akismet-frontend-js'></script>
</body>
</html>