|
| 1 | +#!/usr/bin/python |
| 2 | + |
| 3 | +# License for any modification to the original (linked below): |
| 4 | +# ---------------------------------------------------------------------------- |
| 5 | +# "THE BEER-WARE LICENSE" (Revision 42): |
| 6 | +# Sebastiano Poggi and Daniele Conti wrote this file. As long as you retain |
| 7 | +# this notice you can do whatever you want with this stuff. If we meet some day, |
| 8 | +# and you think this stuff is worth it, you can buy us a beer in return. |
| 9 | + |
| 10 | +import argparse, sys, subprocess, tempfile |
| 11 | +from os.path import splitext |
| 12 | +from distutils.spawn import find_executable |
| 13 | + |
| 14 | +def look_for_ffmpeg_or_abort(): |
| 15 | + ffmpeg_path = find_executable('ffmpeg') |
| 16 | + if ffmpeg_path == None: |
| 17 | + print("** ComputerSaysNoError **") |
| 18 | + print("You need to have ffmpeg installed on your system and on the path for Giffify to work") |
| 19 | + exit(1) |
| 20 | + |
| 21 | +def parse_cli_arguments(): |
| 22 | + parser = argparse.ArgumentParser(description='Processes a video into a gif.') |
| 23 | + parser.add_argument('video', type=str, help='The video to be processed') |
| 24 | + parser.add_argument('-o', '--outfile', type=str, help='Target path') |
| 25 | + parser.add_argument('-dw', '--desired-width', type=int, default=-1) |
| 26 | + parser.add_argument('-dh', '--desired-height', type=int, default=-1) |
| 27 | + parser.add_argument('-f', '--fps', type=int, default=15, help='Output frames per second. Default: 15 fps') |
| 28 | + parser.add_argument('-s', '--start-time', type=int, default=-1, help='Start timestamp, as [-][HH:]MM:SS[.m...] or [-]S+[.m...]') |
| 29 | + parser.add_argument('-e', '--end-time', type=int, default=-1, help='End timestamp, as [-][HH:]MM:SS[.m...] or [-]S+[.m...]. Overridden by -d') |
| 30 | + parser.add_argument('-d', '--duration', type=int, default=-1, help='Duration, as [-][HH:]MM:SS[.m...] or [-]S+[.m...]. Overrides -e') |
| 31 | + parser.add_argument('-c', '--crop', type=str, help='Output crop, as per ffmpeg\'s crop filter specs (i.e., out_w:out_h:x:y)') |
| 32 | + parser.add_argument('-r', '--rotate', dest='rotate', action='store_true', help='Rotate output 270 degrees clockwise (useful for Genymotion)') |
| 33 | + |
| 34 | + return parser.parse_args() |
| 35 | + |
| 36 | +def gif_path(path): |
| 37 | + return splitext(path)[0] + '.gif' |
| 38 | + |
| 39 | +def get_palette_path(): |
| 40 | + try: |
| 41 | + palette_file = tempfile.NamedTemporaryFile() |
| 42 | + return palette_file.name + '.png' |
| 43 | + finally: |
| 44 | + palette_file.close() |
| 45 | + |
| 46 | +def insert_before_output_path(args, elements): |
| 47 | + index = args.index('-y') |
| 48 | + return args[:index] + elements + args[index:] |
| 49 | + |
| 50 | +look_for_ffmpeg_or_abort() |
| 51 | + |
| 52 | +args = parse_cli_arguments() |
| 53 | + |
| 54 | +input_path = args.video |
| 55 | +output_path = gif_path(input_path) if args.outfile is None else args.outfile |
| 56 | + |
| 57 | +fps = args.fps |
| 58 | + |
| 59 | +dw = args.desired_width |
| 60 | +dh = args.desired_height |
| 61 | + |
| 62 | +s = args.start_time |
| 63 | +e = args.end_time |
| 64 | +d = args.duration |
| 65 | +c = args.crop |
| 66 | + |
| 67 | +if args.rotate: |
| 68 | + rotate_filters = 'transpose=2,' |
| 69 | +else: |
| 70 | + rotate_filters = "" |
| 71 | + |
| 72 | +if c is not None: |
| 73 | + crop_filter = "crop={crop},".format(crop = c) |
| 74 | +else: |
| 75 | + crop_filter = "" |
| 76 | + |
| 77 | +filters = "{rotate}{crop}fps={fps},scale={dw}:{dh}:flags=lanczos".format(rotate = rotate_filters, crop = crop_filter, fps = fps, dw = dw, dh = dh) |
| 78 | + |
| 79 | +palette_filters = "{filters},palettegen".format(filters = filters) |
| 80 | +palette_path = get_palette_path() |
| 81 | + |
| 82 | +output_filters = "{filters} [x]; [x][1:v] paletteuse".format(filters = filters) |
| 83 | + |
| 84 | +ffmpeg_args_palette = ['ffmpeg', |
| 85 | + '-v', 'warning', |
| 86 | + '-i', input_path, |
| 87 | + '-vf', palette_filters, |
| 88 | + '-y', palette_path] |
| 89 | +ffmpeg_args_gif = ['ffmpeg', |
| 90 | + '-v', 'warning', |
| 91 | + '-i', input_path, |
| 92 | + '-i', palette_path, |
| 93 | + '-lavfi', output_filters, |
| 94 | + '-y', output_path] |
| 95 | + |
| 96 | +if s != -1: |
| 97 | + ffmpeg_args_gif = insert_before_output_path(ffmpeg_args_gif, ["-ss", str(s)]) |
| 98 | + |
| 99 | +if e != -1: |
| 100 | + ffmpeg_args_gif = insert_before_output_path(ffmpeg_args_gif, ["-to", str(e)]) |
| 101 | + |
| 102 | +if d != -1: |
| 103 | + ffmpeg_args_gif = insert_before_output_path(ffmpeg_args_gif, ["-t", str(d)]) |
| 104 | + |
| 105 | +print("First pass: extracting colour palette, hang tight...") |
| 106 | +subprocess.call(ffmpeg_args_palette) |
| 107 | + |
| 108 | +print("Second pass: converting that nice video into a sweet, high quality gif...") |
| 109 | +subprocess.call(ffmpeg_args_gif) |
| 110 | + |
| 111 | +print("Done! Now go and show off to your friends and colleagues") |
0 commit comments