Export iTunes Playlists to a non-iTunes World

I freely admit my dislike for iTunes—it’s a black box where you toss your music, giving full control over your library to Apple.
The problem is that sometimes you might want to manage your library in a way that Apple never intended, and then things become challenging. iPods, iPads, and iPhones pretty much force us to use iTunes, so why not figure out some way to lessen the pain?

I like the playlist tools that iTunes provides, and I find it very convenient to create Smart Playlists or to create Genius lists. However, I want to keep my Master Library of music elsewhere, far from iTunes. Wouldn’t it be great if I could share these playlists on my home network in a completely nondenominational format? Wouldn’t it be nice if my Squeezebox server would have those same playlists available? And wouldn’t it be spectacular if those playlists would magically appear on a network drive whenever the lists change in iTunes?

Read on for details on how I accomplished this and to download the free utility I wrote to handle this task.

The Problem To Be Solved

In order to understand what this application does, you need to know what I was trying to accomplish.

I have a NAS drive on my network that contains my Master Library of mp3 files. The files on this drive are located under /media/music.

I have a Squeezebox server, running on an Ubuntu machine. This server is used by Squeezebox devices to play music anywhere in my house. It holds a copy of the Master Library, located under /srv/squeezebox/music

What I want: I want my Mac to magically push proper m3u playlists to the Squeezebox server—the Squeezebox server understands m3u playlists.

What I don’t want: Squeezebox supports direct access to iTunes. But I don’t want to do this because I don’t want to have to run Squeezebox on my Mac. Why did I bother setting up a dedicated server if I have to keep my Mac running?

Why is this challenging: Even if I could point Squeezebox to the bare iTunes database file, all of the file paths are wrong. My master library might have a song at …/artist/album_name/disc 1/, while iTunes might put it at …/Artist/Album Name/, omitting the disc 1 folder and reformatting names.

My Solution

I wrote a small command line application in Perl that supports the following:

  • Paths to songs in Apple’s mysterious black box are converted to nice paths to the same songs in my golden Master Library.
  • Even if a song name changes or a folder path changes, the original master copy is found.
  • All playlists are generated in m3u format.
  • Smart playlists and Genius playlists are supported.
  • The generated playlists are automatically moved to either a local directory or a remote server, using scp.
  • Network shares are automatically mounted at beginning of process and unmounted at end of process.
  • You can provide a file path to be prepended to all playlist entries.
  • Exits early if the iTunes library has not changed since the last run.
  • Supports exclusion patterns in order to skip some playlists.
  • A local Squeezebox server can be automatically pinged to cause it to refresh playlists.

Here is the full documentation for the Playlist Mapper application.

Basic Installation

Please note that this is a command line program. Some day I might figure out how to make a pretty GUI installer for it, but for now you have to get your hands a little dirty in order to install and configure.

Download the application here: plmapper

This is a Perl script that uses many standard utilities already present in a Snow Leopard OS X installation. You should not need anything other than the plmapper file itself.

Unzip the file and place plmapper in your home directory.

Open a Terminal window and make the script executable:

chmod u+x plmapper

Read the man page for the application by doing this:

./plmapper --man

There are many command line arguments, but you don’t need to worry about most of them. Create a file called plmapper.config in your home directory and put any arguments you need in that file, as name-value pairs.

Example:

itunes=/Users/Bozo/Music/iTunes Music Library.xml
dest=/Users/Bozo/Desktop
library=/Volumes/media/music

Once you have added all of the configuration settings, go ahead and run it:

./plmapper

You should see plenty of status information go by as each playlist is processed.

Making it Run Automatically

There are better ways of doing this, but I’m a command-line commando, so I simply added the following cron entry (see crontab):

0 22 * * * /Users/Me/bin/plmapper >/Users/Me/plmapper.log 2>&1

This causes the plmapper program to run once daily at 10pm, logging output to a file called plmapper.log in my home directory.

Of course, this can be tweaked to taste; it doesn’t matter if you run it hourly since the app skips any heavy lifting once it sees that the iTunes library file hasn’t been touched.

How It Works

The application performs the following basic steps:

  • Mounts network shares using mount_smbfs.
  • All music files in the external non-iTunes Master Library are listed in an internal data structure, sorted by file size.
    This allows us to quickly find a “short list” of possible candidate files that match a given source file. This works because mp3 files have fairly random sizes.
  • The iTunes database XML file is processed using an XSLT transformation to generate a set of basic m3u playlist files. The xsltproc command line utility is used to run the transformation.
  • All paths in the playlists are converted from escaped URI syntax (e.g. %20 for space) to regular UTF-8 characters.
  • Each path is resolved to a single music file (i.e. an mp3 file) in the Master Library.
    This is done by obtaining the size of the iTunes version of the file and then looking this up in the internal list created in the first step.
  • Once a short list of candidates is found, the first few thousand bytes of each file are compared until a match is found.The risks of choosing the wrong file are low: all that will happen is that a song might be switched in the playlist accidentally. As such, it isn’t worth scanning the full file to ensure they are exact matches.This algorithm works quite well, and can detect music files that have been renamed and moved around.
  • The playlist files are now converted from utf8mac to utf8 using the iconv command line utility.This process collapses wide UTF-8 characters, consisting of a letter plus an accent add-on, into their single-letter equivalents. This process is known as Unicode Normalization, and this tool uses Normalization form C: Compatibility decompisitiion followed by canonical composition.
  • Playlist files are now copied to a local or remote directory, either as direct file copy or via the scp utility.
  • Optionally, a special call is made to a specified Squeezebox server to tell the server to refresh all playlists.
  • Any mounted shares that were mounted by this process are unmounted via diskutil unmount.

Limitations

  • Nested folders of playlists currently act funny. The lists will come over, but you may get two lists. You can use the exclude option to prevent this.
  • Songs with non-ASCII characters in the file name might not be recognized by all players. Squeezebox has a problem with many special characters—I tested this by adding songs like Águas de Março.mp3 using Squeezebox’s own playlist editor, and when I reloaded the list the songs were not there. This is a bug in Squeezebox server.
  • Occasionally the file matching algorithm might pick the wrong song. In an effort to speed up processing, the program only looks at the first few thousand bytes of files when comparing. It isn’t a big deal if the wrong file is added to a playlist, is it?

Closing Thoughts

As often happens, this program was written because I had an itch that needed to be scratched. Now that I went through all of the hassles, I hope others can benefit.

Through good fortune, I am working in a Macintosh environment. This means that all of the important tools were already present on my machine: perl, xsltproc; mount; iconv; scp; nc; and others. If I were writing this for a PC, I probably would have written the whole thing in Java—a more familiar language to me, but one that comes with its own configuration issues.

I plan on updating plmapper over time, and if anyone has suggestions, please let me know.

And if a Perl guru wants to tell me my Perl skillz are subpar, go right ahead—but be gentle, and let me know how to improve the app.

[Update]

Fixed a couple of bugs related to path and “qx” calls, thanks to Chris. Updated version here.

4 Responses to “Export iTunes Playlists to a non-iTunes World”

  1. Chris writes:

    I tried using your script. I get the following error:

    chris@aurora:/home/slimserver$ ./plmapper –force
    Configuration

    Itunes library: /home/slimserver/iTunes Music Library.xml
    NAS music library: /home/slimserver/music
    Destination directory: /tmp

    Generating playlists…Can’t exec “XSLTPROC”: No such file or directory at ./plmapper line 280, line 91.
    done.
    Building file tree…done.

  2. Tad writes:

    Yeah… I saw a similar error in my log recently, but had been putting off fixing this because I didn’t know anyone actually downloaded the script :-)

    It’s either my own path problem or Apple made some change in system configuration in a recent patch.

    I’ll look into this tonight.

    Thanks for bring this to my attention!

  3. Chris writes:

    I found that for line 280, changing from:

    print qx(XSLTPROC –novalid $myxslt $myitunes);

    to:

    print qx(@{[XSLTPROC]} –novalid $myxslt $myitunes);

    allowed the xsltproc command to run. It seems that none of the constants within the qx() function are converted to their values. The program sends XSLTPROC to the shell instead of /usr/bin/xsltproc. I don’t know what the proper behaviour is supposed to be, but that’s how it’s working for me.

  4. Tad writes:

    Thanks for finding that for me!

    I was experimenting earlier and discovered that everything ran fine when the utilities were in my system path, but as soon as the app is launched via cron, with its pathetic bare-bones set of environmental variables, it fails with these weird errors.

    I find it odd that the qx calls worked find as long as the executables were in the system path.

    Anyway, I wrapped all of the references to constants in qx calls in the same syntax you used. It is working better now.

    I also found that there was a hardcoded “library” in the old one, and a copy/paste error in the code that was supposed to unmount the second Samba connection.

    All of that should be fixed now.

Leave a Reply