#!/usr/bin/env python """ flac2mp3 (Transcode FLAC to MP3) Requirements: * python * flac (including metaflac) * lame History: -------- 2007-02-24 Darren Stone Created, using structure from ogg2mp3. http://bitmason.com Run with --help for usage. Distribute, use, and modify freely, but please keep the history above and usage below up to date! """ from sys import argv, exit, stdout from os import system, stat, getpid, unlink from os.path import basename from signal import signal, SIGINT from commands import getoutput # base LAME command. # 1. ID3 tags, etc. will be automatically appended if available # 2. user-supplied command line options will also be appended lame_cmd_base = 'lame --quiet' # describes mapping from Vorbis comment keys to LAME ID3 tag commands keys_to_id3 = { 'TITLE': '--tt', 'ARTIST': '--ta', 'ALBUM': '--tl', 'GENRE': '--tg', 'COMMENT': '--tc', 'DATE': '--ty', 'TRACKNUMBER': '--tn', } # temporary WAV file will be created (and later removed) in current working directory WAV_FILENAME_PREFIX = "_tmp_flac2mp3_" def flac_info_dict(flacfilename): """ Return dictionary of flac meta tags, containing at least the following keys -if- they are present in the flac file: TITLE, ARTIST, ALBUM, GENRE, DATE, COMMENT, TRACKNUMBER """ d = {} out = getoutput("metaflac --list %s" % shell_quote(flacfilename)) out = out.splitlines() for line in out: if line.find("comment[") >= 0: for k in keys_to_id3.keys(): res = line.split("%s=" % k) if len(res) > 1: d[k] = res[1] return d def file_size(filename): """ Return size of file, in bytes. """ return stat(filename).st_size def size_to_human(bytes): """ Return string representation of the byte count, human-readable (i.e. in B, KB, MB, or GB) """ if bytes >= 1024*1024*1024: return "%0.1f GB" % (float(bytes)/1024.0/1024.0/1024.0) elif bytes >= 1024*1024: return "%0.1f MB" % (float(bytes)/1024.0/1024.0) elif bytes >= 1024: return "%0.1f KB" % (float(bytes)/1024.0) else: return "%d B" % bytes def file_size_human(filename): """ Return string representation of the filename, human-readable (i.e. in B, KB, MB, or GB) """ return size_to_human(stat(filename).st_size) def shell_quote(s): """ Quote and escape the given string (if necessary) for inclusion in a shell command """ return "\"%s\"" % s.replace('"', '\\"') def transcode(flacfilename): """ Transcode given FLAC to MP3 in current directory, with .mp3 extension, transferring meta info where possible. Return (flacsize, mp3size). """ try: wavfilename = "%s%d.wav" % (WAV_FILENAME_PREFIX, getpid()) mp3filename = "%s.mp3" % basename(flacfilename)[:-5] flacsize = file_size_human(flacfilename) stdout.write("%s (%s)\n" % (flacfilename, flacsize)) flacdict = flac_info_dict(flacfilename) encode_cmd = lame_cmd_base for k in flacdict.keys(): if k in keys_to_id3.keys(): encode_cmd = "%s %s %s" % (encode_cmd, keys_to_id3[k], shell_quote(flacdict[k])) stdout.write(" %s: %s\n" % (str(k), str(flacdict[k]))) stdout.write("%s " % mp3filename) stdout.flush() decode_cmd = "flac -d --silent -o %s %s 2>/dev/null" % (shell_quote(wavfilename), shell_quote(flacfilename)) encode_cmd = "%s %s %s 2>/dev/null" % (encode_cmd, wavfilename, shell_quote(mp3filename)) system(decode_cmd) system(encode_cmd) mp3size = file_size_human(mp3filename) stdout.write("(%s)\n\n" % mp3size) except Exception, e: stdout.write(str(e)) try: unlink(wavfilename) except: pass return (file_size(flacfilename), file_size(mp3filename)) def sig_int_handler(p0, p1): """ Make CTRL-C less catasrophic """ pass if __name__ == '__main__': # TODO: ensure flac, metaflac, lame are available signal(SIGINT, sig_int_handler) if len(argv) < 2 or (len(argv) >= 2 and argv[1] in ('-h', '--help', '-?')): progname = basename(argv[0]) print "Usage: %s [LAME_OPTIONS] FILE1 [FILE2 [FILE3 ...]]" % progname print "\nTranscode FILE(s) from FLAC to MP3." print "MP3s with same basename and .mp3 extension will be written to current working" print "directory and meta info will be transferred to ID3 tags where possible." print "\nExamples:" print "%s -B 256 --vbr-new -V 0 *.flac (decent quality VBR)" % progname print "%s -m m -s 22.05 -b 56 -q 9 --lowpass 8 *.flac (lo-fi, fast, mono CBR)" % progname exit(1) # append user-supplied cmd line options (for LAME) argv.pop(0) while not argv[0].lower().endswith('.flac'): lame_cmd_base = "%s %s" % (lame_cmd_base, argv[0]) argv.pop(0) fcount = 0 flacsize = 0 mp3size = 0 for f in argv: (s1, s2) = transcode(f) fcount += 1 flacsize += s1 mp3size += s2 # summary if fcount > 1: stdout.write("%s FLACs (%s) transcoded to MP3 (%s)\n" % ( fcount, size_to_human(flacsize), size_to_human(mp3size)))