‘music’ Category

 

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();