video-to-gif/video_to_gif2.py

91 lines
3.0 KiB
Python

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(
prog='video_to_gif2.py',
description='Use ffmpeg and gifski 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")
parser.add_argument('-w', '--width', type=str, default='960', \
help='width of the GIF in pixels, respecting aspect ratio (default: 960)')
parser.add_argument('-r', '--framerate', type=str, \
default='12', help='framerate of GIF (default: 12)')
parser.add_argument('-e', '--extra', default=False, action='store_true', \
help='increase quality at the expense of file size and encoding time')
parser.add_argument('-t', '--tag', type=str, default=get_tag(), \
help='optional tag included in file name')
return vars(parser.parse_args())
def get_tag() -> str:
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=5))
def call_ffmpeg(command: List[str]) -> bool:
print("ffmpeg", ' '.join(command))
full_command = ["ffmpeg", '-hide_banner', '-loglevel', 'error'] + command
# TODO: Find a way to avoid shell=True usage.
process = subprocess.run(' '.join(full_command), shell=True)
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']
extra = args['extra']
extra_cmd = ''
quality = '90'
if extra:
extra_cmd = '--extra'
quality = '100'
output_map = {}
for input in inputs:
file_name = f"{Path(input).stem}_{tag}.gif"
full_path = f"{Path(input).parent}/{file_name}"
if extra:
ex_cmd = '--extra'
command = ['-i', input, '-pix_fmt', 'yuv444p', '-f', 'yuv4mpegpipe', '-', '|', 'gifski', \
'--width', f"{width}", '-r', f"{framerate}", extra_cmd, '-Q', quality, \
'-o', f"{full_path}", '-']
command = list(filter(None, command))
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__':
main()