diff --git a/Dockerfile b/Dockerfile index ec759f2..56f111c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] \ No newline at end of file diff --git a/README.md b/README.md index a02fb4b..db060fe 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,34 @@ # 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 +docker run -it --rm -v "/Users/MyUsername/Movies/:$PWD" video_to_gif $PWD/my_movie.mov ``` \ No newline at end of file diff --git a/video_to_gif.py b/video_to_gif.py index 7a87338..b65d468 100644 --- a/video_to_gif.py +++ b/video_to_gif.py @@ -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: