Example #1
0
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))
Example #2
0
File: vfr.py Project: breakertt/vfr
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))
Example #3
0
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))
Example #4
0
File: vfr.py Project: Xuno/dotfiles
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))