#!/usr/bin/python
import os, sys, optparse
import lxml.etree
import urllib, shutil
import re

home = os.getenv('HOME')
iTunesFolder = os.path.join(home, 'Music', 'iTunes')
iTunesFile = os.path.join(iTunesFolder, 'iTunes Library.xml')
if not os.path.exists(iTunesFile):
	iTunesFile = os.path.join(iTunesFolder, 'iTunes Music Library.xml')
	if not os.path.exists(iTunesFile):
		print 'Cannot find iTunes Library in', iTunesFolder
		sys.exit()

def parsePlaylist(playlist):
	name = None
	identifier = None
	itemList = None

	for element in playlist.iterchildren('key'):
		if element.text == 'Name':
			name = element.getnext().text
		elif element.text == 'Playlist Persistent ID':
			identifier = element.getnext().text
		elif element.text == 'Playlist Items':
			itemList = element.getnext()
	
	return name, identifier, itemList

def parseTrack(trackInfo, tracks):
	track = None
	trackId = None
	path = None

	for element in trackInfo.iterchildren('key'):
		if element.text == 'Track ID':
			trackId = element.getnext().text
	
	for possibleTrack in tracks.iterchildren('key'):
		if possibleTrack.text == trackId:
			trackElement = possibleTrack.getnext()
			track = {}
			for trackdata in trackElement.iterchildren('key'):
				track[trackdata.text] = trackdata.getnext().text
	
	return track, trackId

def humanReadableFilesize(filesize):
	for  bytesLevel in ['B', 'KB', 'MB', 'GB', 'TB']:
		if filesize < 1024.0:
			return "%3.1f%s" % (filesize, bytesLevel)
		filesize /= 1024.0
	
	return "%3.1f%s" % (filesize, 'PB')

def copyTrack(track):
	source = track['Location']
	if source.startswith('file://localhost'):
		source = source[16:]
	elif source.startswith('file:///'):
		source = source[7:]
	else:
		#probably an online stream, but report it just in case
		print 'Ignoring', source
		return

	#determine source path
	source = urllib.url2pathname(source)

	#determine destination path
	albumFolder, fileName = os.path.split(source)
	artistFolder, albumName = os.path.split(albumFolder)
	musicFolder, artistName = os.path.split(artistFolder)
	destination = os.path.join(options.copy, artistName, albumName, fileName)
	destinationDir = os.path.join(options.copy, artistName, albumName)

	#copy file
	if os.path.exists(destination):
		newStats = os.stat(source)
		oldStats = os.stat(destination)
		if newStats.st_mtime == oldStats.st_mtime:
			return
		elif newStats.st_mtime < oldStats.st_mtime:
			print '\nThere is a newer version of', artistName, albumName, fileName, 'at', options.copy
			return

	if not os.path.exists(destinationDir):
		os.makedirs(destinationDir)

	if not options.showTracks:
		status = 'Copying ' + str(currentItem) + ' of ' + str(itemCount)
		print '\b%s%s'%(status,'\b' * len(status)),
		sys.stdout.flush()
	shutil.copy2(source, destination)

def quit(message):
	print message
	sys.exit()

def sortablestring(track, key):
	if key not in track:
		return None

	text = track[key]

	text = text.lower()
	text = text.strip()
	text = re.sub(r'&[rl][sd]quo;', r'', text);

	if text.startswith(('the ', 'a ', 'an ')):
		before, separator, after = text.partition(' ')
		text = after
	if text.startswith(('"', "'")):
		text = text[1:]

	return text

def trackSortData(track):
	artist = sortablestring(track, 'Artist')
	album = sortablestring(track, 'Album')
	title = sortablestring(track, 'Name')
	
	return artist, album, title

parser = optparse.OptionParser("%prog [-options] <playlist1 playlist2 etc.>")
parser.add_option('-s', '--space', help='Tally up disk usage for tracks', action='store_true', dest='tallySpace')
parser.add_option('-t', '--tracks', help='Show all tracks', action='store_true', dest='showTracks')
parser.add_option('-c', '--copy', help='Copy tracks', metavar="destination/folder")
parser.add_option('-m', '--main', help='Include main library', action='store_true', dest='showMain')
parser.add_option('-o', '--order', help='Sort tracks before displaying/copying them', action='store_true')
parser.add_option('-r', '--ratings', help='Display rating if displaying tracks', action='store_true')

(options, listsToShow) = parser.parse_args()

root = lxml.etree.parse(iTunesFile)
iTunes = root.find('dict')

for element in iTunes.iterchildren('key'):
	if element.text == 'Music Folder':
		iTunesFolder = element.getnext().text

	if element.text == 'Playlists':
		playlists = element.getnext()

	if element.text == 'Tracks':
		tracks = element.getnext()

for playlist in playlists:
	playlistName, playlistId, playlistItems = parsePlaylist(playlist)
	if playlistName == 'Library':
		continue

	if playlistName == 'Music' and not options.showMain and playlistName not in listsToShow:
		continue

	if playlistItems is None:
		continue

	if not listsToShow or playlistName in listsToShow:
		itemCount = len(playlistItems)
		spaceUsed = 0
		currentItem = 0
		print playlistName, "("+str(itemCount), 'item'+('s' if itemCount!=1 else '') +')'
		if options.showTracks or options.tallySpace or options.copy:
			trackList = []
			for track in playlistItems:
				track, trackId = parseTrack(track, tracks)
				trackList.append(track)
			if options.order:
				trackList.sort(key=trackSortData)

			for track in trackList:
				currentItem = currentItem + 1
				if options.showTracks:
					info = []
					if options.ratings:
						if 'Rating' in track:
							info.append('*'*(int(track['Rating'])/20))
						else:
							info.append('')
					info.append(track['Name'].encode('utf8'))
					if 'Album' in track:
						info.append(track['Album'].encode('utf8'))
					if 'Artist' in track:
						info.append(track['Artist'].encode('utf8'))
					print "\t", "\t".join(info)
				if options.tallySpace and 'Size' in track:
					spaceUsed = spaceUsed + int(track['Size'])
				if options.copy:
					copyTrack(track)
			if options.tallySpace:
				print "\t", 'Space used:', humanReadableFilesize(spaceUsed)
