During the years, I went through my entire music collection in iTunes and rated everything. 1-5 stars.
1: to be deleted.
2: not something I want to listen to normally, but I do want to keep it. Bad sound quality stuff, for instance, but I want to listen to it once per year.
3: normal.
4: quite good. Normally I listen to a 4+5 star random playlist.
5: Bloody good. These songs are epic. Or they contain parts that make me close my eyes and enjoy what’s coming. Or they bring back memories. Some perfect piece of Bach. Or the opening tune of Battlestar Galactica (old series). Or that 20+ minute Neal Morse progressive rock epic. Or the heart-rending laat dit het laatste zijn from the Dutch musical oorlogswinter.
My problem? I had a non-apple laptop for a year. Ubuntu. So no iTunes. I kept
the iTunes Music Library.xml
file with all the file and rating information
and retained the existing folder structure on ubuntu with ubuntu’s player.
I retained the .xml file as I guessed I could use it to get my ratings back.
Problem: nope, whatever I tried, I couldn’t get my ratings back. I wrote a
small script to read my old .xml file and my current one. I mapped tracks in
both of them based on filenames and set ratings based on the old ones. I
copied the resulting .xml file over the real iTunes Music Library.xml
and
restarted iTunes. No effect.
The reason: iTunes doesn’t use that file itself, it is only there to provide
information to other programs that might need to grab the music library info!
It uses some unreadable .itl
database.
I got it working in the end by using a hint I found. The trick is to make playlists per rating. One for 1 star, one for 2 star items, etc.
So I modified my script to not change the ratings, but to add 5 playlists, one for each rating. And to assign the tracks for which I wanted to set the rating to the correct playlists.
In itunes, there’s a “File - Library - Import library…” menu option. I gave
it my newly generated .xml
file and got my 5 playlists!
I then went to each of the playlists, selected everything, right clicked and gave them all the correct rating. Success!
Turns out that Python can read those apple .xml
“plist” files out of the
box with plistlib. Real
handy. A plist file is more or less an XML version of a JSON file.
I had my original .xml
file and a copy of iTunes current one in the same
directory as the script. The output, updated.xml
was what I imported into
iTunes.
Anyway, here is the script. I also added it as a github gist in case that’s handier. There’s a lot to refactor, but obviously I didn’t bother once I got it working: it is a one-time-only script. I added a couple of comments to make the usage clearer:
from collections import defaultdict
import plistlib
# I copied files locally.
# NEW is what gets written.
# CURRENT is the current "iTunes Library.xml" file (or rather, my copy).
# OLD is an old backup with lots of good ratings.
OLD = 'itunes.xml'
CURRENT = 'itunes_2012.xml'
NEW = 'updated.xml'
# I match based on filenames. IDs are different, sigh.
# But I needed to compensate for a new iTunes folder structure.
OLD_PREFIX = 'file://localhost/Users/reinout/Music/iTunes/iTunes%20Music/'
CURRENT_PREFIX = 'file://localhost/Users/reinout/Music/iTunes/iTunes%20Media/Music/'
def main():
# Read in the old tracks and grab the ratings.
old = plistlib.readPlist(OLD)
old_tracks = old['Tracks']
print "Found {} old tracks".format(len(old_tracks))
old_ratings = {}
for track in old_tracks.values():
filename = track['Location'].replace(OLD_PREFIX, '')
rating = track.get('Rating')
if rating is None:
continue
old_ratings[filename] = rating
print "Found {} old ratings".format(len(old_ratings))
# Same with the current ratings. If we've rated something, we want to
# keep that rating.
current = plistlib.readPlist(CURRENT)
current_tracks = current['Tracks']
print "Found {} current tracks".format(len(current_tracks))
current_ratings = {}
for track in current_tracks.values():
filename = track['Location'].replace(CURRENT_PREFIX, '')
rating = track.get('Rating')
if rating is None:
continue
current_ratings[filename] = rating
print "Found {} current ratings".format(len(current_ratings))
# Figure out which old ratings we want to move over.
new_ratings = {}
for filename in old_ratings:
if filename not in current_ratings:
new_ratings[filename]= old_ratings[filename]
print "This means {} new ratings".format(len(new_ratings))
# Create a dict {rating: tracks} for the new ratings I want to set.
new_playlists = defaultdict(list)
for track in current_tracks.values():
filename = track['Location'].replace(CURRENT_PREFIX, '')
new_rating = new_ratings.get(filename)
if new_rating is None:
continue
track_id = track['Track ID']
new_playlists[new_rating].append(track_id)
# Create a playlist per rating. You can set the playlist's items to
# the correct rating in iTunes just fine.
playlists = []
for rating, track_ids in new_playlists.items():
print rating, len(track_ids)
new_playlist = {}
new_playlist['Name'] = str(rating) + ' points'
new_playlist['Visible'] = True
new_playlist['Playlist ID'] = 8000 + rating
new_playlist['All Items'] = True
new_playlist['Playlist Items'] = [{'Track ID': track_id}
for track_id in track_ids]
playlists.append(new_playlist)
# Zap the existing ones, otherwise you end up with double items.
current['Playlists'] = playlists
plistlib.writePlist(current, NEW)
if __name__ == '__main__':
main()
My name is Reinout van Rees and I work a lot with Python (programming language) and Django (website framework). I live in The Netherlands and I'm happily married to Annie van Rees-Kooiman.
Most of my website content is in my weblog. You can keep up to date by subscribing to the automatic feeds (for instance with Google reader):