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 <ipod mountpoint> <content dir>" 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[original_filename] = track; ipod_files[ipod_filename.lower()] = 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()