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()