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?
  • You might see a message that says that the network share is already mounted followed by an error that says that the music library path cannot be found. This may be the result of an empty mount-point directory (e.g. /Volumes/mymusic) left when the device was last mounted or if mounting failed. It is not uncommon in Unix environments to leave mount points hanging around unmounted, but OS X usually doesn’t leave them this way—when you unmount a network drive in OS X, the mount point directory disappears. This orphaned mount point directory will cause plmapper to think the share is mounted when it isn’t. Just remove the empty mount point directory.

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.

[Update 26-Feb-2012]

It has been quite a long time since I last looked at this. As things turned out, my Squeezebox Server died an untimely death and that sort of put the brakes on my central music library project.

A couple of weeks ago I located a spare PC and set up a new Squeezebox Server, so I’m back in business.

Some things I have noticed:

  • Squeezebox Server is now called Logitech Media Server. Keep that in mind when you are looking for an installer. I was following instructions on how to install Squeezebox Server on Ubuntu and I had to change the apt-get command from sudo apt-get install squeezeboxserver to sudo apt-get install logitechmediaserver
  • Apparently they have changed from using MySQL to using SQLite. This made the install even easier—there was no need for me to mess around with creation or configuration of the MySQL user or database.
  • The default server port has changed from 9090 to 9000.
  • They still haven’t fixed the bug with international characters in song titles in playlists.

That said, the plmapper script worked just fine with my new Squeezebox even on Lion. Hope it works for everyone else.

10 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.

  5. Cristian writes:

    I get this error after “Processing File”

    touch: /tmp/.plmapper: Permission denied

    can you help me?

  6. Pasdesignal writes:

    Hi and thanks!
    I use mpd on a homemade networked music server, using a router running openwrt as the embedded device. This little program solved a problem for me perfectly! I am yet to dive deeper into it, but so far it has been an elegant way to transpose my subscribed podcasts into an m3u format for mpd to play. Just need to automate it to update every night now. Better go and read the manual! thanks again.

  7. Rich writes:

    Hi

    Thanks for writing such a great program– it solves a problem I have been frustrated with for a long time. It works perfectly except it seems that for Apple Lossless files the program appends an extra part that makes it impossible for the server to find the songs. In my case the extra part is “#/Volumes” “Volumes” is part of the file path, but because the server is on the same volume as the library it cannot be part of the playlist command. And the # character also seems to be unnecessary. Oddly, it does not add these characters with mp3 files, which work perfect– it’s just a problem with the m4a files. any thoughts?

  8. Rich writes:

    Sorry, I should have written the plmapper “prepends” the extra characters– they appear at the beginning of the file path command in the playlist.

  9. Cam writes:

    First up, thanks for an incredible script. Perfect for syncing playlists to xbmc on a Raspberry Pi! @Rich, you can add m4a files by including them in the extensions in the plmapper script. Line 26: my $extensions=”.mp3,.MP3,.m4a”;

  10. Simon Mitchell writes:

    Hi Tad, stumbled across your site yesterday after a bit of googling and have used your script which works like magic :-)
    The only problem i am experiencing is that the exclude list is not working properly as I only want 3 playlists and exclude the others, but what is happening is of the 7 excludes I defined only 3 are being excluded…? Any ideas?
    Thanks again

Leave a Reply