20 May 2008

flac2m4a.py

Zune's new "gapless" (close enough) playback works better with AACs than it does with Lame-encoded mp3s. So, a quick hack script was in order to convert my collection of FLACs into AACs. But, I have four cores, so the script had to take advantage of those! Here's my flac2m4a Python script, that will convert a whole folder hierarchy into m4a files. It uses as many cores as you have available, and copies cover art so Zune can understand it. It's a bit rough, though.
You will need:
Here's the script:
import os
import shutil
import pp

job_server = pp.Server()
jobs = []

def quote(s):
return s.replace('"', '\\"')

def convert_file(input,output):
flac_mapping = {'title': '\xa9nam', 'artist': '\xa9ART', 'album': '\xa9alb', 'genre': '\xa9gen', 'description': '\xa9cmt', 'composer': '\xa9wrt', 'date': '\xa9day'}

command = 'flac -d -c -s "' + quote(input) + '" | neroAacEnc -br 128000 -lc -if - -of "' + quote(output) + '" 2>/dev/null'
os.system(command)
tagsource = mutagen.flac.FLAC(input)
tagdest = mutagen.mp4.MP4(output)
for k, v in flac_mapping.iteritems():
if tagsource.has_key(k):
tagdest[v] = tagsource[k]
if tagsource.has_key('tracknumber'):
tagdest['trkn'] = [(int(tagsource['tracknumber'][0]), 0)]
tagdest['pgap'] = True
tagdest.save()
return "processed " + os.path.basename(output)

def convert_directory(src, dst):
os.mkdir(dst)
srcnames = os.listdir(src)
for name in srcnames:
srcfname = os.path.join(src, name)
dstfname = os.path.join(dst, name)
if (os.path.isdir(srcfname)):
convert_directory(srcfname, os.path.join(dst, name))
elif (name.endswith('.flac')):
newname = name[:-4] + "m4a"
jobs.append(job_server.submit(convert_file, (srcfname, os.path.join(dst, newname)), (quote,), ("mutagen.flac", "mutagen.mp4", "os", "sys")))
elif (name == "cover.jpg"):
shutil.copyfile(srcfname, os.path.join(dst, "ZuneCustomAlbumArt.jpg"))

if __name__ == "__main__":
import sys
if len(sys.argv) == 3:
print "Starting pp with", job_server.get_ncpus(), "workers"
convert_directory(sys.argv[1], sys.argv[2])
for i, job in enumerate(jobs):
print job(), str(i+1) + "/" + str(len(jobs))
job_server.print_stats()
else:
print "usage: flac2m4a <input> <output>"

2 comments:

Anonymous said...

I am trying to make it work under Mac OS. Basically all things from the requirements list are installed, except for the last one, Nero codec works only on Linux and Windows, and they do not give sources to recompile.

Can you perhaps point me to an alternative codec that will work with your script?

treellama said...

Unless you really need a command line solution, someone's already done a much nicer job of this for Mac OS X!

http://sbooth.org/Max/

It uses Apple's AAC encoder, so it's very high quality.

If you're determined to get the script working, you might try http://sourceforge.net/projects/faac/

You'll have to edit the script to use faac's command line arguments.