video-to-gif/gifmake.py

99 lines
3.3 KiB
Python
Raw Normal View History

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()