Thursday, December 27, 2007

Back Up Your iPod With Rails

A couple years ago I bought an iPod Nano. At that time I was, to my chagrin, on a Windows box. (In my defense, I got a lot of "free" software out of that sacrifice.) Anyway, that box now lives in storage in a sort of shed in Pecos, NM. I'm currently in Pecos, visiting my parents and brother for the holidays, so I dug my old external hard drive out from under a mountain of boxes, but there are still a large number of files on this old iPod which I don't have anywhere else, and which, in point of fact, I actually legitimately bought with actual real money.

Unfortunately, Apple by default "protects" your iPod from "illegal" copying, and if you want to do some legal copying, too bad. After I upgraded to OS X, but before I bought another iPod, I wanted to swap out some of the media files on my Nano - put a few on my laptop's hard drive and pull some new ones off the laptop and into the Nano. This isn't really possible in iTunes or the Finder, unless there's some clever hack I missed. But it turns out to be effortless in either Path Finder or the Terminal - which would be great, if the files still had the names I put them into the iPod with.

However, your iPod actually renames every file you put onto it. Here's an example:

/iPod_Control/Music/F05/JTEH.m4a

iTunes really isn't that great, and I got tired of all this silliness, so I tracked down GNUpod, a nice set of Perl scripts which return control of my lawful property to me, but, having never used GNUpod before, I wanted to backup all my files just in case.

So I whipped up a quick Rails app. You could do this entire thing just with ActiveRecord, not Rails itself, but using a Rails app gives me the ability to add a GUI at some point in future. The Rails app has just one model - MediaFile - and its primary bit of code lives not in a controller or model or even a view, but in lib. I wanted to integrate it with Rails proper, but MP4Info, one of the metadata-extracting libraries I used, isn't compatible with Unicode, which I think Rails 2 added or something - I have a cold so this whole post will be a bit head-fuzzy, as I'm head-fuzzy myself at the moment - so MP4Info lost its ability to obtain data when run from within script/console, but still works perfectly from irb.

I also cheated a little, by preparing a file list ahead of time:

find /Volumes/giles_nano/iPod_Control/Music > ~/programming/releases/mypod/backup_nano/all_files_on_nano.txt

I don't remember everything about Ruby's File stuff, and I wanted to work fast.

The other cheat I used was to create a symlink - both my iPods have spaces in their names, which was enough to confuse ID3Lib, the other metadata-extracting library my code uses. One of them also has a stupid fancy apostrophe in Unicode or something, instead of a nice simple ASCII one, so in either case, creating symlinks on the filesystem removed a ton of development busywork.

By the way, if you noticed that programming/releases/mypod part, and you started to get curious, yes, this is part of a project called myPod, but no, it's not yet released. myPod is very, very far from release-ready at this stage. Contributors and curious types welcome, of course, although you'll need to be patient. Besides the cold I mentioned, which has me barely coherent, I have very, very specific goals for the project, and less free time than I would prefer.

If you want to use this code, you'll need to tweak it somewhat. The file list is hardcoded, you'll probably want to create symlinks for your iPod(s) just like I did, and although in theory it should work for most iPods except for Shuffles, I've currently only thoroughly used it on a Nano from a few years ago. (Large parts of it are also known to work on an 80GB iPod I bought a few months ago, just before they released the iPod Classic.)

To use it, you just go to the root dir of your Rails app and enter irb - not script/console. (Again, some kind of Unicode conflict prevents this thing from running properly from within script/console.) Using the code should look a lot like this:

<macbook of doom:giles> [12-27 10:06] ~/programming/releases/mypod/backup_nano  
! irb
>> require 'lib/nano_scan' ; backup_nano("/Users/giles/Desktop/nano_backup")


Choose an appropriate backup directory location, of course.

What you will end up with is only a backup of your Nano. Nada mas. Next I'm going to see if I can get GNUpod running, but that'll probably have to wait until I get over this cold. (Pecos, NM is way up in the mountains, with snow everywhere.) The files you extract from your iPod will still have the exact same filenames they had on the iPod itself, but you'll have a fairly complete database of their metadata which you can access from Rails, either through your Web browser (if you code some views and controllers) or via script/console. So even though you won't see the song titles or album names on your filesystem, if you go into script/console, which is fine to use at this point, you can pretty easily do:

MediaFile.find_by_title("Policy Of Truth").new_filename

...to get your Depeche Mode fix. You could even do this:

system("open -a QuickTime\\ Player #{MediaFile.find_by_title('Policy Of Truth').new_filename}")

...to launch your song directly from within script/console. It's a one-liner, but I reduced it to a method, like this:

quicktime(song_name)

e.g.,

quicktime("Policy Of Truth")

or

quicktime("Jump")

...if you're more in the mood for Kris Kross.

However, this approach assumes uniquely-named songs, which is not always the case. It should be fairly obvious that you can extend the model to support a search interface, and that is of course planned for the future. A more practical flaw is that this approach will be useful or useless for you depending on whether or not your media files contain metadata - many do, many do not. In practice I extracted at least some metadata from all the files on my Nano, except for two .wav files. I didn't bother to figure out a way to extract their metadata, because there were only two of them, but I know for a fact that the metadata exists, because I created them both in Sonic Foundry Acid Pro many years ago, and Acid both embeds metadata in .wavs and reads it from them. On the other hand, several movie files on my 80GB iPod had no metadata at all. I mainly use my bigger iPod for watching movies on planes, so that's kind of a hassle, but otherwise it's pretty good. My media hard drive is in Los Angeles, so I'm waiting a couple days before I use this code to back up my 80GB iPod. (Backing up a 4GB Nano on a laptop is no big deal.)

Another usage caveat: this code archives your media files' included images in the database, as a temporary measure. This can trigger max_allowed_packets errors in MySQL. I set my max_allowed_packets to 16MB and the problem was solved. Obviously you might not want that on a production machine, but if you're deploying this code in production, that's a whole nother ball of wax (especially legally speaking). Hopefully it's obvious that this code is supposed to run on your local machine, not the network, but just in case, this code is supposed to run on your local machine, not the network.

Again, this is just a tool I used to prepare my Nano for conversion to GNUpod. I'm expanding "release early, release often" to "release everything." It's not production-ready software by any stretch of the imagination, but it could be useful or interesting, so if you want, go ahead and download it, play around with it, and e-mail me at gilesb@gmail.com if you dig it.