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:
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(); |
sungo
November 22, 2007Are 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 :)
Les Greenberg
January 3, 2008Mike
Any suggestions for converting Vinyl to MP3 (original beetles albums)?
Apoorv Parle
February 14, 2014Mike, 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”;