2024-06-29 02:25:42 +00:00
|
|
|
|
|
|
|
import argparse
|
|
|
|
from pathlib import Path
|
|
|
|
import random
|
|
|
|
import string
|
|
|
|
import subprocess
|
2024-07-06 01:41:03 +00:00
|
|
|
from typing import Any, Dict, List, Tuple
|
2024-06-29 02:25:42 +00:00
|
|
|
|
|
|
|
|
|
|
|
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')
|
2024-07-06 01:41:03 +00:00
|
|
|
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',
|
2024-07-06 01:41:03 +00:00
|
|
|
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,
|
2024-07-06 01:41:03 +00:00
|
|
|
default='12', help='framerate of GIF (default: 12)')
|
2024-07-02 04:05:44 +00:00
|
|
|
parser.add_argument('-s', '--scaler', type=str, choices=get_scaling_algorithms(),
|
2024-07-06 01:41:03 +00:00
|
|
|
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(),
|
2024-07-06 01:41:03 +00:00
|
|
|
help='optional tag included in file name')
|
2024-06-29 02:25:42 +00:00
|
|
|
return vars(parser.parse_args())
|
|
|
|
|
|
|
|
|
2024-07-02 04:05:44 +00:00
|
|
|
def get_scaling_algorithms() -> List[str]:
|
|
|
|
return ['fast_bilinear', 'bilinear', 'bicubic', 'experimental', 'neighbor',
|
|
|
|
'area', 'bicublin', 'gauss', 'sinc', 'lanczos', 'spline']
|
|
|
|
|
|
|
|
|
2024-07-03 04:15:18 +00:00
|
|
|
def get_interpolation_methods() -> List[str]:
|
|
|
|
return ['none', 'dup', 'blend', 'mci']
|
|
|
|
|
|
|
|
|
2024-07-02 04:05:44 +00:00
|
|
|
# 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']
|
|
|
|
|
|
|
|
|
2024-06-29 02:25:42 +00:00
|
|
|
def get_tag() -> str:
|
2024-06-29 02:58:29 +00:00
|
|
|
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=5))
|
2024-06-29 02:25:42 +00:00
|
|
|
|
|
|
|
|
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)
|
2024-07-06 01:41:03 +00:00
|
|
|
return process.returncode == 0
|
2024-06-29 02:25:42 +00:00
|
|
|
|
|
|
|
|
2024-07-06 01:41:03 +00:00
|
|
|
def generate_gif_files(inputs: List[str], args: Dict[str, Any]) -> Dict[str, Tuple[bool, str]]:
|
2024-06-29 02:25:42 +00:00
|
|
|
tag = args['tag']
|
|
|
|
width = args['width']
|
|
|
|
framerate = args['framerate']
|
2024-07-02 04:05:44 +00:00
|
|
|
scaler = args['scaler']
|
2024-07-03 04:15:18 +00:00
|
|
|
interpolate = args['interpolate']
|
|
|
|
|
|
|
|
interpolate_cmd = ''
|
|
|
|
if interpolate != 'none':
|
2024-07-26 03:21:55 +00:00
|
|
|
interpolate_cmd = f"minterpolate=fps={
|
2024-07-03 04:15:18 +00:00
|
|
|
framerate}:mi_mode={interpolate},"
|
2024-06-29 02:25:42 +00:00
|
|
|
|
|
|
|
output_map = {}
|
2024-06-29 18:19:12 +00:00
|
|
|
for input in inputs:
|
2024-06-29 02:25:42 +00:00
|
|
|
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)
|
2024-07-06 01:41:03 +00:00
|
|
|
# Tuple of (was it successful?, path of output)
|
|
|
|
output_map[input] = (success, full_path)
|
2024-06-29 02:25:42 +00:00
|
|
|
|
|
|
|
return output_map
|
|
|
|
|
|
|
|
|
|
|
|
def make_gifs(args: Dict[str, Any]) -> None:
|
2024-07-06 01:41:03 +00:00
|
|
|
inputs = args['input']
|
2024-06-29 18:19:12 +00:00
|
|
|
output_map = generate_gif_files(inputs, args)
|
2024-07-06 01:41:03 +00:00
|
|
|
successes = 0
|
2024-06-29 02:25:42 +00:00
|
|
|
for input in output_map:
|
2024-07-06 01:41:03 +00:00
|
|
|
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.")
|
2024-06-29 02:25:42 +00:00
|
|
|
|
|
|
|
|
|
|
|
def main() -> None:
|
|
|
|
make_gifs(get_args())
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2024-06-29 02:58:29 +00:00
|
|
|
main()
|