iTunes and ID3

ziggy on 2003-01-05T23:34:59

After reading brian's experiences with iTunes this morning, I decided to scratch an itch I've had for quite some time now. I've long since gotten rid of my MP3s that I ripped on my (now defunct) FreeBSD box. Since July, I've ripped (and re-ripped) CDs through iTunes because it's just so quick. iTunes does a very nice job of organizing a music directory, and collects a lot of data from CDDB.

Unfortunately, none of this data ever winds up in ID3 tags, so copying MP3s from my Mac to my FreeBSD laptop loses information. The trick is to mine the "iTunes Music Library.xml" file for data and populate the ID3 tags using pudge's MP3::Info.

The first step is to glean out the relevant fields from the XML file. I could sit down and write an XML parser handler for the iTunes file. Or I could be lazy and use brian's Mac::PropertyList, since this XML file is simply a property list. But I didn't do either. Instead, I used my favorite XML gleaning tool: XSLT. I started by looking at the specific XML data in the iTunes file (the Tracks dictionary) and converted the information I was looking for into text. Building this stylesheet was an iterative process, and here is the final result:








 



 
 
 
 
 
 
 
 




 



This produces output like this:
Location: file://localhost/Users/ziggy/Music/iTunes/iTunes%20Music/Louis%20Armstrong/Greatest%20Hits/01%20Sugar.mp3
Name: Sugar
Artist: Louis Armstrong
Album: Greatest Hits
Genre: Jazz
Track Number: 1
I can then easily parse this output and use set_mp3tag from MP3::Info to add the ID3 tags these files were missing.

Here is the full script for anyone who is interested. Note that the XSLT stylesheet was included in the __DATA__ segment to reduce an external dependency.

#!/usr/bin/perl -w

use strict;

use MP3::Info;

my @id3_fields = qw(Location Name Artist Album Year Comment Genre);
push(@id3_fields, "Track Number");

use_winamp_genres();

sub read_metadata {
	my $filename = shift;
	use XML::LibXSLT;
	use XML::LibXML;

	$/ = undef;

	my $parser = XML::LibXML->new();
	my $xslt = XML::LibXSLT->new();

	my $source = $parser->parse_file($filename);
	my $style_doc = $parser->parse_string();
	my $stylesheet = $xslt->parse_stylesheet($style_doc);

	my $results = $stylesheet->transform($source);

	return $stylesheet->output_string($results);
}

## Update ID3 tags

print STDERR "Processing 'iTunes Music Library.xml'...";
my $metadata = read_metadata("$ENV{HOME}/Music/iTunes/iTunes Music Library.xml");
print STDERR "done\n";

my @blocks = split("\n\n", $metadata);

foreach my $block (@blocks) {
	my (%info) = map {m/^(\w+): (.*)$/} split("\n", $block);

	$info{Location} =~ s{^file://localhost}{};
	$info{Location} =~ s{%20}{ }g;

	print STDERR "$info{Artist}: $info{Album}, $info{Name}\n";

	$info{Genre} = 'Dance' if $info{Genre} eq "Electronica/Dance";
	$info{Genre} = 'Other' if $info{Genre} eq "World";
	$info{Genre} = 'Alternative' if $info{Genre} eq "Alternative & Punk";

	set_mp3tag(@info{@id3_fields});
}

__DATA__
## stylesheet, as above


Note

pudge on 2003-01-06T01:40:07

This only sets the ID3v1 tag. MP3::Info does not write ID3v2 yet. I hope to change that at some point in the next few months. In case you (or a reader) doesn't know, ID3v1's main disadvantage is that each field (artist, album, title) is limited to 30 characters. MP3::Info can read ID3v2.2.0 through ID3v2.4.0 (in theory; I have some bug reports that need looking into), but cannot yet write.

So if you writing ID3v1 is fine with you, you will either want to remove the ID3v2 tag (MP3::Info can do that for you), or make sure that when you are reading the data back, you read back the right version (get_mp3tag lets you select which one).

Also, I don't know why the info is not getting from iTunes into the ID3 tags. It should. Maybe it is that you are looking for the ID3v1 tag and it is in the ID3v2 tag? It can have both (ID3v1 is at the end of the file, ID3v2 at or near the beginning), and for most apps, the ID3v2 will override the ID3v1. iTunes defaults to writing ID3v2.2.0, IIRC (and I believe there is no way to change that, though you can tell iTunes to convert the tags to another version, though I am almost certain iTunes is buggy in this conversion ... something I will investigate while working on MP3::Info bug reports :-).

Re:Note

gnat on 2003-01-06T04:57:24

I love the id3convert utility that comes with the id3lib package. It solved my tagging hell (which was different from yours: ripped in RealAudio, tags buggered in iTunes).

--Nat

What is iTunes not updating?

brian_d_foy on 2003-01-06T06:35:28

Can anyone elaborate on what iTunes is not updating in the file? I checked a few of the files I updated through iTunes and everything seemed to update.

pure perl .xml parsing...

morbus on 2003-01-06T22:44:08

I've done simplistic parsing of the .xml file in pure Perl, to generate a list of albums here. It works well enough for my needs, and I generate and curl the list every morning...