I am fed up with using GUIs to sync my ipod with my music database, so I hacked the attached script together this evening. It uses the python bindings of libgpod.

Originally, I thought of using rsync for the file update part of this, but the issue is rsync doesn’t allow you to reformat the destination filenames, and there are restrictions on filename length/character set in the ipod’s iTunesDb/filing system.

I found various other implementations of this around the web, but they all seemed to be attachments to blogs which had gone missing. So I’ve included this one as part of the post.

Any mp3s in the content database and not on the ipod (or which have changed since they were added to the ipod) will be added.

Any mp3s on the ipod which are not in the content database will be removed.

#!/usr/bin/python import os import gpod import sys

Parse args

if len(sys.argv) != 3: print >>sys.stderr, “Syntax: ipodsync ” sys.exit(1) mountpoint = sys.argv[1] content = sys.argv[2]

Open/create the itunesdb

try: db = gpod.Database(mountpoint) except: gpod.itdb_init_ipod(mountpoint, None, “IPOD”, None) db = gpod.Database(mountpoint)

Get details of all the tracks within the itunesdb

invalid_tracks = [] ipod_tracks = {} ipod_files = {} for track in db:

Get userdata

userdata = track[‘userdata’] if userdata == None: invalid_tracks.append(track) continue
if ‘filename_utf8’ not in userdata: invalid_tracks.append(track) continue if ‘sha1_hash’ not in userdata: invalid_tracks.append(track) continue
original_filename = userdata[‘filename_utf8’] hash = userdata[‘sha1_hash’] ipod_filename = track.ipod_filename()

if its a dupe, mark it invalid

if original_filename in ipod_tracks: print >>sys.stderr, “Content file %s duplicated” % original_filename invalid_tracks.append(track) continue

if it doesn’t exist in the content store, mark it invalid

if not os.path.exists(content + ”/” + original_filename): print >>sys.stderr, “Content file %s vanished” % original_filename invalid_tracks.append(track) continue

if it doesn’t exist on the ipod, mark it invalid

if not os.path.exists(ipod_filename): print >>sys.stderr, “IPod file %s vanished” % ipod_filename invalid_tracks.append(track) continue

if the hash doesn’t match, mark it invalid

new_hash = gpod.gtkpod.sha1_hash(content + ”/” + original_filename) if new_hash != hash: print >>sys.stderr, “Content file %s changed” % original_filename invalid_tracks.append(track) continue

Keep note of it

ipod_tracks = track; ipod_files = track;

remove any invalid ones from the database

for track in invalid_tracks: db.remove(track)

Now, go through the files in content, checking if they should be added

os.chdir(content) for root, dirs, files in os.walk(”.”): for content_filename in files: full_filename = root + ”/” + content_filename

# Ignore bad extensions
if full\_filename.lower().endswith(('jpg', 'nfo', 'htaccess', 'bmp', 'txt', 'sfv')):
  continue

# Ignore tracks we already have
if full\_filename in ipod\_tracks:
  continue

# Add it!
try:
  track = db.new\_Track(filename=full\_filename)
except:
  print >>sys.stderr, "Failed to add: %s" % full\_filename

Now, go through the files on the ipod, checking if they’re defunct

for root, dirs, files in os.walk(mountpoint + “/iPod_Control/Music”): for content_filename in files: full_filename = root + ”/” + content_filename

# Ignore tracks we already have
if full\_filename.lower() in ipod\_files:
  continue
  
print >>sys.stderr, "IPod file %s defunct" % full\_filename    
os.unlink(full\_filename)

def print_progress(database, track, i, total): print >>sys.stderr, “Copying to iPod %04d/%d: %s” % (i,total,track)

Finally, sync the db etc

try: db.copy_delayed_files(callback=print_progress) except Exception,ex: print >>sys.stderr, ex db.close()