def main(args): from optparse import OptionParser p = OptionParser(description='Grabs avisynth trims and outputs chapter ' 'file, qpfile and/or cuts audio (works with cfr and ' 'vfr input)', version='VFR Chapter Creator 0.10.0', usage='%prog [options] infile.avs [outfile.avs]') p.add_option('--label', '-l', action="store", dest="label", help="Look for a trim() statement or succeeding comment only " "on lines matching LABEL. Default: case insensitive trim") p.add_option('--clip', action="store", dest="clip", help="Look for trims() using specific clip, like" "Trim(ClipX,0,100). Default: any trim") p.add_option('--line', '-g', action="store", type="int", dest="line", help="Specify directly the line used") p.add_option('--input', '-i', action="store", help='Audio file to be cut', dest="input") p.add_option('--output', '-o', action="store", help='Cut audio from MKVMerge', dest="output") p.add_option('--fps', '-f', action="store", help='Frames per second or Timecodes file', dest="fps") p.add_option('--ofps', action="store", help='Output frames per second', dest="ofps") p.add_option('--timecodes', action="store", help='Output v2 timecodes', dest="otc") p.add_option('--chapters', '-c', action="store", help='Chapters file [.{0}/.txt]'.format("/.".join( exts.keys())), dest="chapters") p.add_option('--chnames', '-n', action="store", help='Path to template file for chapter names (utf8 w/o bom)', dest="chnames") p.add_option('--template', '-t', action="store", help="Template file for chapters", dest="template") p.add_option('--uid', action="store", help="Base UID for --template or --chnames", dest="uid") p.add_option('--qpfile', '-q', action="store", help='QPFile for x264', dest="qpfile") p.add_option('--verbose', '-v', action="store_true", help='Verbose', dest="verbose") p.add_option('--merge', '-m', action="store_true", help='Merge cut files', dest="merge") p.add_option('--remove', '-r', action="store_true", help='Remove cut files', dest="remove") p.add_option('--delay', '-d', action="store", help="Set delay of audio (can be negative)", dest="delay") p.add_option('--reverse', '-b', action="store_true", help="Reverse parsing of .avs", dest="reverse") p.add_option('--test', action="store_true", help="Test mode (do not create new files)", dest="test") p.add_option('--IDR', '--idr', action="store_true", help="Set this to make qpfile with IDR frames instead of K frames", dest="IDR") p.add_option('--sbr', action="store_true", help="Set this if inputting an .aac and it's SBR/HE-AAC", dest="sbr") (o, a) = p.parse_args(args) if len(a) < 1: p.error("No avisynth script specified.") if not o.fps: o.fps = default_fps ifps = False else: ifps = True #Determine chapter type if o.chapters: chre = compile("\.({0})$(?i)".format("|".join(exts.keys()))) ret = chre.search(o.chapters) chapter_type = exts[ret.group(1).lower()] if ret else "OGM" else: chapter_type = '' if o.template and o.chnames: p.error("Choose either --chnames or --template, not both.") elif o.template and chapter_type != 'MKV': p.error("--template needs to output to .xml.") if not o.output and o.input: ret = splitext(o.input) o.output = '{0}.cut.mka'.format(ret[0]) if o.verbose: status = "Avisynth file: \t{0}\n".format(a[0]) status += "Label: \t\t{0}\n".format(o.label) if o.label else "" status += "Clip name: \t{0}\n".format(o.clip) if o.clip else "" status += ("Parsing order: \t{0}\n".format("Bottom to top" if o.reverse else "Top to bottom")) status += "Line: \t\t{0}\n".format(o.line) if o.line else "" status += ("Audio file: \t{0}{1}\n".format(o.input, "(SBR)" if o.sbr else "") if o.input else "") status += "Cut Audio file: {0}\n".format(o.output) if o.output else "" status += "Timecodes/FPS: \t{0}{1}\n".format(o.fps, " to " + o.ofps if o.ofps else "") if o.ofps != o.fps else "" status += "Output v2 Tc: \t{0}\n".format(o.otc) if o.otc else "" status += ("Chapters file: \t{0}{1}\n".format(o.chapters, " ({0})".format(chapter_type) if chapter_type else "") if o.chapters else "") status += ("Chapter Names: \t{0}\n".format(o.chnames) if o.chnames else "") status += ("Template file: \t{0}\n".format(o.template) if o.template else "") status += "QP file: \t{0} ({1} frames)\n".format(o.qpfile, 'I' if o.IDR else 'K') if o.qpfile else "" status += "\n" status += ("Merge/Rem files:{0}/{1}\n".format(o.merge, o.remove) if o.merge or o.remove else "") status += ("Verbose: \t{0}\n".format(o.verbose) if o.verbose else "") status += "Test Mode: \t{0}\n".format(o.test) if o.test else "" print(status) # Get frame numbers and corresponding timecodes from avs Trims, Trimsts, Trims2, Trims2ts, audio = parse_trims(a[0], o.fps, o.ofps, o.otc if not o.test else '', o.input, o.label, o.reverse, o.line, o.clip) nt2 = len(Trims2ts) if o.verbose: print('In trims: {0}\n'.format(', '.join(['({0},{1})'.format(i[0], i[1]) for i in Trims]))) print('In timecodes: {0}\n'.format(', '.join(['({0},{1})'.format(i[0], i[1]) for i in Trimsts]))) print('Out trims: {0}\n'.format(', '.join(['({0},{1})'.format(i[0], i[1]) for i in Trims2]))) print('Out timecodes: {0}\n'.format(', '.join(['({0},{1})'.format( fmt_time(i[0]), fmt_time(i[1])) for i in Trims2ts]))) # make qpfile if o.qpfile and not o.template: if not o.test: write_qpfile(o.qpfile, Trims2, o.IDR) if o.verbose: print('Writing keyframes to {0}\n'.format(o.qpfile)) # make audio cuts if o.input: from subprocess import call, check_output from sys import getfilesystemencoding if Trims[0][0] == '0': includefirst = True audio = audio[1:] else: includefirst = False cuttimes = [] # get mkvmerge version get_mkvmerge_version = check_output([mkvmerge, "--version"]).decode() ver = [int(n) for n in get_mkvmerge_version.split()[1][1:].split('.')] parts_able = ver >= [5,6,0] # first version with --split parts if parts_able: if includefirst: cuttimes = ['-{}'.format(audio.pop(0))] if not includefirst and len(audio) == 1: cuttimes = '{}-'.format(audio[0]) else: cuttimes = ',+'.join(cuttimes + ['{}-{}'.format(audio[i], audio[i + 1]) for i in range(0,len(audio),2)]) else: cuttimes = ','.join(audio) max_audio = len(audio) + 2 # get info from mkvmerge ident = check_output([mkvmerge, "--identify-for-mmg", o.input]) identre = compile("Track ID (\d+): audio( \(AAC\) " "\[aac_is_sbr:true\])?") ret = (identre.search(ident.decode(getfilesystemencoding())) if ident else None) tid = ret.group(1) if ret else '0' sbr = ("0:1" if o.sbr or ret.group(2) else "0:0" if o.input.endswith("aac") else "") # determine delay delre = compile('DELAY ([-]?\d+)') ret = delre.search(o.input) delay = ('{0}:{1}'.format(tid, o.delay if o.delay else ret.group(1)) if o.delay or ret else None) cutCmd = [mkvmerge, '-o', o.output] if not parts_able: cutCmd[-1] += '.split.mka' if delay: cutCmd.extend(['--sync', delay]) if sbr: cutCmd.extend(['--aac-is-sbr', sbr]) cutCmd.extend([o.input, '--split']) if parts_able: cutCmd.extend(['parts:' + cuttimes]) else: cutCmd.extend(['timecodes:' + cuttimes]) if o.verbose: print('Cutting: {0}\n'.format( ' '.join(['"{0}"'.format(i) for i in cutCmd]))) else: cutCmd.append('-q') if not o.test: cutExec = call(cutCmd) if cutExec == 1: print("Mkvmerge exited with warnings: {0:d}".format(cutExec)) elif cutExec == 2: exit("Failed to execute mkvmerge: {0:d}".format(cutExec)) if o.merge and not parts_able: merge = [] for i in range(1, max_audio): if ((includefirst == True and i % 2 != 0) or (includefirst == False and i % 2 == 0)): merge.append('{0}{1}.split-{2:03d}.mka'.format('+' if len(merge) > 0 else '', o.output, i)) mergeCmd = [mkvmerge, '-o', o.output] mergeCmd.extend(merge) if o.verbose: print('\nMerging: {0}\n'.format(' '.join(['"{0}"'.format(i) for i in mergeCmd]))) else: mergeCmd.append('-q') if not o.test: mergeExec = call(mergeCmd) if mergeExec == 1: print("Mkvmerge exited with warnings: {0:d}".format( mergeExec)) elif mergeExec == 2: exit("Failed to execute mkvmerge: {0:d}".format(mergeExec)) if o.remove and not parts_able: remove = ['{0}.split-{1:03d}.mka'.format(o.output, i) for i in range(1, max_audio)] if o.verbose: print('\nDeleting: {0}\n'.format(', '.join(remove))) if not o.test: from os import unlink [unlink(i) if isfile(i) else True for i in remove] # make offseted avs if len(a) > 1: try: from chapparse import writeAvisynth fNum = [i[0] for i in Trims2] set = {'avs': '"' + a[1] + '"', 'input': '', 'resize': ''} writeAvisynth(set, fNum) except ImportError: print('Script chapparse.py needed for avisynth output to work.') # write chapters if chapter_type: if chapter_type == 'MKV': Trims2ts = [(fmt_time(i[0]), fmt_time(i[1]) if i[1] != 0 else None) for i in Trims2ts] if o.template: from templates import AutoMKVChapters as amkvc output = o.chapters[:-4] if not o.test else None chaps = amkvc(o.template, output=output, avs=a[0], trims=Trims2ts, kframes=Trims2, uid=o.uid, label=o.label, ifps=ifps, clip=o.clip, idr=o.IDR) else: # Assign names to each chapter if --chnames chapter_names = [] if o.chnames: with open(o.chnames, encoding='utf-8') as f: [chapter_names.append(line.strip()) for line in f.readlines()] if not o.chnames or len(chapter_names) < len(Trims2ts): # The if statement is for clarity; it doesn't actually do # anything useful for i in range(len(chapter_names), len(Trims2ts)): chapter_names.append("Chapter {:02d}".format(i + 1)) if chapter_type == 'MKV': from templates import AutoMKVChapters as amkvc tmp = amkvc.Template() tmp.trims = Trims2ts tmp.kframes = Trims2 if o.qpfile: tmp.qpf = o.qpfile tmp.idr = o.IDR ed = tmp.Edition() ed.default = 1 ed.num_chapters = len(chapter_names) ed.uid = int(o.uid) * 100 if o.uid else tmp.uid * 100 cuid = ed.uid ed.chapters = [] for i in range(len(chapter_names)): ch = tmp.Chapter() cuid += 1 ch.uid = cuid ch.name = [chapter_names[i]] ch.start, ch.end = (Trims2ts[i][0], Trims2ts[i][1]) ed.chapters.append(ch) tmp.editions = [ed] chaps = tmp if not o.test: if chapter_type == 'MKV': chaps.toxml(o.chapters[:-4]) else: with open(o.chapters, "w", encoding='utf-8') as output: if chapter_type == 'OGM': chap = ('CHAPTER{1:02d}={0}\nCHAPTER{1:02d}' 'NAME={2}\n') elif chapter_type == 'X264': chap = '{0} {2}\n' Trims2ts = [fmt_time(i[0], 1) for i in Trims2ts] [output.write(chap.format(Trims2ts[i], i + 1, chapter_names[i])) for i in range(len(Trims2ts))] if o.verbose: print("Writing {} Chapters to {}". format(chapter_type, o.chapters))
def main(args): from optparse import OptionParser p = OptionParser(description='Grabs avisynth trims and outputs chapter ' 'file, qpfile and/or cuts audio (works with cfr and ' 'vfr input)', version='VFR Chapter Creator 0.10.0', usage='%prog [options] infile.avs [outfile.avs]') p.add_option('--label', '-l', action="store", dest="label", help="Look for a trim() statement or succeeding comment only " "on lines matching LABEL. Default: case insensitive trim") p.add_option('--clip', action="store", dest="clip", help="Look for trims() using specific clip, like" "Trim(ClipX,0,100). Default: any trim") p.add_option('--line', '-g', action="store", type="int", dest="line", help="Specify directly the line used") p.add_option('--input', '-i', action="store", help='Audio file to be cut', dest="input") p.add_option('--output', '-o', action="store", help='Cut audio from MKVMerge', dest="output") p.add_option('--fps', '-f', action="store", help='Frames per second or Timecodes file', dest="fps") p.add_option('--ofps', action="store", help='Output frames per second', dest="ofps") p.add_option('--timecodes', action="store", help='Output v2 timecodes', dest="otc") p.add_option('--chapters', '-c', action="store", help='Chapters file [.{0}/.txt]'.format("/.".join( exts.keys())), dest="chapters") p.add_option('--chnames', '-n', action="store", help='Path to template file for chapter names (utf8 w/o bom)', dest="chnames") p.add_option('--template', '-t', action="store", help="Template file for chapters", dest="template") p.add_option('--uid', action="store", help="Base UID for --template or --chnames", dest="uid") p.add_option('--qpfile', '-q', action="store", help='QPFile for x264', dest="qpfile") p.add_option('--verbose', '-v', action="store_true", help='Verbose', dest="verbose") p.add_option('--merge', '-m', action="store_true", help='Merge cut files', dest="merge") p.add_option('--remove', '-r', action="store_true", help='Remove cut files', dest="remove") p.add_option('--delay', '-d', action="store", help="Set delay of audio (can be negative)", dest="delay") p.add_option('--reverse', '-b', action="store_true", help="Reverse parsing of .avs", dest="reverse") p.add_option('--test', action="store_true", help="Test mode (do not create new files)", dest="test") p.add_option( '--IDR', '--idr', action="store_true", help="Set this to make qpfile with IDR frames instead of K frames", dest="IDR") p.add_option('--sbr', action="store_true", help="Set this if inputting an .aac and it's SBR/HE-AAC", dest="sbr") (o, a) = p.parse_args(args) if len(a) < 1: p.error("No avisynth script specified.") if not o.fps: o.fps = default_fps ifps = False else: ifps = True #Determine chapter type if o.chapters: chre = compile("\.({0})$(?i)".format("|".join(exts.keys()))) ret = chre.search(o.chapters) chapter_type = exts[ret.group(1).lower()] if ret else "OGM" else: chapter_type = '' if o.template and o.chnames: p.error("Choose either --chnames or --template, not both.") elif o.template and chapter_type != 'MKV': p.error("--template needs to output to .xml.") if not o.output and o.input: ret = splitext(o.input) o.output = '{0}.cut.mka'.format(ret[0]) if o.verbose: status = "Avisynth file: \t{0}\n".format(a[0]) status += "Label: \t\t{0}\n".format(o.label) if o.label else "" status += "Clip name: \t{0}\n".format(o.clip) if o.clip else "" status += ("Parsing order: \t{0}\n".format( "Bottom to top" if o.reverse else "Top to bottom")) status += "Line: \t\t{0}\n".format(o.line) if o.line else "" status += ("Audio file: \t{0}{1}\n".format( o.input, "(SBR)" if o.sbr else "") if o.input else "") status += "Cut Audio file: {0}\n".format(o.output) if o.output else "" status += "Timecodes/FPS: \t{0}{1}\n".format( o.fps, " to " + o.ofps if o.ofps else "") if o.ofps != o.fps else "" status += "Output v2 Tc: \t{0}\n".format(o.otc) if o.otc else "" status += ("Chapters file: \t{0}{1}\n".format( o.chapters, " ({0})".format(chapter_type) if chapter_type else "") if o.chapters else "") status += ("Chapter Names: \t{0}\n".format(o.chnames) if o.chnames else "") status += ("Template file: \t{0}\n".format(o.template) if o.template else "") status += "QP file: \t{0} ({1} frames)\n".format( o.qpfile, 'I' if o.IDR else 'K') if o.qpfile else "" status += "\n" status += ("Merge/Rem files:{0}/{1}\n".format(o.merge, o.remove) if o.merge or o.remove else "") status += ("Verbose: \t{0}\n".format(o.verbose) if o.verbose else "") status += "Test Mode: \t{0}\n".format(o.test) if o.test else "" print(status) # Get frame numbers and corresponding timecodes from avs Trims, Trimsts, Trims2, Trims2ts, audio = parse_trims( a[0], o.fps, o.ofps, o.otc if not o.test else '', o.input, o.label, o.reverse, o.line, o.clip, o.merge) nt2 = len(Trims2ts) if o.verbose: print('In trims: {0}\n'.format(', '.join( ['({0},{1})'.format(i[0], i[1]) for i in Trims]))) print('In timecodes: {0}\n'.format(', '.join( ['({0},{1})'.format(i[0], i[1]) for i in Trimsts]))) print('Out trims: {0}\n'.format(', '.join( ['({0},{1})'.format(i[0], i[1]) for i in Trims2]))) print('Out timecodes: {0}\n'.format(', '.join([ '({0},{1})'.format(fmt_time(i[0]), fmt_time(i[1])) for i in Trims2ts ]))) # make qpfile if o.qpfile and not o.template: if not o.test: write_qpfile(o.qpfile, Trims2, o.IDR) if o.verbose: print('Writing keyframes to {0}\n'.format(o.qpfile)) # make audio cuts if o.input: split_audio(audio, o.input, o.output, o.delay, o.sbr, o.merge, o.remove, o.verbose, o.test) # make offseted avs if len(a) > 1: try: from chapparse import writeAvisynth fNum = [i[0] for i in Trims2] set = {'avs': '"' + a[1] + '"', 'input': '', 'resize': ''} writeAvisynth(set, fNum) except ImportError: print('Script chapparse.py needed for avisynth output to work.') # write chapters if chapter_type: if chapter_type == 'MKV': Trims2ts = [(fmt_time(i[0]), fmt_time(i[1]) if i[1] != 0 else None) for i in Trims2ts] if o.template: from templates import AutoMKVChapters as amkvc output = o.chapters[:-4] if not o.test else None chaps = amkvc(o.template, output=output, avs=a[0], trims=Trims2ts, kframes=Trims2, uid=o.uid, label=o.label, ifps=ifps, clip=o.clip, idr=o.IDR) else: # Assign names to each chapter if --chnames chapter_names = [] if o.chnames: with open(o.chnames, encoding='utf-8') as f: [ chapter_names.append(line.strip()) for line in f.readlines() ] if not o.chnames or len(chapter_names) < len(Trims2ts): # The if statement is for clarity; it doesn't actually do # anything useful for i in range(len(chapter_names), len(Trims2ts)): chapter_names.append("Chapter {:02d}".format(i + 1)) if chapter_type == 'MKV': from templates import AutoMKVChapters as amkvc tmp = amkvc.Template() tmp.trims = Trims2ts tmp.kframes = Trims2 if o.qpfile: tmp.qpf = o.qpfile tmp.idr = o.IDR ed = tmp.Edition() ed.default = 1 ed.num_chapters = len(chapter_names) ed.uid = int(o.uid) * 100 if o.uid else tmp.uid * 100 cuid = ed.uid ed.chapters = [] for i in range(len(chapter_names)): ch = tmp.Chapter() cuid += 1 ch.uid = cuid ch.name = [chapter_names[i]] ch.start, ch.end = (Trims2ts[i][0], Trims2ts[i][1]) ed.chapters.append(ch) tmp.editions = [ed] chaps = tmp if not o.test: if chapter_type == 'MKV': chaps.toxml(o.chapters[:-4]) else: with open(o.chapters, "w", encoding='utf-8') as output: if chapter_type == 'OGM': chap = ('CHAPTER{1:02d}={0}\nCHAPTER{1:02d}' 'NAME={2}\n') elif chapter_type == 'X264': chap = '{0} {2}\n' Trims2ts = [fmt_time(i[0], 1) for i in Trims2ts] [ output.write( chap.format(Trims2ts[i], i + 1, chapter_names[i])) for i in range(len(Trims2ts)) ] if o.verbose: print("Writing {} Chapters to {}".format(chapter_type, o.chapters))
def main(args): from optparse import OptionParser p = OptionParser(description='Grabs avisynth trims and outputs chapter ' 'file, qpfile and/or cuts audio (works with cfr and ' 'vfr input)', version='VFR Chapter Creator 0.10.0', usage='%prog [options] infile.avs [outfile.avs]') p.add_option('--label', '-l', action="store", dest="label", help="Look for a trim() statement or succeeding comment only " "on lines matching LABEL. Default: case insensitive trim") p.add_option('--clip', action="store", dest="clip", help="Look for trims() using specific clip, like" "Trim(ClipX,0,100). Default: any trim") p.add_option('--line', '-g', action="store", type="int", dest="line", help="Specify directly the line used") p.add_option('--input', '-i', action="store", help='Audio file to be cut', dest="input") p.add_option('--output', '-o', action="store", help='Cut audio from MKVMerge', dest="output") p.add_option('--fps', '-f', action="store", help='Frames per second or Timecodes file', dest="fps") p.add_option('--ofps', action="store", help='Output frames per second', dest="ofps") p.add_option('--timecodes', action="store", help='Output v2 timecodes', dest="otc") p.add_option('--chapters', '-c', action="store", help='Chapters file [.{0}/.txt]'.format("/.".join( exts.keys())), dest="chapters") p.add_option('--chnames', '-n', action="store", help='Path to template file for chapter names (utf8 w/o bom)', dest="chnames") p.add_option('--template', '-t', action="store", help="Template file for chapters", dest="template") p.add_option('--uid', action="store", help="Base UID for --template or --chnames", dest="uid") p.add_option('--qpfile', '-q', action="store", help='QPFile for x264', dest="qpfile") p.add_option('--verbose', '-v', action="store_true", help='Verbose', dest="verbose") p.add_option('--merge', '-m', action="store_true", help='Merge cut files', dest="merge") p.add_option('--remove', '-r', action="store_true", help='Remove cut files', dest="remove") p.add_option('--delay', '-d', action="store", help="Set delay of audio (can be negative)", dest="delay") p.add_option('--reverse', '-b', action="store_true", help="Reverse parsing of .avs", dest="reverse") p.add_option('--test', action="store_true", help="Test mode (do not create new files)", dest="test") p.add_option('--IDR', '--idr', action="store_true", help="Set this to make qpfile with IDR frames instead of K frames", dest="IDR") p.add_option('--sbr', action="store_true", help="Set this if inputting an .aac and it's SBR/HE-AAC", dest="sbr") (o, a) = p.parse_args(args) if len(a) < 1: p.error("No avisynth script specified.") if not o.fps: o.fps = default_fps ifps = False else: ifps = True #Determine chapter type if o.chapters: chre = compile("\.({0})$(?i)".format("|".join(exts.keys()))) ret = chre.search(o.chapters) chapter_type = exts[ret.group(1).lower()] if ret else "OGM" else: chapter_type = '' if o.template and o.chnames: p.error("Choose either --chnames or --template, not both.") elif o.template and chapter_type != 'MKV': p.error("--template needs to output to .xml.") if not o.output and o.input: ret = splitext(o.input) o.output = '{0}.cut.mka'.format(ret[0]) if o.verbose: status = "Avisynth file: \t{0}\n".format(a[0]) status += "Label: \t\t{0}\n".format(o.label) if o.label else "" status += "Clip name: \t{0}\n".format(o.clip) if o.clip else "" status += ("Parsing order: \t{0}\n".format("Bottom to top" if o.reverse else "Top to bottom")) status += "Line: \t\t{0}\n".format(o.line) if o.line else "" status += ("Audio file: \t{0}{1}\n".format(o.input, "(SBR)" if o.sbr else "") if o.input else "") status += "Cut Audio file: {0}\n".format(o.output) if o.output else "" status += "Timecodes/FPS: \t{0}{1}\n".format(o.fps, " to " + o.ofps if o.ofps else "") if o.ofps != o.fps else "" status += "Output v2 Tc: \t{0}\n".format(o.otc) if o.otc else "" status += ("Chapters file: \t{0}{1}\n".format(o.chapters, " ({0})".format(chapter_type) if chapter_type else "") if o.chapters else "") status += ("Chapter Names: \t{0}\n".format(o.chnames) if o.chnames else "") status += ("Template file: \t{0}\n".format(o.template) if o.template else "") status += "QP file: \t{0} ({1} frames)\n".format(o.qpfile, 'I' if o.IDR else 'K') if o.qpfile else "" status += "\n" status += ("Merge/Rem files:{0}/{1}\n".format(o.merge, o.remove) if o.merge or o.remove else "") status += ("Verbose: \t{0}\n".format(o.verbose) if o.verbose else "") status += "Test Mode: \t{0}\n".format(o.test) if o.test else "" print(status) # Get frame numbers and corresponding timecodes from avs Trims, Trimsts, Trims2, Trims2ts, audio = parse_trims(a[0], o.fps, o.ofps, o.otc if not o.test else '', o.input, o.label, o.reverse, o.line, o.clip, o.merge) nt2 = len(Trims2ts) if o.verbose: print('In trims: {0}\n'.format(', '.join(['({0},{1})'.format(i[0], i[1]) for i in Trims]))) print('In timecodes: {0}\n'.format(', '.join(['({0},{1})'.format(i[0], i[1]) for i in Trimsts]))) print('Out trims: {0}\n'.format(', '.join(['({0},{1})'.format(i[0], i[1]) for i in Trims2]))) print('Out timecodes: {0}\n'.format(', '.join(['({0},{1})'.format( fmt_time(i[0]), fmt_time(i[1])) for i in Trims2ts]))) # make qpfile if o.qpfile and not o.template: if not o.test: write_qpfile(o.qpfile, Trims2, o.IDR) if o.verbose: print('Writing keyframes to {0}\n'.format(o.qpfile)) # make audio cuts if o.input: split_audio(audio, o.input, o.output, o.delay, o.sbr, o.merge, o.remove, o.verbose, o.test) # make offseted avs if len(a) > 1: try: from chapparse import writeAvisynth fNum = [i[0] for i in Trims2] set = {'avs': '"' + a[1] + '"', 'input': '', 'resize': ''} writeAvisynth(set, fNum) except ImportError: print('Script chapparse.py needed for avisynth output to work.') # write chapters if chapter_type: if chapter_type == 'MKV': Trims2ts = [(fmt_time(i[0]), fmt_time(i[1]) if i[1] != 0 else None) for i in Trims2ts] if o.template: from templates import AutoMKVChapters as amkvc output = o.chapters[:-4] if not o.test else None chaps = amkvc(o.template, output=output, avs=a[0], trims=Trims2ts, kframes=Trims2, uid=o.uid, label=o.label, ifps=ifps, clip=o.clip, idr=o.IDR) else: # Assign names to each chapter if --chnames chapter_names = [] if o.chnames: with open(o.chnames, encoding='utf-8') as f: [chapter_names.append(line.strip()) for line in f.readlines()] if not o.chnames or len(chapter_names) < len(Trims2ts): # The if statement is for clarity; it doesn't actually do # anything useful for i in range(len(chapter_names), len(Trims2ts)): chapter_names.append("Chapter {:02d}".format(i + 1)) if chapter_type == 'MKV': from templates import AutoMKVChapters as amkvc tmp = amkvc.Template() tmp.trims = Trims2ts tmp.kframes = Trims2 if o.qpfile: tmp.qpf = o.qpfile tmp.idr = o.IDR ed = tmp.Edition() ed.default = 1 ed.num_chapters = len(chapter_names) ed.uid = int(o.uid) * 100 if o.uid else tmp.uid * 100 cuid = ed.uid ed.chapters = [] for i in range(len(chapter_names)): ch = tmp.Chapter() cuid += 1 ch.uid = cuid ch.name = [chapter_names[i]] ch.start, ch.end = (Trims2ts[i][0], Trims2ts[i][1]) ed.chapters.append(ch) tmp.editions = [ed] chaps = tmp if not o.test: if chapter_type == 'MKV': chaps.toxml(o.chapters[:-4]) else: with open(o.chapters, "w", encoding='utf-8') as output: if chapter_type == 'OGM': chap = ('CHAPTER{1:02d}={0}\nCHAPTER{1:02d}' 'NAME={2}\n') elif chapter_type == 'X264': chap = '{0} {2}\n' Trims2ts = [fmt_time(i[0], 1) for i in Trims2ts] [output.write(chap.format(Trims2ts[i], i + 1, chapter_names[i])) for i in range(len(Trims2ts))] if o.verbose: print("Writing {} Chapters to {}". format(chapter_type, o.chapters))
def main(): p = optparse.OptionParser(description='Grabs avisynth trims and outputs chapter file, qpfile and/or cuts audio (works with cfr and vfr input)', prog='vfr.py', version='VFR Chapter Creator 0.6.3', usage='%prog [options] infile.avs') p.add_option('--label', '-l', action="store", help="Look for a trim() statement only on lines matching LABEL, interpreted as a regular expression. Default: case insensitive trim", dest="label") p.add_option('--input', '-i', action="append", help='Audio file to be cut', dest="input") p.add_option('--output', '-o', action="store", help='Cut audio from MKVMerge', dest="output") p.add_option('--fps', '-f', action="store", help='Frames per second (for cfr input)', dest="fps") p.add_option('--timecodes', '-t', action="store", help='Timecodes file from the vfr video (v1 needs tcConv)', dest="timecodes") p.add_option('--chapters', '-c', action="store", help='Chapters file [.xml/.x264.txt/.txt]', dest="chapters") p.add_option('--qpfile', '-q', action="store", help='QPFile for x264 (frame-accurate only if used with final framecount)', dest="qpfile") p.add_option('--verbose', '-v', action="store_true", help='Verbose', dest="verbose") p.add_option('--merge', '-m', action="store_true", help='Merge cut files', dest="merge") p.add_option('--remove', '-r', action="store_true", help='Remove cut files', dest="remove") p.add_option('--frames', action="store", help='Number of frames for v1 conversion', dest="frames") p.add_option('--test', action="store_true", help="Test mode (do not create new files)", dest="test") (options, args) = p.parse_args() if len(args) < 1: p.error("No avisynth script specified.") elif options.timecodes == None and os.path.isfile(args[0] + ".tc.txt") == False and options.fps == None: options.timecodes = '30000/1001' elif options.timecodes == None and os.path.isfile(args[0] + ".tc.txt") == True: options.timecodes = args[0] + ".tc.txt" elif options.timecodes != None and options.fps != None: p.error("Can't use vfr input AND cfr input") elif options.timecodes != None and os.path.isfile(options.timecodes) == True: options.timecodes = options.timecodes else: options.timecodes = options.fps if options.chapters != None: cExt = options.chapters[-3:] if cExt == 'xml': chapType = 'MKV' elif options.chapters[-8:] == 'x264.txt': chapType = 'X264' elif cExt == 'txt': chapType = 'OGM' else: chapType = 'OGM' else: chapType = '' if options.output == None and options.input != None: if len(options.input) != 1: p.error("No output specified."); options.output = '%s.%s.mka' % (options.input[0], args[0][:-4]) if options.verbose == True: status = """ Avisynth file: {input} Label: {label} Audio file: {audio} Cut Audio file: {cutaudio} Timecodes/FPS: {timecodes} Chapters file: {chapters} QP file: {qpfile} Merge/Rem files: {merge}/{remove} Verbose: {verbose} Test Mode: {test} """.format(input=args[0], audio=",".join(options.input), label=options.label, cutaudio=options.output, timecodes=options.timecodes, chapters=options.chapters, qpfile=options.qpfile, merge=options.merge, remove=options.remove, verbose=options.verbose, test=options.test) print(status) quiet = '' if options.verbose == True else '-q' audio = [] Trims = [] with open(args[0], "r") as avs: # use only the first non-commented line with trims if options.label != None: trimre = re.compile("(?<!#)%s\((\d+)\s*,\s*(\d+)\)" % options.label) else: trimre = re.compile("(?<!#)trim\((\d+)\s*,\s*(\d+)\)",re.I) for line in avs: if trimre.match(line) != None: Trims = trimre.findall(line) break if len(Trims) < 1: sys.exit("Error: Avisynth script has no uncommented trims") if options.verbose == True: print('In trims: {}'.format(', '.join(['({},{})'.format(i[0],i[1]) for i in Trims]))) # trims' offset calculation Trims2 = [] Trims2ts = [] options.timecodes = [options.timecodes, determineFormat(options.timecodes)] tc = options.timecodes if tc[1] == 2: nTrims = int(options.frames) if options.frames != None else int(Trims[-1][1])+2 if os.path.isfile(tc[0]+"v2.txt") == False: tcConv = call('"%s" "%s" "%s" %d' % (tcConv, tc[0], tc[0]+"v2.txt", nTrims), shell=True) if tcConv > 0: sys.exit("Failed to execute tcConv: %d; Please put it in your path" % tcConv) options.timecodes[0] = tc[0]+"v2.txt" for i in range(len(Trims)): fn1 = int(Trims[i][0]) # first frame fn1ts = Ts(fn1,tc)[0] # first frame timecode fn2 = int(Trims[i][1]) # last frame fn2ts = Ts(fn2,tc)[0] # last frame timecode fn2tsaud = Ts(fn2+1,tc) # last frame timecode for audio if i != 0: # if it's not the first trim last = int(Trims[i-1][1])+1 lastts = Ts(last,tc)[0] offset += fn1-last offsetts += fn1ts-lastts elif fn1 > 0: # if the first trim doesn't start at 0 offset = fn1 offsetts = fn1ts else: offset = 0 offsetts = 0 # apply the offset to the trims Trims2.append([fn1-offset,fn2-offset]) Trims2ts.append([fn1ts-offsetts,fn2ts-offsetts]) # make list with timecodes to cut audio audio.append(formatTime(fn1ts,tc)) if len(fn2tsaud) == 1: audio.append(formatTime(fn2tsaud[0],tc)) if options.verbose == True: print('Out trims: {}'.format(', '.join(['({},{})'.format(i[0],i[1]) for i in Trims2]))) # make qpfile if options.qpfile != None and options.test == None: with open(options.qpfile, "w") as qpf: for trim in Trims2: qpf.write('%s I -1\n' % trim[0]) # make audio cuts if options.input != None: mkvInput = [] for inputfile in options.input: delay = re.search('DELAY ([-]?\d+)',inputfile).group(1) if re.search('DELAY ([-]?\d+)',inputfile) != None else '0' mkvInput.append('--sync 0:%s "%s"' % (delay, inputfile)) if audio[0] == "00:00:00.000": includefirst = True audio = audio[1:] else: includefirst = False cuttimes = ','.join(audio) cutCmd = '"%s" -o "%s" %s --split timecodes:%s %s' % (mkvmerge, options.output + '.split.mka', ' '.join(mkvInput), cuttimes, quiet) if options.verbose == True: print('Cutting: %s\n' % cutCmd) if options.test == None: cutExec = call(cutCmd, shell=True) if cutExec == 1: print("Mkvmerge exited with warnings: %d" % cutExec) elif cutExec == 2: sys.exit("Failed to execute mkvmerge: %d" % cutExec) if options.merge == True: merge = [] for i in range(1,len(audio)+2): if (includefirst == True and i % 2 != 0) or (includefirst == False and i % 2 == 0): merge.append('"%s.split-%03d.mka"' % (options.output, i)) mergeCmd = '"%s" -o "%s" %s %s' % (mkvmerge,options.output, ' +'.join(merge), quiet) if options.verbose == True: print('Merging: %s\n' % ' +'.join(merge)) if options.test == None: print(mergeCmd) mergeExec = call(mergeCmd, shell=True) if mergeExec == 1: print("Mkvmerge exited with warnings: %d" % mergeExec) elif mergeExec == 2: sys.exit("Failed to execute mkvmerge: %d" % mergeExec) if options.remove == True: remove = ['%s.split-%03d.mka' % (options.output, i) for i in range(1,len(audio)+2)] if options.verbose == True: print() [print('Deleting: '+i) for i in remove] if options.test == None: [os.unlink(i) if os.path.exists(i) else True for i in remove] if chapparseExists == True: # make offseted avs if len(args) > 1: fNum = [i[0] for i in Trims2] set = {'avs':'"'+args[1]+'"','input':'','resize':''} writeAvisynth(set,fNum) if chapType == 'MKV': EditionUID = random.randint(100000,1000000) matroskaXmlHeader = '<?xml version="1.0" encoding="UTF-8"?>\n<!-- <!DOCTYPE Tags SYSTEM "matroskatags.dtd"> -->\n<Chapters>' matroskaXmlEditionHeader = """ <EditionEntry> <EditionFlagHidden>{}</EditionFlagHidden> <EditionFlagDefault>{}</EditionFlagDefault> <EditionFlagOrdered>{}</EditionFlagOrdered> <EditionUID>{}</EditionUID> """.format(0,1,1,EditionUID) matroskaXmlEditionFooter = ' </EditionEntry>' matroskaXmlFooter = '\n</Chapters>' matroskaXmlTagsHeader = '<?xml version="1.0" encoding="UTF-8"?>\n<!-- <!DOCTYPE Tags SYSTEM "matroskatags.dtd"> -->\n<Tags>' matroskaXmlTagsEdition = """ <Tag> <Targets> <EditionUID>{}</EditionUID> <TargetTypeValue>50</TargetTypeValue> </Targets> <Simple> <Name>TITLE</Name> <String>{}</String> <TagLanguage>{}</TagLanguage> <DefaultLanguage>1</DefaultLanguage> </Simple> </Tag>""".format(EditionUID,"Default","eng") if options.test == None and chapType != '': with open(options.chapters, "w") as output: if chapType == 'MKV': output.write(matroskaXmlHeader) output.write(matroskaXmlEditionHeader) [output.write(generateChap(formatTime(Trims2ts[i][0],tc), formatTime(Trims2ts[i][1],tc),i+1,chapType)) for i in range(len(Trims2ts))] output.write(matroskaXmlEditionFooter) output.write(matroskaXmlFooter) else: [output.write(generateChap(formatTime(Trims2ts[i][0],tc), formatTime(Trims2ts[i][1],tc),i+1,chapType)) for i in range(len(Trims2ts))] elif chapType != '': print("Writing {} Chapters to {}".format(chapType,options.chapters))