Blog / Random thoughts and musings.

 

MP3 metadata tagging

I’ve been trying to work on my MP3 collection’s ID3 metadata, and in the past weeks have gone through close to a dozen different ID3 tagging utilities and libraries, before finally finding a solution I’m happy with, so I thought I’d summarize my findings here. Going into all my crazy requirements would take forever, but a few key ones were:

  1. Read and write ID3v2.4 tags (so that I could use the TSOP “sort-order” tag, causing “Sarah McLachlan” to sort into the M’s instead of the S’s iTunes)
  2. Read and write multiple TXXX “user-text” tags (which I use for things like creating playlists)
  3. Have a Linux command-line interface, or an API easy enough for me to make one
  4. Support Latin1 multi-byte encoding for funky European lettering (like Björk)

The underlying vision behind this is my non-standard workflow. I rip all my CDs into FLAC (Free Lossless Audio Codec) format using the Max CD ripper/encoder application for Mac OSX. These FLAC files are my masters, and once ripped, the CDs go into a storage cabinet and (hopefully) are never seen again. Once I’m happy with the metadata in the FLAC files, I kick off a script that converts them to MP3, and copies the metadata over (including some custom mappings).

After lots of trial and error, and finding utilities and libraries that were missing one feature or another, I finally settled on using TagLib via its Perl interface. Unfortunately, I couldn’t find any examples of code using this interface, so had to struggle through the terse docs and fall back on lots of experimentation. Below is some of the code I came up with… hopefully it’ll help someone else with this library some day…

Example 1: Printing all the ID3v2.4 tags in an MP3 file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/usr/bin/perl -w
 
#
# Just print out all the ID3v2.4 tags in the MP3 file
#
 
use Audio::TagLib;
use warnings;
use strict;
 
if ($#ARGV != 0) {
  print "Usage: mp3-tagslist.pl mp3file\n";
  exit;
}
 
my $mp3fn = $ARGV[0];
my $mp3 = Audio::TagLib::MPEG::File->new($mp3fn);
my $mp3tag = $mp3->ID3v2Tag(1);
my $list = $mp3tag->frameList();
 
my $iter = $list->begin();
for (my $i = 0; $i < $list->size(); $i++) {
  my $id = $iter->data()->frameID();
  my $iter2 = $id->begin();
  my $idstr = "";
  for (my $j = 0; $j < $id->size(); $j++) {
    $idstr .= $$iter2;
    $iter2++;
  }
  my $data = $iter->data()->toString()->toCString();
  print "$idstr = $data\n";
  $iter++;
}

Here’s what the output looks like:

<10 home->bin> id3v24view.pl 02.\ Busy\ Child.mp3
TIT2 = Busy Child
TPE1 = The Crystal Method
TALB = Vegas
COMM = Ripped with EAC 0.95 beta 2
TRCK = 2
TSOP = Crystal Method
TXXX = [MFPLAYLIST] Dance,Mike,NewWave,Popular,Techno,

Example 2: Converting Vorbis tags (the common ones, and a few custom ones) from FLAC files to the corresponding ID3v2.4 tags in MP3 files

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#!/usr/bin/perl -w
 
#
# Copy all our tags from a FLAC file to an MP3 file.
#
 
use Audio::TagLib;
use warnings;
use strict;
 
if ($#ARGV != 1) {
  print "Usage: flac2mp3-tags.pl flacfile mp3file\n";
  exit;
}
 
my $flacfn = $ARGV[0];
my $mp3fn = $ARGV[1];
 
my $flac = Audio::TagLib::FLAC::File->new($flacfn);
my $mp3 = Audio::TagLib::MPEG::File->new($mp3fn);
 
my $flactag = $flac->xiphComment(0);
$mp3->strip();
my $mp3tag = $mp3->ID3v2Tag(1);
my $mp3tag1 = $mp3->ID3v1Tag(1);
 
$mp3tag->setTitle($flactag->title());
$mp3tag->setArtist($flactag->artist());
$mp3tag->setAlbum($flactag->album());
$mp3tag->setComment($flactag->comment());
$mp3tag->setGenre($flactag->genre());
$mp3tag->setYear($flactag->year());
$mp3tag->setTrack($flactag->track());
 
$mp3tag1->setTitle($flactag->title());
$mp3tag1->setArtist($flactag->artist());
$mp3tag1->setAlbum($flactag->album());
$mp3tag1->setComment($flactag->comment());
$mp3tag1->setGenre($flactag->genre());
$mp3tag1->setYear($flactag->year());
$mp3tag1->setTrack($flactag->track());
 
# Add some custom fields
my $field;
 
# Performer sort order
my $str = Audio::TagLib::String->new("ARTISTSORT");
if ($flactag->fieldListMap()->contains($str)) {
  my $text = $flactag->fieldListMap()->getItem($str)->toString();
  my $bv = Audio::TagLib::ByteVector->new("TSOP");
  $field = Audio::TagLib::ID3v2::TextIdentificationFrame->new($bv, "Latin1");
  $field->setText($text);
  $mp3tag->addFrame($field);
}
 
# Custom text entries for the TXXX fields
foreach my $tagname ( ("DATEADDED", "MFPLAYLIST") ) {
  $str = Audio::TagLib::String->new($tagname)
  if ($flactag->fieldListMap()->contains($str)) {
    my $text = $flactag->fieldListMap()->getItem($str)->toString();
    $field = Audio::TagLib::ID3v2::UserTextIdentificationFrame->new("Latin1");
    $field->setDescription(Audio::TagLib::String->new($tagname));
    $field->setText($text);
    $mp3tag->addFrame($field);
  }
}
 
$mp3->save();
 

3 comments

  1. sungo
    November 22, 2007

    Are you aware of mp3fs, a fuse based pseudo filesystem? Basically, you mount your flac directory, ala bind mounts, someplace else like /mp3. when you read files out of that new directory, mp3fs converts them to mp3 on the fly, based on specifications you provided in the mount options. It’s very sexy. I don’t know how it would hold up to your tagging requirements though :)

  2. Les Greenberg
    January 3, 2008

    Mike

    Any suggestions for converting Vinyl to MP3 (original beetles albums)?

  3. Apoorv Parle
    February 14, 2014

    Mike, Thanks for pointing out this module. I was always running into issues with ID3v2.4 using other modules (MP3::Tag).
    I found detailed documentation of the C++ API of this module here http://taglib.github.io/api/
    The perl module is not exactly the same, but it is roughly equivalent.
    For others who are looking for an even simpler and abstracted interface (which can handle mp3 as well as other formats), here is a sample program:

    #!/usr/bin/perl

    use Audio::TagLib;
    use warnings;
    use strict;

    die “Usage: $0 \n” unless ($#ARGV == 0);

    my $mp3 = Audio::TagLib::FileRef->new($ARGV[0]);
    my $mp3tag=$mp3->tag();

    my $artist = $mp3tag->artist()->toCString();
    my $title = $mp3tag->title()->toCString;
    my $album = $mp3tag->album()->toCString;
    print “$artist\n$title\n$album\n”;

    $mp3tag->setTitle(Audio::TagLib::String->new(“==$title++”));
    $mp3->save();

    $artist = $mp3tag->artist()->toCString();
    $title = $mp3tag->title()->toCString;
    $album = $mp3tag->album()->toCString;
    print “New:\n$artist\n$title\n$album\n”;