ipodsync python script

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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: