iTunes – Fixing ID3 tags in MP3’s – Take two

Written by max on 2009-05-03

Overview

In these last three posts [1] [2] [3] I tackled fixing my existing MP3’s to make them more iTunes friendly. I did it with three Perl scripts using two different ID3 libraries : Audio::TabLib which is a Perl wrapper to KDE’s TagLib, and MP3::Tag.

I’ve since given up on MP3::Tag. Although updated recently, it still does not support ID3v2 2.4, which, unfortunately is the default in iTunes versions 7 and 8. So I’ve combined all three scripts into one script that only uses Audio::TagLib. One bonus of this, is that TagLib is a fast C++ library, and so the script runs a lot faster than a Pure Perl based method. One batch run on 160GB of MP3’s only took a couple minutes.

The Script

The following script does all three of the functions from my last posts :

  1. Add Folder.jpg (or another image) to the MP3 for Cover Art
  2. Convert version 1 tags (ID3v1) to version 2 (ID3v2)
  3. Set the iTunes Compilation Flag (ICMP)

You can download the script here : http://warped.org/linux/mp3_fix_tag or copy paste from below :

#!/usr/bin/perl -w
# mp3_tag_fix
# Max Baker max@warped.org
# 5/3/09
 
$VERSION=1.1;
 
use File::Glob qw(:globally :glob);
use Audio::TagLib;
use Getopt::Long;
GetOptions (\%Args, "h|help", "f|front=s", 'b|back=s',
            'c|covers|addcovers','v1tov2', 'comp|compilation');
 
$Cover_Front = $Args{f} || "Folder.jpg";
$Cover_Back  = $Args{b} || "Folder_back.jpg";
 
die &usage if (! scalar @ARGV or $Args{h});
die &usage unless ($Args{c} || $Args{v1tov2} || $Args{comp});
 
foreach my $f (@ARGV) {
    if (-d $f) {
        recurse_dir($f);
        next;
    }
    go($f);
}
 
exit;
 
sub go {
    my $f = shift;
 
    # Only work on files that end in .mp3
    return if $f eq '.';
    return if $f eq '..';
    return unless -r $f and $f =~ /\.mp3$/i;
 
    print "  $f\n";
 
    my $mp3 = Audio::TagLib::MPEG::File->new($f) or die;
    my $id3v1 = $mp3->ID3v1Tag(1);
    my $id3v2 = $mp3->ID3v2Tag(1);
 
    # Cover Art
    if ($Args{c}) {
        #print "    --> Adding Cover Art\n";
        add_image($id3v2,$f,"$root/$Cover_Front","FrontCover","Cover (front)") if -r "$root/$Cover_Front";
        add_image($id3v2,$f,"$root/$Cover_Back", "BackCover", "Cover (back)")  if -r "$root/$Cover_Back";
    }
 
    # Copy v1 tags to v2
    if ($Args{v1tov2}) {
        print "    --> Copying ID3v1 data to ID3v2\n";
        $id3v2->setArtist(Audio::TagLib::String->new($id3v1->artist)) if nb($id3v1->artist);
        $id3v2->setAlbum(Audio::TagLib::String->new($id3v1->album))  if nb($id3v1->album);
        $id3v2->setTitle(Audio::TagLib::String->new($id3v1->title))  if nb($id3v1->title);
        $id3v2->setYear($id3v1->year)                                if nb($id3v1->year);
        $id3v2->setTrack($id3v1->track)                              if nb($id3v1->track);
        $id3v2->setGenre(Audio::TagLib::String->new($id3v1->genre))  if nb($id3v1->genre);
    }
 
    # Add/Replace TCMP - Compilation Tag
    if ($Args{comp}) {
        print "    --> Setting Compilation Flag\n";
        my $tcmp = Audio::TagLib::ByteVector->new("TCMP");
        $id3v2->removeFrames($tcmp);
 
        #my $f = Audio::TagLib::ID3v2::TextIdentificationFrame->new($tcmp, "UTF8");
        my $f = Audio::TagLib::ID3v2::TextIdentificationFrame->new($tcmp);
        $f->setText(Audio::TagLib::String->new("1"));
        $id3v2->addFrame($f);
    }
 
    $mp3->save();
}
 
sub recurse_dir {
    my $root = shift;
 
    print "Entering $root\n";
 
    # bsd_glob handles spaces in file names/paths
    my @files = bsd_glob("$root/*",GLOB_QUOTE);
    foreach my $f (@files) {
        if (-d $f) {
            recurse_dir($f);
            next;
        }
        go($f);
    }
}
 
# not blank or undef
sub nb {
    my $string = shift;
    return 0 unless defined $string;
    return 0 if $string =~ /^\s*$/;
    return 1;
}
 
sub add_image {
    my ($id3v2,$f,$img,$type,$desc) = @_;
 
    print "    --> add_image($type) $img -> $f\n";
 
    open(PICFILE, "< $img") or die "Can't open image $img. $!\n";
 
    my $imgdata;
    my $filesize = -s PICFILE;
    binmode(PICFILE);
    read(PICFILE, $imgdata, $filesize);
    close(PICFILE);
 
    my $imgbv = Audio::TagLib::ByteVector->new();
    $imgbv->setData($imgdata,$filesize);
    my $bv = Audio::TagLib::ByteVector->new("APIC");
    my $field = Audio::TagLib::ID3v2::AttachedPictureFrame->new($bv, "UTF8");
    $field->setPicture($imgbv);
    $field->setTextEncoding("UTF8");
    $field->setMimeType(Audio::TagLib::String->new("image/jpeg"));
    $field->setType($type);
    $field->setDescription(Audio::TagLib::String->new($desc));
    $id3v2->addFrame($field);
}
 
sub usage {
    return < < "end_usage";
USAGE: $0 <dir> <cmd> [options]
 
mp3_tag_fix Version $VERSION
 
This script is used to fix up MP3 files for use in iTunes.
 
It can do the following things :
    * Recursively go through a directory and embed album art 
    * Copy ID3v1 tag data into ID3v2
 
<commands>
    -c | --addcovers - Embed $Cover_Front / $Cover_Back into the MP3
    --v1tov2         - Copy ID3v1 Tags into ID3v2 Tag
    --comp           - Set iTunes Compilation flag (TCMP)
 
[OPTIONS]
    -f - The name of the image to look for in each dir to embed front-covers
    -b - The name of the image to look for in each dir to embed back-covers
 
Max Baker max\@warped.org 5/3/2009
end_usage
}
</commands></cmd>

Prerequisites

See the Prerequisites section on this post.