Files
playground/ffmpeg_gif.py
T
2023-02-18 07:39:08 +01:00

128 lines
3.5 KiB
Python
Executable File

#!/usr/bin/env python3.9
import argparse
import datetime
import hashlib
import math
import re
import subprocess
import sys
import typing
from pathlib import Path
import ffmpeg
def create_gif(
video_path: Path,
from_time: str,
to_time: typing.Optional[str] = None,
save_path: typing.Optional[Path] = None,
duration: typing.Optional[int] = 10,
fps: int = 15,
width: int = 320,
) -> Path:
if not save_path:
# strip extra decimals
time_clean = re.sub(r"\.(\d)\d+", r".\1", from_time).replace(":", "")
save_path = video_path.with_name(f"{video_path.stem}_{time_clean}.gif")
info = ffmpeg.ffprobe(video_path)
scaled_width, scaled_height = width, math.ceil(width / info.ar / info.sample_ar)
subprocess.run(
[
"ffmpeg",
"-v",
"warning",
"-y",
"-ss",
from_time,
*(["-to", to_time] if to_time else ["-t", str(duration)]),
"-ignore_chapters",
"1",
"-i",
str(video_path),
"-filter_complex",
# f"fps={fps},scale={scale}:-1:flags=lanczos[x];[x]split[x1][x2];[x1]palettegen[p];[x2][p]paletteuse",
f"[0:v] fps={fps},scale={scaled_width}:{scaled_height},split [a][b];[a] palettegen=stats_mode=full [p];[b][p] paletteuse=new=1",
str(save_path),
],
check=True,
)
return save_path
def optimize_gif(gif_path: Path) -> Path:
subprocess.run(
[
"gifsicle",
"--lossy=98",
"--optimize=3",
"--batch",
"-i",
str(gif_path),
],
check=True,
)
return gif_path
def parse_args(argv: typing.List[str] = None) -> argparse.Namespace:
arger = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
arger.add_argument("video_path", type=str, help="Path to video file")
arger.add_argument(
"--start", "-s", dest="from_time", type=str, help="Time to start from, e.g. 02:59", required=True
)
arger.add_argument("--duration", "-d", default=3, type=int, help="Duration of gif in seconds")
arger.add_argument("--fps", default=15, type=int, help="Frames per second")
arger.add_argument("--width", default=400, type=int, help="Image width")
arger.add_argument("--optimize", default=True, action="store_true", help="Reduce GIF filesize")
if len(argv) == 0:
argv = ["--help"]
return arger.parse_args(argv)
def md5(text: str) -> str:
return hashlib.md5(text.encode("utf-8")).hexdigest()
def main():
args = parse_args(sys.argv[1:])
video_path = Path(args.video_path)
from_time = args.from_time
try:
time = datetime.timedelta(seconds=round(float(from_time), 1))
from_time = str(time)
if time.total_seconds() < 3600:
from_time = from_time[2:]
except ValueError:
pass
save_path = None
if str(args.video_path).startswith("http"):
save_path = Path("~/Downloads").expanduser() / f"{md5(str(video_path))}__{from_time.replace(':', '')}.gif"
gif_path = create_gif(
video_path=video_path,
save_path=save_path,
from_time=from_time,
duration=args.duration,
fps=args.fps,
width=args.width,
)
if args.optimize:
try:
gif_path = optimize_gif(gif_path)
except:
pass
print(gif_path.resolve())
if __name__ == "__main__":
main()