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
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 && \
apt upgrade && \
WORKDIR /home
RUN apt update -y && \
apt upgrade -y && \
apt install ffmpeg -y
ENTRYPOINT ["python", "video_to_gif.py"]

View File

@ -1,31 +1,33 @@
# video-to-gif
## Usage
```
usage: video_to_gif.py [-h] -i INPUT [-w WIDTH] [-r FRAMERATE]
[-s {fast_bilinear,bilinear,bicubic,experimental,neighbor,area,bicublin,gauss,sinc,lanczos,spline}]
[-interpolate {none,dup,blend,mci}] [-t TAG]
usage: video_to_gif.py [-h] [-w WIDTH] [-r FRAMERATE]
[-s {fast_bilinear,bilinear,bicubic,experimental,neighbor,area,bicublin,gauss,sinc,lanczos,spline}] [-i {none,dup,blend,mci}]
[-t TAG]
INPUT [INPUT ...]
Use ffmpeg to make GIFs from videos
positional arguments:
INPUT input file, supports passing a glob like /Users/MyName/Videos/*.mov
options:
-h, --help show this help message and exit
-i INPUT, --input INPUT
Input pattern like '/users/MyName/Videos/*.mov'
-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
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}
Scaling algorithm to use (default: lanczos)
-interpolate {none,dup,blend,mci}, --interpolate {none,dup,blend,mci}
Interpolation method to use (default: none)
-t TAG, --tag TAG Optional tag included in file name
scaling algorithm to use (default: lanczos)
-i {none,dup,blend,mci}, --interpolate {none,dup,blend,mci}
interpolation method to use (default: none)
-t TAG, --tag TAG optional tag included in file name
— Be gay and do crime
```
## Docker
```shell
```sh
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 glob
from pathlib import Path
import random
import string
import subprocess
from typing import Any, Dict, List
from typing import Any, Dict, List, Tuple
def get_args() -> Dict[str, Any]:
@ -13,18 +12,18 @@ def get_args() -> Dict[str, Any]:
prog='video_to_gif.py',
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('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)')
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)')
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('-interpolate', '--interpolate', type=str, choices=get_interpolation_methods(),
default='none', help='Interpolation method to use (default: none)')
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)')
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())
@ -46,16 +45,14 @@ 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 call_ffmpeg(command: str) -> str:
def call_ffmpeg(command: str) -> bool:
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']
width = args['width']
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:
file_name = f"{Path(input).stem}_{tag}.gif"
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 { \
full_path}")
output_map[input] = full_path
# 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 = get_inputs(args)
inputs = args['input']
output_map = generate_gif_files(inputs, args)
successes = 0
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: