import os import os.path import SplitTracks from Config import logger logger.setlevel('error') musicdir = os.environ['MUSIC'] lines = open('MusicTest.txt', 'r').readlines() for l in lines: l = l.strip() if len(l) > 0 and len(l.split(None,1)) > 1: name, params = l.split(None, 1) realtracks = eval(params) print "%s:" % name SplitTracks.testsplit(realtracks, os.path.join(musicdir, 'Albums', "%s.wav" % name)) print
def splitTrack(savefilename): savepath = os.path.join(dumpdir, "%s.wav" % savefilename) wav = SplitTracks.WAVSplitter(savepath) tracks = wav.parsewav() notesfile = open(os.path.join(dumpdir, "%s-notes.txt" % savefilename), 'w') logger.debug(', '.join(["%f" % t['length'] for t in tracks]), notesfile) flags, metadata, tracklist = ParseTables.readextendedtracklist(os.path.join(dumpdir, "%s.txt" % savefilename)) album, artist, year, genre = metadata maxnamelen = max((len(t[0]) for t in tracklist)) #Match up the measured tracks with the "official" list, tracklist trackmappings = [None] * len(tracklist) tracksfailed = [False] * len(tracklist) lastmatched = 0 #Match any with similar track lengths #for i, t in enumerate(tracklist): # for j, track in enumerate(tracks[lastmatched:]): # if abs(track['length'] - t[1]) < vs['Constants']['tracklentoleranceseconds']: # trackmappings[i] = [lastmatched+j] # lastmatched = j #See if runs of tracks can be combined to match with official ones #Simple case if numbers of found and official tracks match if len(tracks) == len(tracklist): for i in xrange(len(tracklist)): trackmappings[i] = [i] else: def trackmappingbounds(i): j = i + 1 while j < len(trackmappings) and not trackmappings[j]: j += 1 k = i - 1 while k >= 0 and not trackmappings[k]: k -= 1 #print "bounds=[%d, %d)" % (k, j) if k < 0: if j < len(trackmappings): indexrange = range(trackmappings[j][0]) else: indexrange = range(len(tracks)) elif j >= len(trackmappings): indexrange = range(trackmappings[k][-1] + 1, len(tracks)) else: indexrange = range(trackmappings[k][-1]+1, trackmappings[j][0]) return indexrange for i, t in enumerate(tracklist): if trackmappings[i] is None: indexrange = trackmappingbounds(i) matched = False #indexrange is the range of track indices in tracks that may be part of official track i for j1 in range(len(indexrange)): for j2 in range(j1+1, len(indexrange)+1): if abs(sum((tracks[k]['length'] for k in indexrange[j1:j2])) - t[1]) < vs['Constants']['tracklentoleranceseconds']: trackmappings[i] = indexrange[j1:j2] #print "Succeed", i, trackmappings[i] matched = True for k, tracki in enumerate(trackmappings[i][:-1]): if tracks[trackmappings[i][k]]['sharpend'] and tracks[trackmappings[i][k+1]]['sharpstart']: track = tracks[tracki] logger.warn("Removing skip at %f in %s" % (float(track['tend'] - track['tstart']) / wav.framerate, tracklist[i][0]), notesfile) break if matched: break for i, t in enumerate(tracklist): if trackmappings[i] is None: indexrange = trackmappingbounds(i) trackmappings[i] = indexrange #print "Fail ", i, trackmappings[i] #print trackmappings logger.warn("Failed to locate track %d, %s" % (i+1, tracklist[i][0]), notesfile) tracksfailed[i] = True realtracks = list() for i, m in enumerate(trackmappings): failed = tracksfailed[i] if len(m) == 0: continue sharpstart = tracks[m[0]]['sharpstart'] sharpend = tracks[m[-1]]['sharpend'] startreason = tracks[m[0]]['startreason'] endreason = tracks[m[-1]]['endreason'] if sharpstart: startchar = '|' else: startchar = '<' if sharpend: endchar = '|' else: endchar = '>' trackslen = sum((tracks[t]['length'] for t in m)) diff = trackslen - tracklist[i][1] diffint = int(abs(diff)) if diff < 0: diffstr = "%.3f seconds %s" % (diff, '-' * diffint) else: diffstr = "%.3f seconds %s" % (diff, '+' * diffint) if failed: logger.warn("%d. %s (%d secs):" % (i+1, tracklist[i][0].ljust(maxnamelen), tracklist[i][1]) + " =/=\t%s%s Tracks %s, Length = %.3f secs (%s) %s%s" % (startchar, startreason, str(map(lambda x: x+1, m)), trackslen, diffstr, endreason, endchar), notesfile) else: logger.info("%d. %s (%d secs):" % (i+1, tracklist[i][0].ljust(maxnamelen), tracklist[i][1]) + " ==\t%s%s Tracks %s, Length = %.3f secs (%s) %s%s" % (startchar, startreason, str(map(lambda x: x+1, m)), trackslen, diffstr, endreason, endchar), notesfile) #Only remove actual skips and not intentional silence in tracks indexpairs = [ [tracks[m[0]]['tstart']] ] for j, t in enumerate(m[:-1]): if tracks[t]['sharpend'] and tracks[m[j+1]]['sharpstart']: indexpairs[-1].append(tracks[t]['tend']) indexpairs.append([tracks[m[j+1]]['tstart']]) indexpairs[-1].append(tracks[m[-1]]['tend']) track = wav.compoundslice(indexpairs) realtrack = dict() realtrack['indexpairs'] = indexpairs realtrack['sharpstart'] = tracks[m[0]]['sharpstart'] realtrack['sharpend'] = tracks[m[-1]]['sharpend'] realtrack['startreason'] = tracks[m[0]]['startreason'] realtrack['endreason'] = tracks[m[-1]]['endreason'] realtrack['length'] = sum((tracks[t]['length'] for t in m)) realtrack['tracknum'] = i+1 #Dynamics analysis realtrack['maxlevel'] = max((tracks[t]['maxlevel'] for t in m)) realtrack['rmsintervals'] = SplitTracks.rmsintervals(track, chunksize=wav.framerate, dbfs=True) realtrack['maxintervalrms'] = np.amax(realtrack['rmsintervals']) realtrack['dynamicrange'] = realtrack['maxlevel'] - realtrack['maxintervalrms'] realtrack['rms'] = SplitTracks.todbfs(SplitTracks.rms(track)) realtrack['dbtoamplify'] = 0.0 realtrack['limitlevel'] = 0.0 realtrack['residue'] = 0.0 realtrack['postlimitamplify'] = 0.0 #Skip if the track is slaved if ('p' in flags and i in flags['p']) or ('a' in flags and i in flags['a']): realtracks.append(realtrack) continue realtrack['hist'], bins = SplitTracks.peakhistogram(track, rangeratio=(0.5,1.0), proportional=True) realtrack['top10hist'], top10bins = SplitTracks.peakhistogram(track, rangeratio=(0.9,1.0), proportional=True) if np.amin(realtrack['top10hist']) > 0: realtrack['topratio'] = float(realtrack['top10hist'][-1]) / float(np.amax(realtrack['top10hist'][:-1])) else: realtrack['topratio'] = 0 i = realtrack['hist'].shape[0]-1 s = realtrack['hist'][i] while s <= vs['Constants']['proportionpeakstochop']: i -= 1 s += realtrack['hist'][i] realtrack['choplevel'] = SplitTracks.todbfs(2**(8*wav.sampwidth-1) * (0.5 + float(i) / (2 * realtrack['hist'].shape[0]))) logger.debug("\tMaxlevel: %.3f\tAverage RMS: %.3f\tMax RMS: %.3f\tTopratio: %.3f" % (realtrack['maxlevel'], realtrack['rms'], realtrack['maxintervalrms'], realtrack['topratio']), notesfile) realtracks.append(realtrack) #Can do analysis of the entire album's dynamics here maxlevel = max((rt['maxlevel'] for rt in realtracks)) maxrms = max((rt['maxintervalrms'] for rt in realtracks)) mindynamicrange = min((rt['dynamicrange'] for rt in realtracks)) mindynamicrange = min(vs['Constants']['mindynamicrange'], mindynamicrange) #Dynamics analysis for i, rt in enumerate(realtracks): if ('p' in flags and i in flags['p']) or ('a' in flags and i in flags['a']): continue #We need either the dbtoamplify (for flat tracks) or the information for #limiting and post-limiting amplification #The simple case: the track has already been peak limited, so just amplify #it to the desired max level if rt['topratio'] >= vs['Constants']['topratioforbrickwalled'] or rt['dynamicrange'] < vs['Constants']['mindynamicrange']: rt['dbtoamplify'] = rt['maxlevel'] * -1 + vs['Constants']['dbfstoamplify'] else: #Otherwise, set the limiting level to the level that #proportionpeakstochop occur above #Maximum amount of headroom we can lose from the peaks (will be negative) #Determined by the minimum dynamic range maxdbtochop = rt['dynamicrange'] - mindynamicrange minlimitlevel = 20 * math.log10((math.pow(10, float(-1 * maxdbtochop)/20) - 0.2)/0.8) limitlevel = max(rt['choplevel'], minlimitlevel) residue = vs['Constants']['baseresidue'] lllfbr = vs['Constants']['lowestlimitlevelforbaseresidue'] #Lower residual level if the chop is extreme enough if limitlevel <= lllfbr: residue *= max(1.0 - 0.75 * (lllfbr - limitlevel) / (-1 * lllfbr), 0.25) #Calculate level to amplify to reach full scale limit = float(SplitTracks.fromdbfs(limitlevel)) newlimit = limit + (2**(8*wav.sampwidth - 1) - limit) * residue newmaxlevel = SplitTracks.todbfs(newlimit) postlimitamplify = -1 * newmaxlevel - rt['maxlevel'] hllffs = vs['Constants']['highestlimitlevelforfullscale'] if limitlevel >= hllffs: postlimitamplify += (hllffs - limitlevel)/(-1 * hllffs) rt['limitlevel'] = limitlevel rt['residue'] = residue rt['postlimitamplify'] = postlimitamplify #Build runs if continuous or 'c' in flags: runs = [0] * len(realtracks) runnum = 1 else: runs = [None] * len(realtracks) runnum = 0 for i, rt in enumerate(realtracks): #Skip if part of a run if runs[i] is not None: continue #Look for tracks that connect to this one (unless it is not like its #neighbors) j = i-1 while j >= 0 and ((realtracks[j+1]['sharpstart'] and realtracks[j]['sharpend']) or ('p' in flags and j in flags['p']) or ('a' in flags and j+1 in flags['a'])): j -= 1 lowerbound = j + 1 j = i+1 while j < len(realtracks) and (realtracks[j-1]['sharpend'] and realtracks[j]['sharpstart'] or ('p' in flags and j-1 in flags['p']) or ('a' in flags and j in flags['a'])): j += 1 upperbound = j if upperbound - lowerbound > 1: for j in xrange(lowerbound,upperbound): runs[j] = runnum runnum += 1 #Connect all runs for i in xrange(runnum): run = [realtracks[j] for j in xrange(len(runs)) if runs[j] == i and ('q' not in flags or j not in flags['q'])] limitedrun = [t for t in run if t['dbtoamplify'] != 0.0] nonlimitedrun = [t for t in run if t['limitlevel'] != 0.0] if len(limitedrun) > 0: dbta = min((t['dbtoamplify'] for t in limitedrun)) for tr in run: tr['dbtoamplify'] = dbta elif len(nonlimitedrun) > 0: mindex = 0 maxl = nonlimitedrun[0]['limitlevel'] for j, t in enumerate(nonlimitedrun[1:]): if (t['limitlevel'] > maxl and t['limitlevel'] != 0.0) or maxl == 0.0: maxl = t['limitlevel'] mindex = j + 1 for tr in run: tr['limitlevel'] = nonlimitedrun[mindex]['limitlevel'] tr['residue'] = nonlimitedrun[mindex]['residue'] tr['postlimitamplify'] = nonlimitedrun[mindex]['postlimitamplify'] if len(run) > 0: ml = min((t['maxlevel'] for t in run)) for tr in run: rt['maxlevel'] = ml #Apply dynamics changes and save tracks for i, rt in enumerate(realtracks): if rt['dbtoamplify'] != 0.0: logger.info("Amplifying track %d by %.3f dB" % (rt['tracknum'], rt['dbtoamplify']), notesfile) else: logger.info("Limiting track %d to %.3f dBfs (r=%.2f) and amplifying by %.3f dB" % (rt['tracknum'], rt['limitlevel'], rt['residue'], rt['postlimitamplify']), notesfile) if not test: savename = os.path.join(dumpdir, "%s%d-%s.wav" % (savefilename[:3], i, savefilename)) wav.saveslice(rt['indexpairs'], savename, rt['sharpstart'], rt['sharpend'], rt['maxlevel'], rt['dbtoamplify'], rt['limitlevel'], rt['residue'], rt['postlimitamplify']) #Util.mp3encode(savename, tracklist[i][0], artist=artist, album=album, year=year, trackno=rt['tracknum'], trackcount=len(tracklist), genre=genre) notesfile.close()