Exemple #1
0
def main():
    par = argparse.ArgumentParser(usage='butterflow [options] [video]',
                                  add_help=False)
    req = par.add_argument_group('Required arguments')
    gen = par.add_argument_group('General options')
    dsp = par.add_argument_group('Display options')
    vid = par.add_argument_group('Video options')
    mux = par.add_argument_group('Muxing options')
    fgr = par.add_argument_group('Advanced options')

    req.add_argument('video', type=str, nargs='?', default=None,
                     help='Specify the input video')

    gen.add_argument('-h', '--help', action='help',
                     help='Show this help message and exit')
    gen.add_argument('--version', action='store_true',
                     help='Show program\'s version number and exit')
    gen.add_argument('-d', '--devices', action='store_true',
                     help='Show detected OpenCL devices and exit')
    gen.add_argument('-sw', action='store_true',
                     help='Set to force software rendering')
    gen.add_argument('-c', '--cache', action='store_true',
                     help='Show cache information and exit')
    gen.add_argument('--rm-cache', action='store_true',
                     help='Set to clear the cache and exit')
    gen.add_argument('-prb', '--probe', action='store_true',
                     help='Show media file information and exit')
    gen.add_argument('-v', '--verbosity', action='count',
                     help='Set to increase output verbosity')
    gen.add_argument('-q', '--quiet', action='store_true',
                     help='Set to suppress console output')

    dsp.add_argument('-p', '--show-preview', action='store_true',
                     help='Set to show video preview')
    dsp.add_argument('-a', '--add-info', action='store_true',
                     help='Set to embed debugging info into the output video')
    dsp.add_argument('-tt', '--text-type',
                     choices=['light', 'dark', 'stroke'],
                     default=settings['text_type'],
                     help='Specify text type for debugging info, '
                     '(default: %(default)s)')
    dsp.add_argument('-mrk', '--mark-frames', action='store_true',
                     help='Set to mark interpolated frames')

    vid.add_argument('-o', '--output-path', type=str,
                     default=settings['out_path'],
                     help='Specify path to the output video')
    vid.add_argument('-r', '--playback-rate', type=str,
                     help='Specify the playback rate as an integer or a float '
                     'Fractional forms are acceptable, e.g., 24/1.001 is the '
                     'same as 23.976. To use a multiple of the source '
                     'video\'s rate, follow a number with `x`, e.g., "2x" '
                     'will double the frame rate. The original rate will be '
                     'used by default if nothing is specified.')
    vid.add_argument('-s', '--subregions', type=str,
                     help='Specify rendering subregions in the form: '
                     '"a=TIME,b=TIME,TARGET=VALUE" where TARGET is either '
                     '`fps`, `dur`, `spd`. Valid TIME syntaxes are [hr:m:s], '
                     '[m:s], [s], [s.xxx], or `end`, which signifies to the '
                     'end the video. You can specify multiple subregions by '
                     'separating them with a colon `:`. A special subregion '
                     'format that conveniently describes the entire clip is '
                     'available in the form: "full,TARGET=VALUE".')
    vid.add_argument('-k', '--keep-subregions', action='store_true',
                     help='Set to render subregions that are not explicitly '
                          'specified')
    vid.add_argument('-vs', '--video-scale', type=str,
                     default=str(settings['video_scale']),
                     help='Specify output video size in the form: '
                     '"WIDTH:HEIGHT" or by using a factor. To keep the '
                     'aspect ratio only specify one component, either width '
                     'or height, and set the other component to -1, '
                     '(default: %(default)s)')
    vid.add_argument('-l', '--lossless', action='store_true',
                     help='Set to use lossless encoding settings')
    vid.add_argument('-sm', '--smooth-motion', action='store_true',
                     help='Set to tune for smooth motion. This mode yields '
                     'artifact-less frames by emphasizing blended frames over '
                     'warping pixels.')

    mux.add_argument('-mux', action='store_true',
                     help='Set to mux the source audio with the output video')

    fgr.add_argument('--fast-pyr', action='store_true',
                     help='Set to use fast pyramids')
    fgr.add_argument('--pyr-scale', type=float,
                     default=settings['pyr_scale'],
                     help='Specify pyramid scale factor, '
                     '(default: %(default)s)')
    fgr.add_argument('--levels', type=int,
                     default=settings['levels'],
                     help='Specify number of pyramid layers, '
                     '(default: %(default)s)')
    fgr.add_argument('--winsize', type=int,
                     default=settings['winsize'],
                     help='Specify averaging window size, '
                     '(default: %(default)s)')
    fgr.add_argument('--iters', type=int,
                     default=settings['iters'],
                     help='Specify number of iterations at each pyramid '
                     'level, (default: %(default)s)')
    fgr.add_argument('--poly-n', type=int,
                     choices=settings['poly_n_choices'],
                     default=settings['poly_n'],
                     help='Specify size of pixel neighborhood, '
                     '(default: %(default)s)')
    fgr.add_argument('--poly-s', type=float,
                     default=settings['poly_s'],
                     help='Specify standard deviation to smooth derivatives, '
                     '(default: %(default)s)')
    fgr.add_argument('-ff', '--flow-filter', choices=['box', 'gaussian'],
                     default=settings['flow_filter'],
                     help='Specify which filter to use for optical flow '
                     'estimation, (default: %(default)s)')

    for i, arg in enumerate(sys.argv):
        if arg[0] == '-' and arg[1].isdigit():
            sys.argv[i] = ' '+arg

    args = par.parse_args()

    fmt = '[butterflow:%(filename)s:%(funcName)s.%(levelname)s]: %(message)s'
    logging.basicConfig(level=settings['loglevel_0'], format=fmt)
    log = logging.getLogger('butterflow')

    if args.verbosity == 1:
        log.setLevel(settings['loglevel_1'])
    if args.verbosity >= 2:
        log.setLevel(settings['loglevel_2'])
    if args.quiet:
        log.setLevel(settings['loglevel_quiet'])
        settings['quiet'] = True

    if args.version:
        print(__version__)
        return 0

    cachedir = settings['tempdir']
    if args.cache:
        nfiles = 0
        sz = 0
        for dirpath, dirnames, filenames in os.walk(cachedir):
            if dirpath == settings['clbdir']:
                continue
            for filename in filenames:
                nfiles += 1
                fp = os.path.join(dirpath, filename)
                sz += os.path.getsize(fp)
        sz = sz / 1024.0**2
        print('{} files, {:.2f} MB'.format(nfiles, sz))
        print('cache @ '+cachedir)
        return 0
    if args.rm_cache:
        if os.path.exists(cachedir):
            import shutil
            shutil.rmtree(cachedir)
        print('cache deleted, done.')
        return 0

    if args.devices:
        ocl.print_ocl_devices()
        return 0

    if not args.video:
        print('no file specified, use: -h for help')
        return 1
    elif not os.path.exists(args.video):
        print('file does not exist')
        return 1

    if args.probe:
        avinfo.print_av_info(args.video)
        return 0

    extension = os.path.splitext(os.path.basename(args.output_path))[1].lower()
    if extension[1:] != 'mp4':
        print('bad out file extension')
        return 0

    av_info = avinfo.get_av_info(args.video)

    use_sw_interpolate = args.sw or not ocl.compat_ocl_device_available()
    if use_sw_interpolate:
        log.warn('not using opencl, ctrl+c to quit')

    if args.flow_filter == 'gaussian':
        args.flow_filter = cv2.OPTFLOW_FARNEBACK_GAUSSIAN
    else:
        args.flow_filter = 0

    if args.smooth_motion:
        args.polys = 0.01

    def optflow_fn(x, y,
                   pyr=args.pyr_scale, levels=args.levels,
                   winsize=args.winsize, iters=args.iters, polyn=args.poly_n,
                   polys=args.poly_s, fast=args.fast_pyr,
                   filt=args.flow_filter):
        if use_sw_interpolate:
            return cv2.calcOpticalFlowFarneback(
                x, y, pyr, levels, winsize, iters, polyn, polys, filt)
        else:
            return motion.ocl_farneback_optical_flow(
                x, y, pyr, levels, winsize, iters, polyn, polys, fast, filt)

    interpolate_fn = None
    if use_sw_interpolate:
        from butterflow.interpolate import sw_interpolate_flow
        interpolate_fn = sw_interpolate_flow
    else:
        interpolate_fn = motion.ocl_interpolate_flow

    try:
        w, h = w_h_from_input_str(args.video_scale, av_info['w'], av_info['h'])
        sequence = sequence_from_input_str(args.subregions,
                                           av_info['duration'],
                                           av_info['frames'])
        rate = rate_from_input_str(args.playback_rate, av_info['rate'])
    except (ValueError, AttributeError) as error:
        print('error: '+str(error))
        return 1

    def nearest_even_int(x):
        return x & ~1

    w1, h1 = av_info['w'], av_info['h']
    w2, h2 = nearest_even_int(w), nearest_even_int(h)

    if w1*h1 > w2*h2:
        scaling_method = settings['scaler_dn']
    elif w1*h1 < w2*h2:
        scaling_method = settings['scaler_up']
    else:
        scaling_method = None

    rnd = Renderer(args.video,
                   args.output_path,
                   sequence,
                   rate,
                   optflow_fn,
                   interpolate_fn,
                   w2,
                   h2,
                   scaling_method,
                   args.lossless,
                   args.keep_subregions,
                   args.show_preview,
                   args.add_info,
                   args.text_type,
                   args.mark_frames,
                   args.mux)

    motion.set_num_threads(settings['ocv_threads'])

    log.info('will render:\n' + str(rnd.sequence))

    success = True
    total_time = 0
    try:
        import timeit
        total_time = timeit.timeit(rnd.render,
                                   setup='import gc;gc.enable()',
                                   number=1)
    except (KeyboardInterrupt, SystemExit):
        success = False
    if success:
        log.info('made: '+args.output_path)
        out_sz = os.path.getsize(args.output_path) / 1024.0**2
        log.info('write ratio: {}/{}, ({:.2f}%) {:.2f} MB'.format(
                 rnd.frs_written,
                 rnd.frs_to_render,
                 rnd.frs_written*100.0/rnd.frs_to_render,
                 out_sz))
        txt = 'frames: {} real, +{} interpolated, +{} dupe, -{} drop'
        if not settings['quiet']:
            print(txt.format(rnd.source_frs,
                             rnd.frs_interpolated,
                             rnd.frs_duped,
                             rnd.frs_dropped))
        log.info('butterflow took {:.3g} mins, done.'.format(total_time / 60))
        return 0
    else:
        log.warn('quit unexpectedly')
        log.warn('files left in cache @ '+settings['tempdir'])
        return 1
Exemple #2
0
def main():
    par = argparse.ArgumentParser(usage='butterflow [options] [video]',
                                  add_help=False)
    req = par.add_argument_group('Required arguments')
    gen = par.add_argument_group('General options')
    dev = par.add_argument_group('Device options')
    dsp = par.add_argument_group('Display options')
    vid = par.add_argument_group('Video options')
    mux = par.add_argument_group('Muxing options')
    fgr = par.add_argument_group('Advanced options')

    req.add_argument('video', type=str, nargs='?', default=None,
                     help='Specify the input video')

    gen.add_argument('-h', '--help', action='help',
                     help='Show this help message and exit')
    gen.add_argument('--version', action='store_true',
                     help='Show program\'s version number and exit')
    gen.add_argument('--cache-dir', type=str,
                     help='Specify path to the cache directory')
    gen.add_argument('-c', '--cache', action='store_true',
                     help='Show cache information and exit')
    gen.add_argument('--rm-cache', action='store_true',
                     help='Set to clear the cache and exit')
    gen.add_argument('-prb', '--probe', action='store_true',
                     help='Show media file information and exit')
    gen.add_argument('-v', '--verbosity', action='count',
                     help='Set to increase output verbosity')
    gen.add_argument('-q', '--quiet', action='store_true',
                     help='Set to suppress console output')

    dev.add_argument('-d', '--show-devices', action='store_true',
                     help='Show detected OpenCL devices and exit')
    dev.add_argument('-device', type=int,
                     default=-1,
                     help='Specify the preferred OpenCL device to use as an '
                     'integer. Device numbers can be listed with the `-d` '
                     'option. The device will be chosen automatically if '
                     'nothing is specified.')
    dev.add_argument('-sw', action='store_true',
                     help='Set to force software rendering')

    dsp.add_argument('-p', '--show-preview', action='store_true',
                     help='Set to show video preview')
    dsp.add_argument('-e', '--embed-info', action='store_true',
                     help='Set to embed debugging info into the output video')
    dsp.add_argument('-tt', '--text-type',
                     choices=['light', 'dark', 'stroke'],
                     default=settings['text_type'],
                     help='Specify text type for embedded debugging info, '
                     '(default: %(default)s)')
    dsp.add_argument('-m', '--mark-frames', action='store_true',
                     help='Set to mark interpolated frames')

    vid.add_argument('-o', '--output-path', type=str,
                     default=settings['out_path'],
                     help='Specify path to the output video')
    vid.add_argument('-r', '--playback-rate', type=str,
                     help='Specify the playback rate as an integer or a float.'
                     ' Fractional forms are acceptable, e.g., 24/1.001 is the '
                     'same as 23.976. To use a multiple of the source '
                     'video\'s rate, follow a number with `x`, e.g., "2x" '
                     'will double the frame rate. The original rate will be '
                     'used by default if nothing is specified.')
    vid.add_argument('-s', '--subregions', type=str,
                     help='Specify rendering subregions in the form: '
                     '"a=TIME,b=TIME,TARGET=VALUE" where TARGET is either '
                     '`spd`, `dur`, `fps`. Valid TIME syntaxes are [hr:m:s], '
                     '[m:s], [s], [s.xxx], or `end`, which signifies to the '
                     'end the video. You can specify multiple subregions by '
                     'separating them with a colon `:`. A special subregion '
                     'format that conveniently describes the entire clip is '
                     'available in the form: "full,TARGET=VALUE".')
    vid.add_argument('-k', '--keep-subregions', action='store_true',
                     help='Set to render subregions that are not explicitly '
                          'specified')
    vid.add_argument('-vs', '--video-scale', type=str,
                     default=str(settings['video_scale']),
                     help='Specify output video size in the form: '
                     '"WIDTH:HEIGHT" or by using a factor. To keep the '
                     'aspect ratio only specify one component, either width '
                     'or height, and set the other component to -1, '
                     '(default: %(default)s)')
    vid.add_argument('-l', '--lossless', action='store_true',
                     help='Set to use lossless encoding settings')
    vid.add_argument('-sm', '--smooth-motion', action='store_true',
                     help='Set to tune for smooth motion. This mode yields '
                     'artifact-less frames by emphasizing blended frames over '
                     'warping pixels.')

    mux.add_argument('-audio', action='store_true',
                     help='Set to add the source audio to the output video')

    fgr.add_argument('--fast-pyr', action='store_true',
                     help='Set to use fast pyramids')
    fgr.add_argument('--pyr-scale', type=float,
                     default=settings['pyr_scale'],
                     help='Specify pyramid scale factor, '
                     '(default: %(default)s)')
    fgr.add_argument('--levels', type=int,
                     default=settings['levels'],
                     help='Specify number of pyramid layers, '
                     '(default: %(default)s)')
    fgr.add_argument('--winsize', type=int,
                     default=settings['winsize'],
                     help='Specify averaging window size, '
                     '(default: %(default)s)')
    fgr.add_argument('--iters', type=int,
                     default=settings['iters'],
                     help='Specify number of iterations at each pyramid '
                     'level, (default: %(default)s)')
    fgr.add_argument('--poly-n', type=int,
                     choices=settings['poly_n_choices'],
                     default=settings['poly_n'],
                     help='Specify size of pixel neighborhood, '
                     '(default: %(default)s)')
    fgr.add_argument('--poly-s', type=float,
                     default=settings['poly_s'],
                     help='Specify standard deviation to smooth derivatives, '
                     '(default: %(default)s)')
    fgr.add_argument('-ff', '--flow-filter', choices=['box', 'gaussian'],
                     default=settings['flow_filter'],
                     help='Specify which filter to use for optical flow '
                     'estimation, (default: %(default)s)')

    for i, arg in enumerate(sys.argv):
        if arg[0] == '-' and arg[1].isdigit():
            sys.argv[i] = ' '+arg
    args = par.parse_args()

    format = '[butterflow:%(levelname)s]: %(message)s'

    logging.basicConfig(level=settings['loglevel_0'], format=format)
    log = logging.getLogger('butterflow')

    if args.verbosity == 1:
        log.setLevel(settings['loglevel_1'])
    if args.verbosity >= 2:
        log.setLevel(settings['loglevel_2'])

    if args.quiet:
        log.setLevel(settings['loglevel_quiet'])
        settings['quiet'] = True

    if args.version:
        print(__version__)
        return 0

    if args.cache_dir is not None:
        cachedir = os.path.normpath(args.cache_dir)
        if os.path.exists(cachedir):
            if not os.path.isdir(cachedir):
                print('Cache path is not a directory')
                return 1
        else:
            os.makedirs(cachedir)
        settings['tempdir'] = cachedir
        settings['clbdir'] = os.path.join(cachedir, 'clb')
        if not os.path.exists(settings['clbdir']):
            os.makedirs(settings['clbdir'])
        ocl.set_cache_path(settings['clbdir'] + os.sep)

    cachedir = settings['tempdir']

    cachedirs = []
    tempfolder = os.path.dirname(cachedir)
    for dirpath, dirnames, filenames in os.walk(tempfolder):
        for d in dirnames:
            if 'butterflow' in d:
                if 'butterflow-'+__version__ not in d:
                    cachedirs.append(os.path.join(dirpath, d))
        break

    if args.cache:
        nfiles = 0
        sz = 0
        for dirpath, dirnames, filenames in os.walk(cachedir):
            if dirpath == settings['clbdir']:
                continue
            for filename in filenames:
                nfiles += 1
                fp = os.path.join(dirpath, filename)
                sz += os.path.getsize(fp)
        sz = sz / 1024.0**2
        print('{} files, {:.2f} MB'.format(nfiles, sz))
        print('Cache: '+cachedir)
        return 0
    if args.rm_cache:
        cachedirs.append(cachedir)
        for i, x in enumerate(cachedirs):
            print('[{}] {}'.format(i, x))
        choice = raw_input('Remove these directories? [y/N] ')
        if choice != 'y':
            print('Leaving the cache alone, done.')
            return 0
        for x in cachedirs:
            if os.path.exists(x):
                import shutil
                shutil.rmtree(x)
        print('Cache deleted, done.')
        return 0

    if args.show_devices:
        ocl.print_ocl_devices()
        return 0

    if not args.video:
        print('No file specified')
        return 1
    elif not os.path.exists(args.video):
        print('File doesn\'t exist')
        return 1

    if args.probe:
        avinfo.print_av_info(args.video)
        return 0

    av_info = avinfo.get_av_info(args.video)
    if av_info['frames'] == 0:
        print('Bad file with 0 frames')
        return 1

    extension = os.path.splitext(os.path.basename(args.output_path))[1].lower()
    if extension[1:] != settings['v_container']:
        print('Bad output file extension. Must be {}.'.format(
              settings['v_container'].upper()))
        return 0

    if not args.sw and not ocl.compat_ocl_device_available():
        print('No compatible OpenCL devices were detected. Must force software '
              'rendering with the `-sw` flag to continue.')
        return 1

    log.info('Version '+__version__)
    log.info('Cache directory:\t%s' % cachedir)

    for x in cachedirs:
        log.warn('Stale cache directory (delete with `--rm-cache`): %s' % x)

    if not args.sw and ocl.compat_ocl_device_available():
        log.info('At least one compatible OpenCL device was detected')
    else:
        log.warning('No compatible OpenCL devices were detected.')

    if args.device != -1:
        try:
            ocl.select_ocl_device(args.device)
        except IndexError as error:
            print('Error: '+str(error))
            return 1
        except ValueError:
            if not args.sw:
                print('An incompatible device was selected.\n'
                      'Must force software rendering with the `-sw` flag to continue.')
                return 1

    s = "Using device: %s"
    if args.device == -1:
        s += " (autoselected)"
    log.info(s % ocl.get_current_ocl_device_name())

    use_sw_interpolate = args.sw

    if args.flow_filter == 'gaussian':
        args.flow_filter = cv2.OPTFLOW_FARNEBACK_GAUSSIAN
    else:
        args.flow_filter = 0
    if args.smooth_motion:
        args.poly_s = 0.01

    def optflow_fn(x, y,
                   pyr=args.pyr_scale, levels=args.levels,
                   winsize=args.winsize, iters=args.iters, polyn=args.poly_n,
                   polys=args.poly_s, fast=args.fast_pyr,
                   filt=args.flow_filter):
        if use_sw_interpolate:
            return cv2.calcOpticalFlowFarneback(
                x, y, pyr, levels, winsize, iters, polyn, polys, filt)
        else:
            return motion.ocl_farneback_optical_flow(
                x, y, pyr, levels, winsize, iters, polyn, polys, fast, filt)

    interpolate_fn = None
    if use_sw_interpolate:
        from butterflow.interpolate import sw_interpolate_flow
        interpolate_fn = sw_interpolate_flow
        log.warn("Hardware acceleration is disabled. Rendering will be slow. "
                 "Do Ctrl+c to quit or suspend the process with Ctrl+z and "
                 "then stop it with `kill %1`, etc. You can list suspended "
                 "processes with `jobs`.)")
    else:
        interpolate_fn = motion.ocl_interpolate_flow
        log.info("Hardware acceleration is enabled")

    try:
        w, h = w_h_from_input_str(args.video_scale, av_info['w'], av_info['h'])
        sequence = sequence_from_input_str(args.subregions,
                                           av_info['duration'],
                                           av_info['frames'])
        rate = rate_from_input_str(args.playback_rate, av_info['rate'])
    except (ValueError, AttributeError) as error:
        print('Error: '+str(error))
        return 1

    def nearest_even_int(x, tag=""):
        new_x = x & ~1
        if x != new_x:
            log.warn("%s: %d is not divisible by 2, setting to %d",
                     tag, x, new_x)
        return new_x

    w1, h1 = av_info['w'], av_info['h']
    w2 = nearest_even_int(w, "W")
    if w2 > 256:
        if w2 % 4 > 0:
            old_w2 = w2
            w2 -= 2
            w2 = max(w2, 0)
            log.warn('W: %d > 256 but is not divisible by 4, setting to %d',
                     old_w2, w2)
    h2 = nearest_even_int(h, "H")

    if w1*h1 > w2*h2:
        scaling_method = settings['scaler_dn']
    elif w1*h1 < w2*h2:
        scaling_method = settings['scaler_up']
    else:
        scaling_method = None

    rnd = Renderer(args.video,
                   args.output_path,
                   sequence,
                   rate,
                   optflow_fn,
                   interpolate_fn,
                   w2,
                   h2,
                   scaling_method,
                   args.lossless,
                   args.keep_subregions,
                   args.show_preview,
                   args.embed_info,
                   args.text_type,
                   args.mark_frames,
                   args.audio)

    ocl.set_num_threads(settings['ocv_threads'])

    log.info('Rendering:')
    added_rate = False
    for x in str(rnd.sequence).split('\n'):
        x = x.strip()
        if not added_rate:
            x += ', Rate={}'.format(av_info['rate'])
            log.info(x)
            added_rate = True
            continue
        if not args.keep_subregions and 'autogenerated' in x:
            log.info(x[:-1]+ ', will skip when rendering)')
            continue
        log.info(x)


    temp_subs = rnd.sequence.subregions
    for x in rnd.sequence.subregions:
        overlaps = False
        for y in temp_subs:
            if x is y:
                continue
            elif x.intersects(y):
                overlaps = True
                break
        if overlaps:
            log.warn('At least 1 subregion overlaps with another')
            break

    success = True
    total_time = 0
    try:
        import timeit
        total_time = timeit.timeit(rnd.render,
                                   setup='import gc;gc.enable()',
                                   number=1)
    except (KeyboardInterrupt, SystemExit):
        success = False
    if success:
        log_function = log.info
        if rnd.frs_written > rnd.frs_to_render:
            log_function = log.warn
            log.warn('Unexpected write ratio')
        log_function('Write ratio: {}/{}, ({:.2f}%)'.format(
                 rnd.frs_written,
                 rnd.frs_to_render,
                 rnd.frs_written*100.0/rnd.frs_to_render))
        txt = 'Final output frames: {} source, +{} interpolated, +{} duped, -{} dropped'
        if not settings['quiet']:
            log.info(txt.format(rnd.source_frs,
                                rnd.frs_interpolated,
                                rnd.frs_duped,
                                rnd.frs_dropped))
        old_sz = os.path.getsize(args.video) / 1024.0
        new_sz = os.path.getsize(args.output_path) / 1024.0
        log.info('Output file size:\t{:.2f} kB ({:.2f} kB)'.format(new_sz,
                 new_sz - old_sz))
        log.info('Rendering took {:.3g} mins, done.'.format(total_time / 60))
        return 0
    else:
        log.warn('Quit unexpectedly')
        log.warn('Files were left in the cache @ '+settings['tempdir']+'.')
        return 1
Exemple #3
0
def main():
    par = argparse.ArgumentParser(usage='butterflow [options] [video]',
                                  add_help=False)
    req = par.add_argument_group('Required arguments')
    gen = par.add_argument_group('General options')
    dsp = par.add_argument_group('Display options')
    vid = par.add_argument_group('Video options')
    mux = par.add_argument_group('Muxing options')
    fgr = par.add_argument_group('Advanced options')

    req.add_argument('video',
                     type=str,
                     nargs='?',
                     default=None,
                     help='Specify the input video')

    gen.add_argument('-h',
                     '--help',
                     action='help',
                     help='Show this help message and exit')
    gen.add_argument('--version',
                     action='store_true',
                     help='Show program\'s version number and exit')
    gen.add_argument('-d',
                     '--devices',
                     action='store_true',
                     help='Show detected OpenCL devices and exit')
    gen.add_argument('-sw',
                     action='store_true',
                     help='Set to force software rendering')
    gen.add_argument('-c',
                     '--cache',
                     action='store_true',
                     help='Show cache information and exit')
    gen.add_argument('--rm-cache',
                     action='store_true',
                     help='Set to clear the cache and exit')
    gen.add_argument('-prb',
                     '--probe',
                     action='store_true',
                     help='Show media file information and exit')
    gen.add_argument('-v',
                     '--verbose',
                     action='store_true',
                     help='Set to increase output verbosity')

    dsp.add_argument('-np',
                     '--no-preview',
                     action='store_false',
                     help='Set to disable video preview')
    dsp.add_argument('-a',
                     '--add-info',
                     action='store_true',
                     help='Set to embed debugging info into the output '
                     'video')
    dsp.add_argument('-tt',
                     '--text-type',
                     choices=['light', 'dark', 'stroke'],
                     default=settings.default['text_type'],
                     help='Specify text type for debugging info, '
                     '(default: %(default)s)')
    dsp.add_argument('-mrk',
                     '--mark-frames',
                     action='store_true',
                     help='Set to mark interpolated frames')

    vid.add_argument('-o',
                     '--output-path',
                     type=str,
                     default=settings.default['out_path'],
                     help='Specify path to the output video')
    vid.add_argument('-r',
                     '--playback-rate',
                     type=str,
                     help='Specify the playback rate as an integer or a '
                     'float. Fractional forms are acceptable. To use a '
                     'multiple of the source video\'s rate, follow a number '
                     'with `x`, e.g., "2x" will double the frame rate. The '
                     'original rate will be used by default if nothing is '
                     'specified.')
    vid.add_argument('-s',
                     '--sub-regions',
                     type=str,
                     help='Specify rendering subregions in the form: '
                     '"a=TIME,b=TIME,TARGET=VALUE" where TARGET is either '
                     '`fps`, `dur`, `spd`. Valid TIME syntaxes are [hr:m:s], '
                     '[m:s], [s], [s.xxx], or `end`, which signifies to the '
                     'end the video. You can specify multiple subregions by '
                     'separating them with a colon `:`. A special region '
                     'format that conveniently describes the entire clip is '
                     'available in the form: "full,TARGET=VALUE".')
    vid.add_argument('-t',
                     '--trim-regions',
                     action='store_true',
                     help='Set to trim subregions that are not explicitly '
                     'specified')
    vid.add_argument('-vs',
                     '--video-scale',
                     type=str,
                     default=str(settings.default['video_scale']),
                     help='Specify output video size in the form: '
                     '"WIDTH:HEIGHT" or by using a factor. To keep the '
                     'aspect ratio only specify one component, either width '
                     'or height, and set the other component to -1, '
                     '(default: %(default)s)')
    vid.add_argument('-l',
                     '--lossless',
                     action='store_true',
                     help='Set to use lossless encoding settings')
    vid.add_argument('-sm',
                     '--smooth-motion',
                     action='store_true',
                     help='Set to tune for smooth motion. This mode favors '
                     'accurate and artifact-less frames above all and will '
                     'emphasize blending frames over warping pixels.')

    mux.add_argument('-mux',
                     action='store_true',
                     help='Set to mux the source audio with the output video')

    fgr.add_argument('--fast-pyr',
                     action='store_true',
                     help='Set to use fast pyramids')
    fgr.add_argument('--pyr-scale',
                     type=float,
                     default=settings.default['pyr_scale'],
                     help='Specify pyramid scale factor, '
                     '(default: %(default)s)')
    fgr.add_argument('--levels',
                     type=int,
                     default=settings.default['levels'],
                     help='Specify number of pyramid layers, '
                     '(default: %(default)s)')
    fgr.add_argument('--winsize',
                     type=int,
                     default=settings.default['winsize'],
                     help='Specify averaging window size, '
                     '(default: %(default)s)')
    fgr.add_argument('--iters',
                     type=int,
                     default=settings.default['iters'],
                     help='Specify number of iterations at each pyramid '
                     'level, (default: %(default)s)')
    fgr.add_argument('--poly-n',
                     type=int,
                     choices=settings.default['poly_n_choices'],
                     default=settings.default['poly_n'],
                     help='Specify size of pixel neighborhood, '
                     '(default: %(default)s)')
    fgr.add_argument('--poly-s',
                     type=float,
                     default=settings.default['poly_s'],
                     help='Specify standard deviation to smooth derivatives, '
                     '(default: %(default)s)')
    fgr.add_argument('-ff',
                     '--flow-filter',
                     choices=['box', 'gaussian'],
                     default=settings.default['flow_filter'],
                     help='Specify which filter to use for optical flow '
                     'estimation, (default: %(default)s)')

    # add a space to args that start with a `-` char to avoid an unexpected
    # argument error. needed for the `--video-scale` option
    for i, arg in enumerate(sys.argv):
        if (arg[0] == '-') and arg[1].isdigit():
            sys.argv[i] = ' ' + arg

    args = par.parse_args()

    import logging
    logging.basicConfig(level=settings.default['loglevel_a'],
                        format='%(levelname)-7s: %(message)s')

    log = logging.getLogger('butterflow')
    if args.verbose:
        log.setLevel(settings.default['loglevel_b'])

    if args.version:
        from butterflow.__init__ import __version__
        print('butterflow version %s' % __version__)
        return 0

    if args.cache:
        print_cache_info()
        return 0

    if args.rm_cache:
        rm_cache()
        print('cache deleted, done.')
        return 0

    if args.devices:
        ocl.print_ocl_devices()
        return 0

    if args.video is None:
        print(NO_VIDEO_SPECIFIED)
        return 1

    if not os.path.exists(args.video):
        print('Error: file does not exist at path')
        return 1

    if args.probe:
        if args.video:
            try:
                avinfo.print_av_info(args.video)
            except Exception as e:
                print('Error: %s' % e)
        else:
            print(NO_VIDEO_SPECIFIED)
        return 0

    try:
        vid_info = avinfo.get_av_info(args.video)
    except Exception as e:
        print('Error: %s' % e)
        return 1

    if not vid_info['v_stream_exists']:
        print('Error: no video stream detected')
        return 1

    try:
        sequence = sequence_from_str(vid_info['duration'], vid_info['frames'],
                                     args.sub_regions)
    except Exception as e:
        print('Bad subregion string: %s' % e)
        return 1

    src_rate = (vid_info['rate_n'] * 1.0 / vid_info['rate_d'])
    try:
        rate = rate_from_str(args.playback_rate, src_rate)
    except Exception as e:
        print('Bad playback rate: %s' % e)
        return 1
    if rate < src_rate:
        log.warning('rate=%s < src_rate=%s', rate, src_rate)

    try:
        w, h = w_h_from_str(args.video_scale, vid_info['w'], vid_info['h'])
    except Exception as e:
        print('Bad video scale: %s' % e)
        return 1

    have_ocl = ocl.compat_ocl_device_available()
    use_sw = args.sw or not have_ocl

    if use_sw:
        log.warning('not using opencl accelerated methods')

    # make functions that will generate flows and interpolate frames
    from cv2 import calcOpticalFlowFarneback as sw_optical_flow
    farneback_method = sw_optical_flow if use_sw else \
        motion.ocl_farneback_optical_flow
    flags = 0
    if args.flow_filter == 'gaussian':
        import cv2
        flags = cv2.OPTFLOW_FARNEBACK_GAUSSIAN

    if args.smooth_motion:
        args.poly_s = 0.01

    # don't make the function with `lambda` because `draw_debug_text` will need
    # to retrieve kwargs with the `inspect` module
    def flow_function(x,
                      y,
                      pyr=args.pyr_scale,
                      l=args.levels,
                      w=args.winsize,
                      i=args.iters,
                      polyn=args.poly_n,
                      polys=args.poly_s,
                      fast=args.fast_pyr,
                      filt=flags):
        if farneback_method == motion.ocl_farneback_optical_flow:
            return farneback_method(x, y, pyr, l, w, i, polyn, polys, fast,
                                    filt)
        else:
            return farneback_method(x, y, pyr, l, w, i, polyn, polys, filt)

    from butterflow.interpolate import sw_interpolate_flow
    interpolate_function = sw_interpolate_flow if use_sw else \
        motion.ocl_interpolate_flow

    renderer = Renderer(args.output_path, vid_info, sequence, rate,
                        flow_function, interpolate_function, w, h,
                        args.lossless, args.trim_regions, args.no_preview,
                        args.add_info, args.text_type, args.mark_frames,
                        args.mux)

    motion.set_num_threads(settings.default['ocv_threads'])

    try:
        # time how long it takes to render. timeit will turn off gc, must turn
        # it back on to maximize performance re-nable it in the setup function
        import timeit
        tot_time = timeit.timeit(renderer.render,
                                 setup='import gc;gc.enable()',
                                 number=1)  # only run once
        tot_time /= 60  # time in minutes

        new_sz = human_sz(float(os.path.getsize(args.output_path)))

        print('{} real, {} interpolated, {} duped, {} dropped'.format(
            renderer.tot_src_frs, renderer.tot_frs_int, renderer.tot_frs_dup,
            renderer.tot_frs_drp))
        print('write ratio: {}/{}, ({:.2f}%) {}'.format(
            renderer.tot_frs_wrt, renderer.tot_tgt_frs,
            renderer.tot_frs_wrt * 100.0 / renderer.tot_tgt_frs, new_sz))
        print('butterflow took {:.3g} minutes, done.'.format(tot_time))
    except (KeyboardInterrupt, SystemExit):
        log.warning('stopped early, files were left in the cache')
        log.warning('recoverable @ {}'.format(settings.default['tmp_dir']))
        return 1
    except Exception:
        return 1
Exemple #4
0
def main():
    par = argparse.ArgumentParser(usage='butterflow [options] [video]',
                                  add_help=False)
    req = par.add_argument_group('Required arguments')
    gen = par.add_argument_group('General options')
    dsp = par.add_argument_group('Display options')
    vid = par.add_argument_group('Video options')
    mux = par.add_argument_group('Muxing options')
    fgr = par.add_argument_group('Advanced options')

    req.add_argument('video',
                     type=str,
                     nargs='?',
                     default=None,
                     help='Specify the input video')

    gen.add_argument('-h',
                     '--help',
                     action='help',
                     help='Show this help message and exit')
    gen.add_argument('--version',
                     action='store_true',
                     help='Show program\'s version number and exit')
    gen.add_argument('-d',
                     '--devices',
                     action='store_true',
                     help='Show detected OpenCL devices and exit')
    gen.add_argument('-sw',
                     action='store_true',
                     help='Set to force software rendering')
    gen.add_argument('-c',
                     '--cache',
                     action='store_true',
                     help='Show cache information and exit')
    gen.add_argument('--rm-cache',
                     action='store_true',
                     help='Set to clear the cache and exit')
    gen.add_argument('-prb',
                     '--probe',
                     action='store_true',
                     help='Show media file information and exit')
    gen.add_argument('-v',
                     '--verbose',
                     action='store_true',
                     help='Set to increase output verbosity')

    dsp.add_argument('-np',
                     '--no-preview',
                     action='store_false',
                     help='Set to disable video preview')
    dsp.add_argument('-a',
                     '--add-info',
                     action='store_true',
                     help='Set to embed debugging info into the output video')
    dsp.add_argument('-tt',
                     '--text-type',
                     choices=['light', 'dark', 'stroke'],
                     default=settings['text_type'],
                     help='Specify text type for debugging info, '
                     '(default: %(default)s)')
    dsp.add_argument('-mrk',
                     '--mark-frames',
                     action='store_true',
                     help='Set to mark interpolated frames')

    vid.add_argument('-o',
                     '--output-path',
                     type=str,
                     default=settings['out_path'],
                     help='Specify path to the output video')
    vid.add_argument('-r',
                     '--playback-rate',
                     type=str,
                     help='Specify the playback rate as an integer or a '
                     'float. Fractional forms are acceptable. To use a '
                     'multiple of the source video\'s rate, follow a number '
                     'with `x`, e.g., "2x" will double the frame rate. The '
                     'original rate will be used by default if nothing is '
                     'specified.')
    vid.add_argument('-s',
                     '--subregions',
                     type=str,
                     help='Specify rendering subregions in the form: '
                     '"a=TIME,b=TIME,TARGET=VALUE" where TARGET is either '
                     '`fps`, `dur`, `spd`. Valid TIME syntaxes are [hr:m:s], '
                     '[m:s], [s], [s.xxx], or `end`, which signifies to the '
                     'end the video. You can specify multiple subregions by '
                     'separating them with a colon `:`. A special subregion '
                     'format that conveniently describes the entire clip is '
                     'available in the form: "full,TARGET=VALUE".')
    vid.add_argument('-t',
                     '--trim-subregions',
                     action='store_true',
                     help='Set to trim subregions that are not explicitly '
                     'specified')
    vid.add_argument('-vs',
                     '--video-scale',
                     type=str,
                     default=str(settings['video_scale']),
                     help='Specify output video size in the form: '
                     '"WIDTH:HEIGHT" or by using a factor. To keep the '
                     'aspect ratio only specify one component, either width '
                     'or height, and set the other component to -1, '
                     '(default: %(default)s)')
    vid.add_argument('-l',
                     '--lossless',
                     action='store_true',
                     help='Set to use lossless encoding settings')
    vid.add_argument('-sm',
                     '--smooth-motion',
                     action='store_true',
                     help='Set to tune for smooth motion. This mode favors '
                     'accurate and artifact-less frames above all and will '
                     'emphasize blending frames over warping pixels.')

    mux.add_argument('-mux',
                     action='store_true',
                     help='Set to mux the source audio with the output video')

    fgr.add_argument('--fast-pyr',
                     action='store_true',
                     help='Set to use fast pyramids')
    fgr.add_argument('--pyr-scale',
                     type=float,
                     default=settings['pyr_scale'],
                     help='Specify pyramid scale factor, '
                     '(default: %(default)s)')
    fgr.add_argument('--levels',
                     type=int,
                     default=settings['levels'],
                     help='Specify number of pyramid layers, '
                     '(default: %(default)s)')
    fgr.add_argument('--winsize',
                     type=int,
                     default=settings['winsize'],
                     help='Specify averaging window size, '
                     '(default: %(default)s)')
    fgr.add_argument('--iters',
                     type=int,
                     default=settings['iters'],
                     help='Specify number of iterations at each pyramid '
                     'level, (default: %(default)s)')
    fgr.add_argument('--poly-n',
                     type=int,
                     choices=settings['poly_n_choices'],
                     default=settings['poly_n'],
                     help='Specify size of pixel neighborhood, '
                     '(default: %(default)s)')
    fgr.add_argument('--poly-s',
                     type=float,
                     default=settings['poly_s'],
                     help='Specify standard deviation to smooth derivatives, '
                     '(default: %(default)s)')
    fgr.add_argument('-ff',
                     '--flow-filter',
                     choices=['box', 'gaussian'],
                     default=settings['flow_filter'],
                     help='Specify which filter to use for optical flow '
                     'estimation, (default: %(default)s)')

    for i, arg in enumerate(sys.argv):
        if arg[0] == '-' and arg[1].isdigit():  # accept args w/ - for -vs
            sys.argv[i] = ' ' + arg

    args = par.parse_args()

    logging.basicConfig(level=settings['loglevel_a'],
                        format='[butterflow.%(levelname)s]: %(message)s')
    log = logging.getLogger('butterflow')
    if args.verbose:
        log.setLevel(settings['loglevel_b'])

    if args.version:
        from butterflow.__init__ import __version__
        print('butterflow version {}'.format(__version__))
        return 0

    cachedir = settings['tempdir']
    if args.cache:
        nfiles = 0
        sz = 0
        for dirpath, dirnames, fnames in os.walk(cachedir):
            if dirpath == settings['clbdir']:
                continue
            for fname in fnames:
                nfiles += 1
                fp = os.path.join(dirpath, fname)
                sz += os.path.getsize(fp)
        sz = sz / 1024.0**2
        print('{} files, {:.2f} MB'.format(nfiles, sz))
        print('cache @ ' + cachedir)
        return 0
    if args.rm_cache:
        if os.path.exists(cachedir):
            import shutil
            shutil.rmtree(cachedir)
        print('cache deleted, done.')
        return 0

    if args.devices:
        ocl.print_ocl_devices()
        return 0

    if not args.video:
        print('no file specified')
        return 1
    elif not os.path.exists(args.video):
        print('file does not exist')
        return 1

    if args.probe:
        avinfo.print_av_info(args.video)
        return 0

    av_info = avinfo.get_av_info(args.video)

    if args.flow_filter == 'gaussian':
        args.flow_filter = cv2.OPTFLOW_FARNEBACK_GAUSSIAN
    else:
        args.flow_filter = 0

    if args.smooth_motion:
        args.polys = 0.01

    use_sw_inter = args.sw or not ocl.compat_ocl_device_available()
    if use_sw_inter:
        log.warn('not using opencl, ctrl+c to quit')

    def flow_fn(x,
                y,
                pyr=args.pyr_scale,
                levels=args.levels,
                winsize=args.winsize,
                iters=args.iters,
                polyn=args.poly_n,
                polys=args.poly_s,
                fast=args.fast_pyr,
                filt=args.flow_filter):
        if use_sw_inter:
            return cv2.calcOpticalFlowFarneback(x, y, pyr, levels, winsize,
                                                iters, polyn, polys, filt)
        else:
            return motion.ocl_farneback_optical_flow(x, y, pyr, levels,
                                                     winsize, iters, polyn,
                                                     polys, fast, filt)

    inter_fn = None
    if use_sw_inter:
        from butterflow.interpolate import sw_interpolate_flow
        inter_fn = sw_interpolate_flow
    else:
        inter_fn = motion.ocl_interpolate_flow

    w, h = w_h_from_input_str(args.video_scale, av_info['w'], av_info['h'])

    def mk_even(x):
        return x & ~1

    w = mk_even(w)
    h = mk_even(h)

    rnd = Renderer(
        args.video, args.output_path,
        sequence_from_input_str(args.subregions, av_info['duration'],
                                av_info['frames']),
        rate_from_input_str(args.playback_rate, av_info['rate']), flow_fn,
        inter_fn, w, h, args.lossless, args.trim_subregions, args.no_preview,
        args.add_info, args.text_type, args.mark_frames, args.mux)

    motion.set_num_threads(settings['ocv_threads'])

    log.info('Will render:\n' + str(rnd.sequence))

    success = True
    total_time = 0
    try:
        import timeit
        total_time = timeit.timeit(rnd.render_video,
                                   setup='import gc;gc.enable()',
                                   number=1)
    except (KeyboardInterrupt, SystemExit):
        success = False
    if success:
        log.debug('Made: ' + args.output_path)
        out_sz = os.path.getsize(args.output_path) / 1024.0**2
        log.debug('Write ratio: {}/{}, ({:.2f}%) {:.2f} MB'.format(
            rnd.tot_frs_wrt, rnd.tot_tgt_frs,
            rnd.tot_frs_wrt * 100.0 / rnd.tot_tgt_frs, out_sz))
        print('Frames: {} real, {} interpolated, {} duped, {} dropped'.format(
            rnd.tot_src_frs, rnd.tot_frs_int, rnd.tot_frs_dup,
            rnd.tot_frs_drp))
        print('butterflow took {:.3g} mins, done.'.format(total_time / 60))
        return 0
    else:
        log.warn('files left in cache @ ' + settings['tempdir'])
        return 1
Exemple #5
0
def main():
    par = argparse.ArgumentParser(usage='butterflow [options] [video]',
                                  add_help=False)
    req = par.add_argument_group('Required arguments')
    gen = par.add_argument_group('General options')
    dsp = par.add_argument_group('Display options')
    vid = par.add_argument_group('Video options')
    mux = par.add_argument_group('Muxing options')
    fgr = par.add_argument_group('Advanced options')

    req.add_argument('video', type=str, nargs='?', default=None,
                     help='Specify the input video')

    gen.add_argument('-h', '--help', action='help',
                     help='Show this help message and exit')
    gen.add_argument('--version', action='store_true',
                     help='Show program\'s version number and exit')
    gen.add_argument('-d', '--devices', action='store_true',
                     help='Show detected OpenCL devices and exit')
    gen.add_argument('-sw', action='store_true',
                     help='Set to force software rendering')
    gen.add_argument('-c', '--cache', action='store_true',
                     help='Show cache information and exit')
    gen.add_argument('--rm-cache', action='store_true',
                     help='Set to clear the cache and exit')
    gen.add_argument('-prb', '--probe', action='store_true',
                     help='Show media file information and exit')
    gen.add_argument('-v', '--verbose', action='store_true',
                     help='Set to increase output verbosity')

    dsp.add_argument('-np', '--no-preview', action='store_false',
                     help='Set to disable video preview')
    dsp.add_argument('-a', '--add-info', action='store_true',
                     help='Set to embed debugging info into the output '
                          'video')
    dsp.add_argument('-tt', '--text-type',
                     choices=['light', 'dark', 'stroke'],
                     default=settings.default['text_type'],
                     help='Specify text type for debugging info, '
                     '(default: %(default)s)')
    dsp.add_argument('-mrk', '--mark-frames', action='store_true',
                     help='Set to mark interpolated frames')

    vid.add_argument('-o', '--output-path', type=str,
                     default=settings.default['out_path'],
                     help='Specify path to the output video')
    vid.add_argument('-r', '--playback-rate', type=str,
                     help='Specify the playback rate as an integer or a '
                     'float. Fractional forms are acceptable. To use a '
                     'multiple of the source video\'s rate, follow a number '
                     'with `x`, e.g., "2x" will double the frame rate. The '
                     'original rate will be used by default if nothing is '
                     'specified.')
    vid.add_argument('-s', '--sub-regions', type=str,
                     help='Specify rendering subregions in the form: '
                     '"a=TIME,b=TIME,TARGET=VALUE" where TARGET is either '
                     '`fps`, `dur`, `spd`. Valid TIME syntaxes are [hr:m:s], '
                     '[m:s], [s], [s.xxx], or `end`, which signifies to the '
                     'end the video. You can specify multiple subregions by '
                     'separating them with a colon `:`. A special region '
                     'format that conveniently describes the entire clip is '
                     'available in the form: "full,TARGET=VALUE".')
    vid.add_argument('-t', '--trim-regions', action='store_true',
                     help='Set to trim subregions that are not explicitly '
                          'specified')
    vid.add_argument('-vs', '--video-scale', type=str,
                     default=str(settings.default['video_scale']),
                     help='Specify output video size in the form: '
                     '"WIDTH:HEIGHT" or by using a factor. To keep the '
                     'aspect ratio only specify one component, either width '
                     'or height, and set the other component to -1, '
                     '(default: %(default)s)')
    vid.add_argument('-l', '--lossless', action='store_true',
                     help='Set to use lossless encoding settings')
    vid.add_argument('-sm', '--smooth-motion', action='store_true',
                     help='Set to tune for smooth motion. This mode favors '
                     'accurate and artifact-less frames above all and will '
                     'emphasize blending frames over warping pixels.')

    mux.add_argument('-mux', action='store_true',
                     help='Set to mux the source audio with the output video')

    fgr.add_argument('--fast-pyr', action='store_true',
                     help='Set to use fast pyramids')
    fgr.add_argument('--pyr-scale', type=float,
                     default=settings.default['pyr_scale'],
                     help='Specify pyramid scale factor, '
                     '(default: %(default)s)')
    fgr.add_argument('--levels', type=int,
                     default=settings.default['levels'],
                     help='Specify number of pyramid layers, '
                     '(default: %(default)s)')
    fgr.add_argument('--winsize', type=int,
                     default=settings.default['winsize'],
                     help='Specify averaging window size, '
                     '(default: %(default)s)')
    fgr.add_argument('--iters', type=int,
                     default=settings.default['iters'],
                     help='Specify number of iterations at each pyramid '
                     'level, (default: %(default)s)')
    fgr.add_argument('--poly-n', type=int,
                     choices=settings.default['poly_n_choices'],
                     default=settings.default['poly_n'],
                     help='Specify size of pixel neighborhood, '
                     '(default: %(default)s)')
    fgr.add_argument('--poly-s', type=float,
                     default=settings.default['poly_s'],
                     help='Specify standard deviation to smooth derivatives, '
                     '(default: %(default)s)')
    fgr.add_argument('-ff', '--flow-filter', choices=['box', 'gaussian'],
                     default=settings.default['flow_filter'],
                     help='Specify which filter to use for optical flow '
                     'estimation, (default: %(default)s)')

    # add a space to args that start with a `-` char to avoid an unexpected
    # argument error. needed for the `--video-scale` option
    for i, arg in enumerate(sys.argv):
        if (arg[0] == '-') and arg[1].isdigit():
            sys.argv[i] = ' ' + arg

    args = par.parse_args()

    import logging
    logging.basicConfig(level=settings.default['loglevel_a'],
                        format='%(levelname)-7s: %(message)s')

    log = logging.getLogger('butterflow')
    if args.verbose:
        log.setLevel(settings.default['loglevel_b'])

    if args.version:
        from butterflow.__init__ import __version__
        print('butterflow version %s' % __version__)
        return 0

    if args.cache:
        print_cache_info()
        return 0

    if args.rm_cache:
        rm_cache()
        print('cache deleted, done.')
        return 0

    if args.devices:
        ocl.print_ocl_devices()
        return 0

    if args.video is None:
        print(NO_VIDEO_SPECIFIED)
        return 1

    if not os.path.exists(args.video):
        print('Error: file does not exist at path')
        return 1

    if args.probe:
        if args.video:
            try:
                avinfo.print_av_info(args.video)
            except Exception as e:
                print('Error: %s' % e)
        else:
            print(NO_VIDEO_SPECIFIED)
        return 0

    try:
        vid_info = avinfo.get_av_info(args.video)
    except Exception as e:
        print('Error: %s' % e)
        return 1

    if not vid_info['v_stream_exists']:
        print('Error: no video stream detected')
        return 1

    try:
        sequence = sequence_from_str(vid_info['duration'],
                                     vid_info['frames'], args.sub_regions)
    except Exception as e:
        print('Bad subregion string: %s' % e)
        return 1

    src_rate = (vid_info['rate_n'] * 1.0 /
                vid_info['rate_d'])
    try:
        rate = rate_from_str(args.playback_rate, src_rate)
    except Exception as e:
        print('Bad playback rate: %s' % e)
        return 1
    if rate < src_rate:
        log.warning('rate=%s < src_rate=%s', rate, src_rate)

    try:
        w, h = w_h_from_str(args.video_scale, vid_info['w'], vid_info['h'])
    except Exception as e:
        print('Bad video scale: %s' % e)
        return 1

    have_ocl = ocl.compat_ocl_device_available()
    use_sw = args.sw or not have_ocl

    if use_sw:
        log.warning('not using opencl accelerated methods')

    # make functions that will generate flows and interpolate frames
    from cv2 import calcOpticalFlowFarneback as sw_optical_flow
    farneback_method = sw_optical_flow if use_sw else \
        motion.ocl_farneback_optical_flow
    flags = 0
    if args.flow_filter == 'gaussian':
        import cv2
        flags = cv2.OPTFLOW_FARNEBACK_GAUSSIAN

    if args.smooth_motion:
        args.poly_s = 0.01

    # don't make the function with `lambda` because `draw_debug_text` will need
    # to retrieve kwargs with the `inspect` module
    def flow_function(x, y, pyr=args.pyr_scale, l=args.levels, w=args.winsize,
                      i=args.iters, polyn=args.poly_n, polys=args.poly_s,
                      fast=args.fast_pyr, filt=flags):
        if farneback_method == motion.ocl_farneback_optical_flow:
            return farneback_method(x, y, pyr, l, w, i, polyn, polys, fast, filt)
        else:
            return farneback_method(x, y, pyr, l, w, i, polyn, polys, filt)

    from butterflow.interpolate import sw_interpolate_flow
    interpolate_function = sw_interpolate_flow if use_sw else \
        motion.ocl_interpolate_flow

    renderer = Renderer(
        args.output_path,
        vid_info,
        sequence,
        rate,
        flow_function,
        interpolate_function,
        w,
        h,
        args.lossless,
        args.trim_regions,
        args.no_preview,
        args.add_info,
        args.text_type,
        args.mark_frames,
        args.mux)

    motion.set_num_threads(settings.default['ocv_threads'])

    try:
        # time how long it takes to render. timeit will turn off gc, must turn
        # it back on to maximize performance re-nable it in the setup function
        import timeit
        tot_time = timeit.timeit(renderer.render,
                                 setup='import gc;gc.enable()',
                                 number=1)  # only run once
        tot_time /= 60  # time in minutes

        new_sz = human_sz(float(os.path.getsize(args.output_path)))

        print('{} real, {} interpolated, {} duped, {} dropped'.format(
            renderer.tot_src_frs,
            renderer.tot_frs_int,
            renderer.tot_frs_dup,
            renderer.tot_frs_drp))
        print('write ratio: {}/{}, ({:.2f}%) {}'.format(
            renderer.tot_frs_wrt,
            renderer.tot_tgt_frs,
            renderer.tot_frs_wrt * 100.0 / renderer.tot_tgt_frs,
            new_sz))
        print('butterflow took {:.3g} minutes, done.'.format(tot_time))
    except (KeyboardInterrupt, SystemExit):
        log.warning('stopped early, files were left in the cache')
        log.warning('recoverable @ {}'.format(settings.default['tmp_dir']))
        return 1
    except Exception:
        return 1