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:
parent
ad29252a41
commit
8edf92e0cc
10
Dockerfile
10
Dockerfile
|
@ -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"]
|
29
README.md
29
README.md
|
@ -1,31 +1,34 @@
|
||||||
# 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
|
||||||
|
docker run -it --rm -v "/Users/MyUsername/Movies/:$PWD" video_to_gif $PWD/my_movie.mov
|
||||||
```
|
```
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue