video-to-gif/video_to_gif.py

100 lines
3.5 KiB
Python
Raw Permalink Normal View History

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(
2024-06-29 03:03:23 +00:00
prog='video_to_gif.py',
2024-06-29 02:58:29 +00:00
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")
2024-06-29 02:58:29 +00:00
parser.add_argument('-w', '--width', type=str, default='960',
help='width of the GIF in pixels, respecting aspect ratio (default: 960)')
2024-06-29 02:58:29 +00:00
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)')
2024-06-29 02:58:29 +00:00
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:
2024-06-29 02:58:29 +00:00
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=5))
2024-07-26 03:21:55 +00:00
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}"
2024-07-26 03:21:55 +00:00
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__':
2024-06-29 02:58:29 +00:00
main()