#!/usr/bin/env python
#
#   id3util
#
#   Version 2006-01-05(b)
#
#   ID3 editing utility.  Requires pyid3lib module to be installed on
#   the system.
#
#   Copyright(C) 2006, Matt Roberts.
#
#


# modules we need
import sys
import getopt
import os.path
import mimetypes
try:
	import pyid3lib
except:
	sys.stderr.write("Required module 'pyid3lib' not found.\n")
	sys.exit(1)

# global settings
language = 'eng'

# list of keywords we can set in the tag
keywords = [
	'album',           'artist',     'comment',        'contentgroup',
	'contenttype',     'copyright',  'date',           'encodedby',
	'encodersettings', 'fileowner',  'filetype',       'initialkey',
	'involvedpeople',  'isrc',       'language',       'leadartist',
	'lyricist',        'mediatype',  'mixartist',      'netradioowner',
	'netradiostation', 'origalbum',  'origartist',     'origfilename',
	'origlyricist',    'origyear',   'partinset',      'picture',
	'playlistdelay',   'publisher',  'recordingdates', 'size',
	'songlen',         'subtitle',   'time',           'title',
	'track',           'wwwartist',  'wwwaudiofile',   'wwwaudiosource',
	'wwwcommercialinfo', 'wwwcopyright', 'wwwpayment', 'wwwpublisher',
	'wwwradiopage', 'year'
	]


#
#
#   usage() - write usage information to stdout
#
#

def usage(sw=80):
	tab = (4 * ' ')
	exn = os.path.basename(sys.argv[0])
	sys.stderr.write("Usage: %s [-c] [-v] [-i fn] [ ( field='value' | ^field ) ... ] [--] < files >\n\n" % exn)
	sys.stderr.write("Options include:\n")
	sys.stderr.write("%s-v Increase verbosity\n" % tab)
	sys.stderr.write("%s-c Clear all ID3 Tag information (before applying changes)\n" % tab)
	sys.stderr.write("%s-i <fn> Read filenames to tag from file 'fn'\n\n" % tab)
	sys.stderr.write("Possible field names:\n%s" % tab)
	ct = 0
	keywords.sort()
	maxwidth = 0
	for kw in keywords:
		if len(kw) > maxwidth:
			maxwidth = len(kw)
	limit = (sw - len(tab)) / (maxwidth + 1)
	for kw in keywords:
		format = "%%-%ds" % (maxwidth + 1)
		sys.stderr.write(format % kw)
		ct += 1
		if ct == limit:
			sys.stderr.write("\n%s" % tab)
			ct = 0
	sys.stderr.write("\n\n")


#
#
#   dumpkeys()
#
#

def dumpkeys(id3):
	keys = [i['frameid'] for i in id3]
	sys.stderr.write("# SOURCE FILE KEYS:\n")
	for key in keys:
		sys.stderr.write("   + %s\n" % key)
				

#
#
#
#    MAIN SCRIPT
#
#
#

# read the command line
DEBUG    = 0
clear    = False
optlist  = 'cvi:'
args     = [ ]
droptags = [ ]
infile   = ''
files    = [ ]
fields   = { }
try:
	optlist, args = getopt.getopt(sys.argv[1:], optlist)
except:
	usage()
	sys.exit(1)

# first, deal with option arguments
for opt in optlist:
	if opt[0] == '-i':
		infile = opt[1]
	elif opt[0] == '-c':
		clear = True
	elif opt[0] == '-v':
		DEBUG += 1

# now, weed out the filenames and tag options
done = False
for token in args:
	token = token.strip()
	if not token: continue
	if done:
		files.append(token)
		continue
	if token == '--':
		done = True
		continue
	if token[0] == '^':
		token = token[1:].lower()
		if token in keywords:
			droptags.append(token)
			continue
		
	tokens = token.split('=', 1)

	# if this is a filename, save it
	if len(tokens) != 2:
		files.append(token)

	# if this is a field spec, save it
	elif tokens[0].lower() in keywords:
		key = tokens[0].lower()
		value = tokens[1]
		if key not in fields.keys():
			fields[key] = [ ]
		fields[key] = value

	# otherwise, complain
	else:
		sys.stderr.write("Couldn't figure out what to do with: %s\n" % token)
		sys.exit(1)

# if no input files are given, complain and quit
if not files and not infile:
	usage()
	sys.exit(1)

# if the filenames are to be taken from a file, read it in
if infile:
	fo = None
	if infile == '-':
		fo = sys.stdin
	else:
		try:
			fo = file(infile, 'r')
		except:
			sys.stderr.write("Could not open pathname file: %s\n" % infile)
			sys.exit(1)
			
	files = fo.readlines()

try:
	# loop over all files given, assign filename to 'fn'
	for fn in files:
		# make sure the file exists
		fn = fn.strip()
		if not fn: continue
		if fn[0] == '#': continue
		if not os.path.isfile(fn):
			sys.stderr.write("Could not open source file: %s\n" % fn)
			continue

		# start with a NULL tag object
		id3info = None

		try:
			# DEBUG:
			if DEBUG > 2:
				sys.stderr.write("# OPEN SOURCE FILE: %s\n" % fn)
			elif DEBUG == 1:
				sys.stderr.write("%s\n" % fn)

			# open the file, and read its tag
			id3info = pyid3lib.tag(fn)

			# DEBUG:
			if DEBUG > 2: dumpkeys(id3info)

			# CLEAR: clear tag fields?
			if clear:
				if DEBUG > 2:
					sys.stderr.write("# CLEAR ALL TAG FIELDS\n")
				keys = [i['frameid'] for i in id3info]
				while keys:
					for key in keys:
						id3info.remove(key)
					keys = [i['frameid'] for i in id3info]

				# DEBUG:
				if DEBUG > 2: dumpkeys(id3info)

			# UPDATE: apply tag field changes
			keys = fields.keys()
			keys.sort()
			if DEBUG > 2:
				sys.stderr.write("# APPLY ID3 FIELD CHANGES\n")
			# first, the fields we want to drop
			for field in droptags:
				if DEBUG > 2: sys.stderr.write("# DELETE FIELD: %s\n" % field)
				if field == 'comment':
					id3info.remove('COMM')
				elif field == 'picture':
					id3info.remove('APIC')
				else:
					exec "id3info.%s = None" % (field)
			# the the fields we want to add/change
			for field in keys:
				# pictures need special treatment
				if field == 'comment':
					text = fields['comment']
					d = {
						'text': text,
						'textenc': 0,
						'description': '',
						'language': language,
						'frameid': 'COMM'}
					id3info.append(d)
				elif field == 'picture':
					imagefile = fields['picture']
					if not os.path.isfile(imagefile):
						sys.stderr.write("Cannot open image file: %s\n" % imagefile)
						sys.exit(1)
					mtype, enc = mimetypes.guess_type(imagefile)
					if not mtype:
						sys.stderr.write("Cannot determine MIME type of image file: %s\n" % imagefile)
						sys.exit(1)
					fo = open(imagefile, 'rb')
					d = {
						'frameid' : 'APIC',
						'mimetype' : mtype,
						'picturetype' : 3,
						'data' : fo.read() }
					fo.close()

					# add the image to the tag
					id3info.append(d)

				# other fields are just set by name
				else:
					exec "id3info.%s = fields['%s']" % (field, field)

			# DEBUG: print output of what we have done
			if DEBUG > 1:
				sys.stderr.write("#\n")
				sys.stderr.write("#  ID3 Tag Information: %s\n" % fn)
				sys.stderr.write("#\n\n")
				for e in id3info:
					if 'text' in e.keys():
						sys.stderr.write("[%s]: %s\n" % (e['frameid'], e['text']))
					elif 'mimetype' in e.keys():
						sys.stderr.write("[%s]: %s\n" % (e['frameid'], e['mimetype']))
					elif 'url' in e.keys():
						sys.stderr.write("[%s]: %s\n" % (e['frameid'], e['url']))
					elif 'description' in e.keys():
						sys.stderr.write("[%s]: %s\n" % (e['frameid'], e['description']))
					else:
						sys.stderr.write('[%s]: No extended information available.\n' % e)
				sys.stderr.write("\n")
			if DEBUG > 2:
				dumpkeys(id3info)

			# COMMIT:
			id3info.update()

		except Exception, message:
			sys.stderr.write("# Error while processing %s: %s\n" % (fn, message))
			if DEBUG > 2: raise
except Exception, message:
	sys.stderr.write("# Unhandled exception!  Message is: %s\n" % message)
	if DEBUG > 2: raise

# EOF
