From 78f1b7d7de0ba4ef9c637260e70703a19c664d47 Mon Sep 17 00:00:00 2001 From: Amber Date: Fri, 28 Jun 2024 23:03:23 -0400 Subject: [PATCH] Renaming stuff, adding README.md --- README.md | 0 video_to_gif.py | 106 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 README.md create mode 100644 video_to_gif.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/video_to_gif.py b/video_to_gif.py new file mode 100644 index 0000000..14ccbbd --- /dev/null +++ b/video_to_gif.py @@ -0,0 +1,106 @@ + +import argparse +import glob +import os +from pathlib import Path +import random +import string +import subprocess +from typing import Any, Dict, List + + +def get_args() -> Dict[str, Any]: + parser = argparse.ArgumentParser( + prog='video-to-gif', + description='Use ffmpeg to make GIFs from videos', + epilog='— Be gay and do crime') + parser.add_argument('-i', '--input', type=str, required=True, + help="Input pattern like '/users/MyName/Videos/*.mov'") + parser.add_argument('-w', '--width', type=str, default='960', + help='Width of the GIF in pixels, respecting aspect ratio') + parser.add_argument('-r', '--framerate', type=str, + default='12', help='Framerate of GIF') + parser.add_argument('-t', '--tag', type=str, default=get_tag(), + help='Optional tag included in file name') + parser.add_argument('-f', '--force_palette', action='store_const', + const=True, help='Force regeneration of GIF color palettes') + parser.add_argument('-c', '--clean_up', action='store_const', + const=True, help='Delete color palettes after finishing GIFs') + return vars(parser.parse_args()) + + +def get_tag() -> str: + return ''.join(random.choices(string.ascii_uppercase + string.digits, k=5)) + + +def get_inputs(args: Dict[str, Any]) -> List[str]: + return glob.glob(args['input']) + + +def clean_up_palette_files(palette_map: Dict[str, str]) -> None: + for input in palette_map: + os.remove(palette_map[input]) + + +def call_ffmpeg(command: str) -> str: + print("ffmpeg", *command.split(' ')) + return str(subprocess.run(["ffmpeg", '-hide_banner', '-loglevel', 'error', *command.split(' ')])) + + +def generate_palette_files_and_map(inputs: List[str], args: Dict[str, Any]) -> Dict[str, str]: + force_palette = args['force_palette'] + width = args['width'] + tag = args['tag'] + + palette_map = {} + for input in inputs: + file_name = f"{Path(input).stem}_{tag}_palette.png" + full_path = f"{Path(input).parent}/{file_name}" + if force_palette or not os.path.exists(full_path): + call_ffmpeg( + f"-i {input} -lavfi scale={width}:-1,palettegen -sws_flags ewa_lanczos -y {full_path}") + else: + print(f"Skipped regenerating palette file {file_name}...") + palette_map[input] = full_path + + return palette_map + + +def generate_gif_files(palette_map: Dict[str, str], args: Dict[str, Any]) -> Dict[str, str]: + tag = args['tag'] + width = args['width'] + framerate = args['framerate'] + + output_map = {} + for input in palette_map: + palette = palette_map[input] + file_name = f"{Path(input).stem}_{tag}.gif" + full_path = f"{Path(input).parent}/{file_name}" + call_ffmpeg(f"-i {input} -i {palette} -f gif -r {framerate} -lavfi scale={ \ + width}:-1,paletteuse -sws_flags ewa_lanczos {full_path}") + output_map[input] = full_path + + return output_map + + +def make_gifs(args: Dict[str, Any]) -> None: + clean_up = args['clean_up'] + inputs = get_inputs(args) + + palette_map = generate_palette_files_and_map(inputs, args) + output_map = generate_gif_files(palette_map, args) + + if clean_up: + clean_up_palette_files(palette_map) + + for input in output_map: + print(f"Created {output_map[input]} from {input}") + + +def main() -> None: + make_gifs(get_args()) + return + + +if __name__ == '__main__': + main()