Some time ago I wrote an article about manipulating the iTunes XML database. I had moved some mp3s around on my NFS drive, and iTunes had lost track of them. Though I came up with a Scheme program that tracked them down and correlated them with their old location, it turns out you can't modify the iTunes XML database without losing most of your metadata, including play counts.
The best I could do was to extend the program to create symlinks from the original locations to the new ones; iTunes continues to reference the old locations, but it will see the files through the symlinks and thus access to your music is restored.
Unfortunately, this leaves a bunch of ugly symlinks hanging around for all eternity. This article fills in the missing piece by forcing iTunes to directly use the new locations.
First of all, I found that when you add a new symlinked file to your library, iTunes remembers the endpoint of the link, not the link location itself. Similarly, once in a while I noticed that a Get Info... on certain symlinked files (that I had "moved" with my script) actually showed the new location! This seemed to happen after I made some kind of change to the file's tag data, but it wasn't consistent, and would sometimes not persist after restarting iTunes.
Finally, I determined you just have to change the modification time on the destination file; the next time you do a Get Info..., iTunes traverses the symlink and updates its database with the real location of the file.
Therefore, the formula for moving files around safely is:
So I created a couple tools to help me with this process.
The first is mvln, a silly little perl script which simply moves files and creates symlinks in their place. It will create directories as necessary and works across filesystems. For example, let's move a bunch of albums in Artist/Album/Track.mp3 format from a crazy directory to a more sane location:
$ cd /mnt/mp3/scratch/cd/mp3/ariana/26 # change to source dir first $ mvln /mnt/mp3/cd */*/*.mp3 Audioslave/Audioslave/01 Cochise.mp3 -> /mnt/mp3/cd/Audioslave/Audioslave Audioslave/Audioslave/02 Cochise.mp3 -> /mnt/mp3/cd/Audioslave/Audioslave ... Various Artists/Garden State/13 Winding Road.mp3 -> /mnt/mp3/cd/Various Artists/Garden State
An alternative call to mvln might be
$ find . -type f -print0 | xargs -0 mvln /mnt/mp3/cd
mvln is a bit slow for lots of files, and creates a lot of symlinks, so you might wonder why we can't just symlink the directory instead. The reason we specify the full path to every file is to remove the possibility of collisions. If you said
mvln /mnt/mp3/cd Various\ Artists # bonk
and the destination already had a Various Artists directory, the move will fail. Using the full paths will merge your new Various Artists albums in correctly.
A way to avoid this clash is to increase the depth to Artist/Album/ to lessen the chance of collision. This will probably work for Various Artists, but has the same problem in the following instance where we try to merge in a bunch of floating tracks to an existing floating tracks directory.
mvln /mnt/mp3/cd Delerium/Floating\ tracks # bonk
However, if you're sure these cases don't apply--for example, you're just relocating a large directory somewhere else--then you can speed up the process by specifying a partial path.
The second tool is Touch and refresh tracks, an Applescript which takes the current iTunes selection and touches every file in that selection, then refreshes them. It uses the refresh command to accomplish what you do by hand with Get Info, much faster and without using the GUI.
Download this and place it in ~/Library/iTunes/Scripts or in ~/Library/Scripts/Applications/iTunes if you have enabled the system script menu via Applescript Utility. Either way, the script should appear in your menu bar.
Now select all the tracks in iTunes and run Touch and refresh tracks from the script menu. Selecting tracks may get tedious, so read the endnotes for some alternatives. For example, you can use mvln -t and Refresh tracks to save some manual labor.
This is still a pretty manual process; you have to remember to Touch and Refresh all the tracks you moved, and you have to delete the symlinks yourself later. To my knowledge, there's no way to set the iTunes selection via Applescript, so the selection has to be done by hand. Email me if you know otherwise.
One downside of having to touch files to refresh them is that touched files will be copied back to your iPod on the next sync, even though they didn't really change.
Touching is done through the AppleScript instead of in mvln because I don't always use mvln to create the symlinks--for example, some may be created by the Scheme script which reattaches lost files.
Selecting tracks can be a pain when you've moved a lot of albums and especially when only some of a particular artist's albums have been moved. For this reason I added a -t option to mvln, which touches the files after they are moved, and also created a Refresh tracks script which strips out the touching. This allows you to refresh tracks without worrying about touching extraneous files or making iTunes read a bunch of unnecessary data. In other words, you can select all tracks from an artist even if only one album was moved, or even select every track in your library if you don't want to bother. (When I tried the latter, it took 11 minutes to scan 6000 songs over a wireless NFS connection. Wired is faster.)
As a side benefit, you can use Refresh tracks any time you need to tell iTunes you've made a modification to a music file under its nose. For example, I've corrected a bunch of ID3 tags programmatically and Refresh tracks will make iTunes read the new tags. (The tag change will update the file modification time for you, but you can use Touch and refresh tracks as well.)
If you're moving a large number of albums, consider using the partial-path method described above, as it's faster. Touch all the files first and then move the directories themselves, using Artist/Album paths to avoid collisions. This should work as long as you don't have any "catchall" albums such as "Floating Tracks" in the source. For example:
cd some/large/directory find . -type f -print0 | xargs -0 touch mvln /mnt/mp3/cd */* # Move entire /artist/album/ directories
Date: 2007/07/25 15:15:08