#!/usr/bin/env python
# encoding=utf-8

"""
Converts Apple Lossless (.m4a) files into FLAC (.flac), copying the metadata.
Could quite easily be adapted to other combinations.
"""


METADATA_MAPPING = {
	'\xa9alb': 'album',
	'\xa9nam': 'title',
	'\xa9ART': 'artist',
	'\xa9day': 'date',
	'\xa9cmt': 'description', 
	'\xa9too': 'encoded-by',
	   'cpil': 'compilation',
	'\xa9wrt': 'performer',
	   'trkn': ('tracknumber', 'tracktotal'),
	'\xa9gen': 'genre',
	   'disk': ('discnumber', None),
}

def copy_metadata(m4a, flac):
	for name, value in m4a.items():
		try:
			target = METADATA_MAPPING[name]
		except KeyError:
			pass
		else:
			if isinstance(target, tuple):
				for target, value in zip(target, value):
					if target: flac[target] = unicode(value)
			else:
				flac[target] = unicode(value)
#

def convert(file_in, file_out):
	from tempfile import mkstemp
	from mutagen import m4a, flac
	from os import close, curdir, unlink
	from subprocess import call
	
	wavfd, wavfile = mkstemp('.wav', '.dump.', curdir)
	
	try:
		close(wavfd) # not going to use filehandle.
		
		cmds = [
			['mplayer', '-nortc', '-really-quiet', '-vc', 'null', '-vo', 'null',
				'-ao', 'pcm:file=%s:fast' % wavfile, file_in],
			['flac', wavfile, '-o', file_out],
		]
		
		for cmd in cmds:
			if not call(cmd) == 0:
				break
		else:
			ma, mb = m4a.M4A(file_in), flac.FLAC(file_out)
			copy_metadata(ma, mb)
			mb.save()
		#
	
	finally:
		unlink(wavfile)
#


if __name__ == '__main__':
	# options
	from optparse import OptionParser
	parser = OptionParser()
	parser.add_option('-i', '--in', dest='file_in', default='.',
		metavar="FILE", help="input filename (.m4a) or directory")
	parser.add_option('-o', '--out', dest='file_out', default='.',
		metavar="FILE", help="output filename (.flac) or directory")
	
	options, args = parser.parse_args()
	
	# check options
	from os.path import exists, isdir, isfile
	
	if args:
		parser.error('no extra arguments accepted')
	
	if not exists(options.file_in):
		parser.error('input does not exist')
	
	if isdir(options.file_in):
		if not exists(options.file_out):
			parser.error('output does not exist')
		elif not isdir(options.file_out):
			parser.error('when input is a directory, output must also be a directory')
		else:
			recursive = True
	else:
		recursive = False
	
	# do stuff
	if recursive:
		from os import makedirs, sep, walk
		from os.path import dirname, join, normpath, splitext
		
		din = normpath(options.file_in)
		dout = normpath(options.file_out)
		
		trim = len(din)
		
		for dirpath, dirnames, filenames in walk(din):
			for filename in filenames:
				base, ext = splitext(filename)
				
				if base.startswith('.'):
					continue
				elif not ext == '.m4a':
					continue
				
				inpath = join(dirpath, filename)
				outpath = join(dout, dirpath[trim:].lstrip(sep), '%s.flac' % base)
				
				if exists(outpath):
					print "%s exists; skipping" % outpath
					continue
				else:
					print inpath, '-->', outpath
				
				outdir = dirname(outpath)
				if not isdir(outdir):
					makedirs(outdir)
				
				convert(inpath, outpath)
	
	elif exists(options.file_out):
		parser.error("%s exists; delete first" % options.file_out)
	
	else:
		convert(options.file_in, options.file_out)
#



# End
