#!/usr/local/bin/python # # FLAC Transcoder # $Id: transcode.py,v 1.2 2005/11/29 01:56:34 matt Exp $ # # Transodes a FLAC image with embeded TOC and metadata tags into whatever # file formats are desired. The formats are set via an encoder list in # the configuration file. For details, see CONFIG. import os import sys import getopt import re import ConfigParser import subprocess import string import MusicBrainzHelper import FlacHelper import logging import pickle import glob import random # Default values. DEFAULT_FLAC='flac' DEFAULT_METAFLAC='metaflac' DEFAULT_CONFIG='transcode.cfg' # summary status returned at the end summaryTranscoded = [] summaryNoMusicBrainzId = [] summaryInvalidMusicBrainzId = [] # Print a usage statement def usage ( progname ): print "usage: "+progname+" [-e ] [-f ]" print " [-F ] [-M ] " print " -e : list of encoders to use" print " -f : alternate configuration file ("+DEFAULT_CONFIG+")" print " -F : flac command ("+DEFAULT_FLAC+")" print " -M : metaflac command ("+DEFAULT_METAFLAC+")" print print " input files: a list of flac files to encode" sys.exit( 1 ) # run with arguments and wait for it to return. returns # the return value from the other program def invoke_command ( filename, args ): print filename for i in range(0, len(args)): if (type(args[i]) == str): args[i] = args[i].decode('cp1252') if (type(args[i]) == unicode): args[i] = args[i].encode('cp1252') args[0:0] = [ filename ] process = subprocess.Popen(args, executable=filename) return process.wait() # Create a track number string. Returns a stringified version of number, with # a '0' prepended if the number was less than 10. Don't use on numbers greater # than 99. def gen_trackstr ( number ): if number < 10: return '0'+str( number ) else: return str( number ) # Extract track number 'track_num' from 'filename'. 'flac_cmd' specifies the # FLAC binary to use. def extract_track ( filename, track_num, start_index, flac_cmd ): ext_file = str(track_num) + "-" + str(random.random()) + ".wav" args = [ '-d', '--cue='+str(track_num)+'.'+str(start_index)+'-'+str(track_num+1)+'.0', '--output-name='+ext_file, filename ] retval = invoke_command(flac_cmd, args); if retval != 0: raise EnvironmentError("System call \'"+cmd+"\' returned: "+str(retval)) return ext_file # this maps illegal characters for windows filenames to legal replacements def make_legal_for_windows(s): # remove trailing periods while s.endswith('.'): s = s[0:len(s) - 1] # convert to 8-bit if type(s) == unicode: s = s.encode('cp1252') # translate illegal characters to a safe replacement return string.translate(s, string.maketrans('\"*/:<>?\\|', '\'+-;[]_-;')) # Substitute % keys from 'tag_dict' in the 'src_str' def sub_str ( src_str, tag_dict): src_str = unicode(src_str) for key in tag_dict.keys(): t = type(tag_dict[key]) if (t == unicode): value = tag_dict[key] value = value.encode('cp1252', 'ignore') value = value.decode('cp1252', 'ignore') elif (t == str): value = tag_dict[key].decode('cp1252') else: value = str(tag_dict[key]) src_str = src_str.replace(key, value) return src_str # Process the FLAC image 'filename' using 'encoders'. def process_file ( filename, encoders, flac_cmd, meta_cmd ): flacHelper = FlacHelper.FlacHelper(filename) cuesheet = flacHelper.readFlacCuesheet() # rescope these globals as locals global summaryTranscoded global summaryNoMusicBrainzId global summaryInvalidMusicBrainzId # load the FLAC cuesheet and get the minimum index number for each # track along with it's type # matt's old comment on why we do this: # INDEX_00 is typically inter-track gap that shows up as -0:01 and such # on your CD player. The problem is that this index does not always # exist and if you tell flac to start at a non-existant start track, it'll # start with the previous index, in this case that could be the whole # previous track. So, we test for the existance of index 0 and start # at INDEX_01 if it doesn't exist. startTrackRegex = re.compile(r'TRACK (?P\d+) (?P\w+)') startIndexRegex = re.compile(r'INDEX (?P\d+) (?P\d+):(?P\d\d):(?P\d\d)') trackNumber = -1 trackType = dict() trackFirstIndex = dict() for line in cuesheet: startTrackMatch = startTrackRegex.search(line) if startTrackMatch: trackNumber = int(startTrackMatch.group("tracknum")) - 1 trackType[trackNumber] = int(startTrackMatch.group("tracknum")) trackFirstIndex[trackNumber] = 99 startIndexMatch = startIndexRegex.search(line) if startIndexMatch: indexNumber = int(startIndexMatch.group("index")) if indexNumber < trackFirstIndex[trackNumber]: trackFirstIndex[trackNumber] = indexNumber # see if we have tags cached for this file cachedTagsFilename = filename + ".mb-tags" release = None if os.path.exists(cachedTagsFilename): try: cachedTagsFile = open(cachedTagsFilename, "rb") release = pickle.load(cachedTagsFile) cachedTagsFile.close() except: release = None # get this release from musicBrainz if release == None: # get the musicbrainz id for this file musicBrainzId = flacHelper.getMusicBrainzId() if musicBrainzId == None: print "File has no MusicBrainzId tag: %s" % filename summaryNoMusicBrainzId += filename return release = mb.getReleaseById(musicBrainzId) if release == None: print "MusicBrainzId tag not found for file: %s (%s)" % (filename, musicBrainzId) summaryInvalidMusicBrainzId += filename return if len(release.tracks) != len(trackFirstIndex.keys()): print "track count in FLAC not equal to track count in MusicBrainz" summaryInvalidMusicBrainzId += filename return print "Artist: %s" % release.artist.name.encode('cp1252', 'ignore') print "Title: %s" % release.title.encode('cp1252', 'ignore') # Create the tag dictionary. tags = dict() tags['%P'] = release.artist.name tags['%T'] = release.title tags['%G'] = "Rock" if len(release.releaseEvents) > 0: tags['%Y'] = release.releaseEvents[0].date[0:4] else: tags['%Y'] = "0" tags['%N'] = len(release.tracks) tags['$T'] = make_legal_for_windows(tags['%T']) tags['$P'] = make_legal_for_windows(tags['%P']) # we do stuff a little differently if this is a comp various = not release.isSingleArtistRelease() # Process each track. for trackNumber in xrange(0, tags['%N']): tags['%n'] = gen_trackstr(trackNumber + 1) track = release.tracks[trackNumber] # Add some tags to the dictionary on a per-track basis. tags['%t'] = track.title if various and track.artist: tags['%p'] = track.artist.name else: tags['%p'] = release.artist.name tags['$t'] = make_legal_for_windows(tags['%t']) tags['$p'] = make_legal_for_windows(tags['%p']) fExtracted = False try: for encoder in encoders: (enc_type, enc_std, enc_var, dir_str, filename_std, filename_var) = encoder dir = os.path.expanduser( sub_str( dir_str, tags ) ) tags['%D'] = dir tags['$D'] = make_legal_for_windows( dir ) # create the right encoder command based on album type if various: command_args = re.split('(?