#!/usr/bin/env python # -*- coding: utf-8 -*- import sys, os, tempfile, optparse, shutil version = '0.1.3' date = '2010-06-11' changelog = """Changelog: --- Version 0.1.3 - 2010-06-11 --- * Protect against negative or zero length slices --- Version 0.1.2 - 2010-06-11 --- * Works with wav, aiff and flac * Fixed -g and -s * Added -m for passing through messages from sox and ecasound --- Version 0.1.1 - 2010-06-10 --- * Removed warnings from sox --- Version 0.1.0 - 2010-06-10 --- * First public release """ default_xfade = .3 def print_version(): print version + ' ' + date sys.exit() def print_changelog(): print changelog sys.exit() def search_path(cmdname, path = None): if path is None: path = os.environ["PATH"] if os.name in ["nt", "os2"]: short = [cmdname + "." + ext for ext in ["exe","com","bat"]] else: short = [cmdname] for scmd in short: for dir in path.split(os.pathsep): fcmd = os.path.abspath(os.path.join(dir,scmd)) if os.path.isfile(fcmd): return fcmd return None def check_external_program(program): if not search_path(program): print 'external program "' + program + '" not found, exiting...' sys.exit() def sox(command): check_external_program('sox') if not options.messages: command += ' >/dev/null 2>&1' os.system('sox ' + command) def ecasound(command): check_external_program('ecasound') if not options.messages: command += ' >/dev/null 2>&1' os.system('ecasound ' + command) def isFloat(str): """ Is the given string an integer? """ ok = True try: num = float(str) except ValueError: ok = False return ok def trim2(filename,resultFilename,start,end): if os.path.isfile(filename): length = end - start command = filename + ' ' + resultFilename + ' trim ' + str(start) + ' ' + str(length) sox(command) def fadein(filename,fade): (root, ext) = os.path.splitext(filename) outfile = tempfile.mktemp() + ext file_length = get_length_seconds(filename) if fade < 0: print filename + ': fade time less than 0, skipping...' return elif fade > file_length: print filename + ': fadein time (' + str(fade) + ' seconds) larger than file length (' + str(file_length) + ' seconds), skipping...' return command = filename + ' ' + outfile + ' fade t ' + str(fade) sox(command) shutil.move(outfile,filename) def gain(infile,gain): (root, ext) = os.path.splitext(infile) muted = tempfile.mktemp() + ext command = infile + ' ' + muted + ' vol ' + str(gain) sox(command) shutil.copy(muted,infile) os.remove(muted) def mute(infile): gain(infile,0) def fadeout(filename,fade): (root, ext) = os.path.splitext(filename) outfile = tempfile.mktemp() + ext file_length = get_length_seconds(filename) if fade < 0: print filename + ': fade time less than 0, skipping...' return elif fade > file_length: print filename + ': fadein time (' + str(fade) + ' seconds) larger than file length (' + str(file_length) + ' seconds), skipping...' return command = filename + ' ' + outfile + ' fade t 0 ' + str(file_length) + ' ' + str(fade) sox(command) shutil.move(outfile,filename) def get_length_seconds(file): seconds = 0 check_external_program('ecalength'); result = os.popen('export ECASOUND=ecasound; ecalength ' + str(file), 'r').readlines() for line in result: if line[:len(file)] == file: first = line.split(':') if len(first) > 1: seconds = float(first[1].split('s')[0].strip()) return seconds def mix(infiles,outfile): i = 1 eca = '-r ' for infile in infiles: eca += '-a:' + str(i) + ' -i ' + infile + ' -ea:200 ' i += 1 eca += '-a:all -o ' + outfile ecasound(eca) def cat(infiles,outfile): command = '' for infile in infiles: command += infile + ' ' command += outfile + ' mixer' sox(command) def xfade_snip(infile1,infile2,outfile,fade): (root, ext) = os.path.splitext(infile1) infile1_fadeout = tempfile.mktemp() + ext infile2_fadein = tempfile.mktemp() + ext shutil.copy(infile1,infile1_fadeout) shutil.copy(infile2,infile2_fadein) fadeout(infile1_fadeout,fade*.8) fadein(infile2_fadein,fade*.8) mix([infile1_fadeout,infile2_fadein],outfile) for file in [infile1_fadeout,infile2_fadein]: os.remove(file) def xfade_loop(filename,resultFilename,fade,loop_start,gap_length): (root, ext) = os.path.splitext(filename) """ original sample: | a | b | e | f | g | c | d | l-x x x c-x x (s-l-e-g)/2 x resulting sample: | a | bxg | gap | c | dxe | f | g | l-x x gap (s-l-e-g)/2 x c-x x """ file_length = get_length_seconds(filename) """ if fade < 0: print filename + ': fade time less than 0, skipping...' return elif fade > file_length * .5: print filename + ': fade time (' + str(fade) + ' seconds) larger than half the file length (' + str(file_length) + ' seconds), skipping...' return elif fade > loop_start and loop_start > 0: print filename + ': fade time (' + str(fade) + ' seconds) larger than loop_start (' + str(loop_start) + ' seconds), skipping...' return """ a = tempfile.mktemp() + ext b = tempfile.mktemp() + ext c = tempfile.mktemp() + ext d = tempfile.mktemp() + ext e = tempfile.mktemp() + ext f = tempfile.mktemp() + ext g = tempfile.mktemp() + ext bxg = tempfile.mktemp() + ext dxe = tempfile.mktemp() + ext gap = tempfile.mktemp() + ext a_len = loop_start - fade b_len = fade e_len = fade g_len = fade d_len = fade if loop_start == 0: a_len = 0 b_len = 0 g_len = 0 c_len = (file_length - fade*2) * .5 f_len = c_len else: c_len = (file_length - a_len - b_len - e_len - g_len) * .5 f_len = c_len - fade error = False for length in [a_len,b_len,c_len,d_len,e_len,f_len,g_len]: if length < 0: print 'Error: somethings wrong, try decreacing -x, -s and/or -g' print 'exiting...' return a1 = 0 a2 = a1 + a_len b1 = a2 b2 = b1 + b_len e1 = b2 e2 = e1 + e_len f1 = e2 f2 = f1 + f_len g1 = f2 g2 = g1 + g_len c1 = g2 c2 = c1 + c_len d1 = c2 #d2 = d1 + d_len d2 = file_length """ print 'c:' + str(c_len) print 'd:' + str(d_len) print 'e:' + str(e_len) print 'f:' + str(f_len) print 'g:' + str(g_len) """ #if (d2 - file_length) > .01 or (d2 - file_length) < -.01: if d2 != file_length: print 'oops, last slice is not ending at sample end:' print 'd2:' + str(d2) print 'file_length:' + str(file_length) print 'difference:' + str(file_length - d2) print 'exiting...' sys.exit() trim2(filename,a,a1,a2) trim2(filename,b,b1,b2) trim2(filename,c,c1,c2) trim2(filename,d,d1,d2) trim2(filename,e,e1,e2) trim2(filename,f,f1,f2) trim2(filename,g,g1,g2) if loop_start > 0: xfade_snip(b,g,bxg,fade) xfade_snip(d,e,dxe,fade) if gap_length > 0: trim2(filename,gap,0,gap_length) mute(gap) cat([a,bxg,gap,c,dxe,f,g],resultFilename) else: cat([a,bxg,c,dxe,f,g],resultFilename) else: xfade_snip(d,e,dxe,fade) cat([c,dxe,f],resultFilename) for file in [a,b,c,d,e,f,g,bxg,dxe,gap]: try: os.remove(file) except: pass def process(): global options parser = optparse.OptionParser() # info infoGroup = optparse.OptionGroup(parser, 'Info','') infoGroup.add_option('-v', '--version', action='store_true', dest='print_version', help='Print version and exit', default=False) infoGroup.add_option('-c', '--changelog', action='store_true', dest='print_changelog', help='Print changelog and exit', default=False) infoGroup.add_option('-l', '--length', action='store_true', dest='length', help='Get length (seconds) of sample', default=False) infoGroup.add_option('-m', '--messages', action='store_true', dest='messages', help='Show messages from sox and ecasound', default=False) # processing processGroup = optparse.OptionGroup(parser, 'Processing','') processGroup.add_option('-x', '--xfade', type='float', dest='xfade', help='Cross fade lenth (seconds). Default: 1 (second)', default=default_xfade) processGroup.add_option('-o', '--overwrite', action="store_true", dest='overwrite', help='Overwrite input file', default=False) processGroup.add_option('-s', '--loopstart', type='float', dest='loopstart', help='Start the loop after this time (seconds). Default: 0', default=0) processGroup.add_option('-g', '--gap', type='float', dest='gapsize', help='Insert a gap this long (in seconds) in the sample just before the loop start. This is useful when the application you intend to load the sample in doesn\'t recognize the loop points written in the sample header. Default: 0', default=0) parser.add_option_group(infoGroup) parser.add_option_group(processGroup) if len(sys.argv) == 1: sys.argv.append('-h') elif '-?' in sys.argv: sys.argv.remove('-?') sys.argv.append('-h') (options, args) = parser.parse_args() if options.print_version: print print_version() elif options.print_changelog: print print_changelog() for file in args: if not os.path.exists(file): print 'file "' + file + '" doesn\'t exist, skipping...' elif options.length: print get_length_seconds(file) sys.exit() else: if not options.overwrite: (root, ext) = os.path.splitext(file) outfile = root + '_loop' + ext else: outfile = file xfade_loop(file,outfile,options.xfade,options.loopstart,options.gapsize) process()