A bunch of stuff, including outsourcing globs to the shell, positional input argument, more consistent edocs, and better Docker handling

This commit is contained in:
Amber McCloughan 2024-07-05 21:41:03 -04:00
parent ad29252a41
commit 5cf3914d35
3 changed files with 46 additions and 38 deletions

View File

@ -1,11 +1,13 @@
FROM python:3.12-slim-bookworm FROM python:3.12-slim-bookworm
WORKDIR /app VOLUME /home
COPY video_to_gif.py /app/video_to_gif.py COPY video_to_gif.py /home/video_to_gif.py
RUN apt update && \ WORKDIR /home
apt upgrade && \
RUN apt update -y && \
apt upgrade -y && \
apt install ffmpeg -y apt install ffmpeg -y
ENTRYPOINT ["python", "video_to_gif.py"] ENTRYPOINT ["python", "video_to_gif.py"]

View File

@ -1,31 +1,33 @@
# video-to-gif # video-to-gif
## Usage ## Usage
``` ```
usage: video_to_gif.py [-h] -i INPUT [-w WIDTH] [-r FRAMERATE] usage: video_to_gif.py [-h] [-w WIDTH] [-r FRAMERATE]
[-s {fast_bilinear,bilinear,bicubic,experimental,neighbor,area,bicublin,gauss,sinc,lanczos,spline}] [-s {fast_bilinear,bilinear,bicubic,experimental,neighbor,area,bicublin,gauss,sinc,lanczos,spline}] [-i {none,dup,blend,mci}]
[-interpolate {none,dup,blend,mci}] [-t TAG] [-t TAG]
INPUT [INPUT ...]
Use ffmpeg to make GIFs from videos Use ffmpeg to make GIFs from videos
positional arguments:
INPUT input file, supports passing a glob like /Users/MyName/Videos/*.mov
options: options:
-h, --help show this help message and exit -h, --help show this help message and exit
-i INPUT, --input INPUT
Input pattern like '/users/MyName/Videos/*.mov'
-w WIDTH, --width WIDTH -w WIDTH, --width WIDTH
Width of the GIF in pixels, respecting aspect ratio (default: 960) width of the GIF in pixels, respecting aspect ratio (default: 960)
-r FRAMERATE, --framerate FRAMERATE -r FRAMERATE, --framerate FRAMERATE
Framerate of GIF (default: 12) framerate of GIF (default: 12)
-s {fast_bilinear,bilinear,bicubic,experimental,neighbor,area,bicublin,gauss,sinc,lanczos,spline}, --scaler {fast_bilinear,bilinear,bicubic,experimental,neighbor,area,bicublin,gauss,sinc,lanczos,spline} -s {fast_bilinear,bilinear,bicubic,experimental,neighbor,area,bicublin,gauss,sinc,lanczos,spline}, --scaler {fast_bilinear,bilinear,bicubic,experimental,neighbor,area,bicublin,gauss,sinc,lanczos,spline}
Scaling algorithm to use (default: lanczos) scaling algorithm to use (default: lanczos)
-interpolate {none,dup,blend,mci}, --interpolate {none,dup,blend,mci} -i {none,dup,blend,mci}, --interpolate {none,dup,blend,mci}
Interpolation method to use (default: none) interpolation method to use (default: none)
-t TAG, --tag TAG Optional tag included in file name -t TAG, --tag TAG optional tag included in file name
— Be gay and do crime — Be gay and do crime
``` ```
## Docker ## Docker
```shell ```sh
docker build -t video_to_gif:latest . docker build -t video_to_gif:latest .
docker run video_to_gif --help docker run --rm -it video_to_gif --help
``` ```

View File

@ -1,11 +1,10 @@
import argparse import argparse
import glob
from pathlib import Path from pathlib import Path
import random import random
import string import string
import subprocess import subprocess
from typing import Any, Dict, List from typing import Any, Dict, List, Tuple
def get_args() -> Dict[str, Any]: def get_args() -> Dict[str, Any]:
@ -13,18 +12,18 @@ def get_args() -> Dict[str, Any]:
prog='video_to_gif.py', prog='video_to_gif.py',
description='Use ffmpeg to make GIFs from videos', description='Use ffmpeg to make GIFs from videos',
epilog='— Be gay and do crime') epilog='— Be gay and do crime')
parser.add_argument('-i', '--input', type=str, required=True, parser.add_argument('input', type=str, metavar='INPUT', nargs='+',
help="Input pattern like '/users/MyName/Videos/*.mov'") help="input file, supports passing a glob like /Users/MyName/Videos/*.mov")
parser.add_argument('-w', '--width', type=str, default='960', parser.add_argument('-w', '--width', type=str, default='960',
help='Width of the GIF in pixels, respecting aspect ratio (default: 960)') help='width of the GIF in pixels, respecting aspect ratio (default: 960)')
parser.add_argument('-r', '--framerate', type=str, parser.add_argument('-r', '--framerate', type=str,
default='12', help='Framerate of GIF (default: 12)') default='12', help='framerate of GIF (default: 12)')
parser.add_argument('-s', '--scaler', type=str, choices=get_scaling_algorithms(), parser.add_argument('-s', '--scaler', type=str, choices=get_scaling_algorithms(),
default='lanczos', help='Scaling algorithm to use (default: lanczos)') default='lanczos', help='scaling algorithm to use (default: lanczos)')
parser.add_argument('-interpolate', '--interpolate', type=str, choices=get_interpolation_methods(), parser.add_argument('-i', '--interpolate', type=str, choices=get_interpolation_methods(),
default='none', help='Interpolation method to use (default: none)') default='none', help='interpolation method to use (default: none)')
parser.add_argument('-t', '--tag', type=str, default=get_tag(), parser.add_argument('-t', '--tag', type=str, default=get_tag(),
help='Optional tag included in file name') help='optional tag included in file name')
return vars(parser.parse_args()) return vars(parser.parse_args())
@ -46,16 +45,14 @@ def get_tag() -> str:
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=5)) return ''.join(random.choices(string.ascii_uppercase + string.digits, k=5))
def get_inputs(args: Dict[str, Any]) -> List[str]: def call_ffmpeg(command: str) -> bool:
return glob.glob(args['input'])
def call_ffmpeg(command: str) -> str:
print("ffmpeg", *command.split(' ')) print("ffmpeg", *command.split(' '))
return str(subprocess.run(["ffmpeg", '-hide_banner', '-loglevel', 'error', *command.split(' ')])) process = subprocess.run(["ffmpeg", '-hide_banner', '-loglevel', 'error',
*command.split(' ')])
return process.returncode == 0
def generate_gif_files(inputs: List[str], args: Dict[str, Any]) -> Dict[str, str]: def generate_gif_files(inputs: List[str], args: Dict[str, Any]) -> Dict[str, Tuple[bool, str]]:
tag = args['tag'] tag = args['tag']
width = args['width'] width = args['width']
framerate = args['framerate'] framerate = args['framerate']
@ -71,19 +68,26 @@ def generate_gif_files(inputs: List[str], args: Dict[str, Any]) -> Dict[str, str
for input in inputs: for input in inputs:
file_name = f"{Path(input).stem}_{tag}.gif" file_name = f"{Path(input).stem}_{tag}.gif"
full_path = f"{Path(input).parent}/{file_name}" full_path = f"{Path(input).parent}/{file_name}"
call_ffmpeg(f"-i {input} -f gif -r {framerate} -filter_complex {interpolate_cmd}scale={ \ success = call_ffmpeg(f"-i {input} -f gif -r {framerate} -filter_complex {interpolate_cmd}scale={ \
width}:-1:flags={scaler},split[v1][v2];[v1]palettegen[plt];[v2][plt]paletteuse { \ width}:-1:flags={scaler},split[v1][v2];[v1]palettegen[plt];[v2][plt]paletteuse { \
full_path}") full_path}")
output_map[input] = full_path # Tuple of (was it successful?, path of output)
output_map[input] = (success, full_path)
return output_map return output_map
def make_gifs(args: Dict[str, Any]) -> None: def make_gifs(args: Dict[str, Any]) -> None:
inputs = get_inputs(args) inputs = args['input']
output_map = generate_gif_files(inputs, args) output_map = generate_gif_files(inputs, args)
successes = 0
for input in output_map: for input in output_map:
print(f"Created {output_map[input]} from {input}") 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: def main() -> None: