commit d028085a649449a7e9547d6fabae444324fcedb9 Author: Amber Date: Fri Jun 28 22:25:42 2024 -0400 First pass at video to GIF script in Python diff --git a/gifmake.py b/gifmake.py new file mode 100644 index 0000000..b1cba2b --- /dev/null +++ b/gifmake.py @@ -0,0 +1,99 @@ + +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='GIF Make', + 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(' ')) + # If we want to silence the command output: stdout=open(os.devnull, 'wb') + return str(subprocess.run(["ffmpeg", *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() \ No newline at end of file