import argparse from pathlib import Path import random import string import subprocess from typing import Any, Dict, List, Tuple def get_args() -> Dict[str, Any]: parser = argparse.ArgumentParser( prog='video_to_gif.py', description='Use ffmpeg to make GIFs from videos', epilog='— Be gay and do crime') parser.add_argument('input', type=str, metavar='INPUT', nargs='+', help="input file, supports passing a glob like /Users/MyName/Videos/*.mov") parser.add_argument('-w', '--width', type=str, default='960', help='width of the GIF in pixels, respecting aspect ratio (default: 960)') parser.add_argument('-r', '--framerate', type=str, default='12', help='framerate of GIF (default: 12)') parser.add_argument('-s', '--scaler', type=str, choices=get_scaling_algorithms(), default='lanczos', help='scaling algorithm to use (default: lanczos)') parser.add_argument('-i', '--interpolate', type=str, choices=get_interpolation_methods(), default='none', help='interpolation method to use (default: none)') parser.add_argument('-t', '--tag', type=str, default=get_tag(), help='optional tag included in file name') return vars(parser.parse_args()) def get_scaling_algorithms() -> List[str]: return ['fast_bilinear', 'bilinear', 'bicubic', 'experimental', 'neighbor', 'area', 'bicublin', 'gauss', 'sinc', 'lanczos', 'spline'] def get_interpolation_methods() -> List[str]: return ['none', 'dup', 'blend', 'mci'] # TODO: Figure out how to specify both scaling and dithering algorithms. def get_dithering_algorithms() -> List[str]: return ['none', 'auto', 'bayer', 'ed', 'a_dither', 'x_dither'] def get_tag() -> str: return ''.join(random.choices(string.ascii_uppercase + string.digits, k=5)) def call_ffmpeg(command: List[str]) -> bool: print("ffmpeg", ' '.join(command)) full_command = ["ffmpeg", '-hide_banner', '-loglevel', 'error'] + command process = subprocess.run(full_command) return process.returncode == 0 def generate_gif_files(inputs: List[str], args: Dict[str, Any]) -> Dict[str, Tuple[bool, str]]: tag = args['tag'] width = args['width'] framerate = args['framerate'] scaler = args['scaler'] interpolate = args['interpolate'] interpolate_cmd = '' if interpolate != 'none': interpolate_cmd = f"minterpolate=fps={ \ framerate}:mi_mode={interpolate}," output_map = {} for input in inputs: file_name = f"{Path(input).stem}_{tag}.gif" full_path = f"{Path(input).parent}/{file_name}" command = ['-i', input, '-f', 'gif', '-r', f"{framerate}", '-filter_complex', f"{interpolate_cmd}scale={ \ width}:-1:flags={scaler},split[v1][v2];[v1]palettegen[plt];[v2][plt]paletteuse", f"{full_path}"] success = call_ffmpeg(command) # Tuple of (was it successful?, path of output) output_map[input] = (success, full_path) return output_map def make_gifs(args: Dict[str, Any]) -> None: inputs = args['input'] output_map = generate_gif_files(inputs, args) successes = 0 for input in output_map: entry = output_map[input] if entry[0]: print(f"Created {entry[1]} from {input}") successes += 1 total = len(output_map) print(f"{successes} of {total} commands succeeded.") def main() -> None: make_gifs(get_args()) return if __name__ == '__main__': main()