Пример #1
0
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
Пример #2
0
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()