initial commit
This commit is contained in:
Executable
+121
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env python3.9
|
||||
import argparse
|
||||
import datetime
|
||||
import math
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import typing
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
import ffmpeg_gif
|
||||
|
||||
def parse_duration(text: str) -> datetime.timedelta:
|
||||
re_duration = re.compile(r"(\d+(?:\.\d+)?(?:sec|min|hour|hr|h|m|s))")
|
||||
re_digits = re.compile(r"([\d.]+)")
|
||||
time_scales = {
|
||||
"s": 1,
|
||||
"sec": 1,
|
||||
"m": 60,
|
||||
"min": 60,
|
||||
"h": 3600,
|
||||
"hr": 3600,
|
||||
}
|
||||
|
||||
parts = re_duration.findall(text)
|
||||
if parts:
|
||||
total_secs = 0
|
||||
for it in parts:
|
||||
if m := re_digits.search(it):
|
||||
_, end = m.span()
|
||||
scale = it[end:]
|
||||
total_secs += math.ceil(float(m.group(1)) * time_scales[scale])
|
||||
return datetime.timedelta(seconds=total_secs)
|
||||
|
||||
parts = [float(it) for it in text.split(":")]
|
||||
if len(parts) == 1:
|
||||
return datetime.timedelta(seconds=parts[0])
|
||||
if len(parts) == 2:
|
||||
return datetime.timedelta(minutes=parts[0], seconds=parts[1])
|
||||
if len(parts) == 3:
|
||||
return datetime.timedelta(hours=parts[0], minutes=parts[1], seconds=parts[2])
|
||||
|
||||
|
||||
|
||||
def parse_args(argv: typing.List[str] = None) -> argparse.Namespace:
|
||||
arger = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
arger.add_argument("video_path", type=Path, help="Path to video file")
|
||||
arger.add_argument("--interval", "-i", default=60, type=int, help="Interval between GIFs in seconds")
|
||||
arger.add_argument("--duration", "-d", default=3, type=int, help="GIF duration in seconds")
|
||||
arger.add_argument("--fps", default=18, type=float, 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")
|
||||
arger.add_argument("--from", dest='from_time', type=parse_duration, help="Start from timestamp")
|
||||
arger.add_argument("--to", dest='to_time', type=parse_duration, help="Stop at timestamp")
|
||||
|
||||
if len(argv) == 0:
|
||||
argv = ["--help"]
|
||||
|
||||
return arger.parse_args(argv)
|
||||
|
||||
|
||||
def get_video_duration(video_path: Path) -> datetime.timedelta:
|
||||
# fmt: off
|
||||
args = [
|
||||
'ffprobe',
|
||||
'-v', 'error',
|
||||
'-show_entries', 'format=duration',
|
||||
'-of', 'default=noprint_wrappers=1:nokey=1',
|
||||
video_path,
|
||||
]
|
||||
# fmt: on
|
||||
p = subprocess.run(args, stdout=subprocess.PIPE, check=True)
|
||||
return datetime.timedelta(seconds=round(float(p.stdout), 1))
|
||||
|
||||
|
||||
def summarize(video_path: Path, width: int = 400, gif_duration: int = 3, fps: int = 15, interval_seconds: int = 60, optimize: bool = True,
|
||||
from_time: typing.Optional[datetime.timedelta] = None, to_time: typing.Optional[datetime.timedelta] = None) -> None:
|
||||
duration = get_video_duration(video_path)
|
||||
|
||||
if not from_time:
|
||||
from_time = datetime.timedelta(minutes=2)
|
||||
if not to_time:
|
||||
to_time = duration
|
||||
|
||||
count = math.ceil((to_time - from_time).total_seconds() // interval_seconds)
|
||||
interval = (to_time - from_time) / count
|
||||
for i in range(count):
|
||||
start = from_time + datetime.timedelta(seconds=round(interval.total_seconds() * i, 1))
|
||||
end = start + datetime.timedelta(seconds=gif_duration)
|
||||
if end >= to_time:
|
||||
break
|
||||
gif_path = ffmpeg_gif.create_gif(
|
||||
video_path=video_path,
|
||||
from_time=str(start),
|
||||
to_time=str(end),
|
||||
width=width,
|
||||
fps=fps,
|
||||
duration=duration,
|
||||
)
|
||||
if optimize:
|
||||
gif_path = ffmpeg_gif.optimize_gif(gif_path)
|
||||
print(str(gif_path.absolute()))
|
||||
|
||||
def main():
|
||||
args = parse_args(sys.argv[1:])
|
||||
video_path: Path = args.video_path
|
||||
summarize(
|
||||
video_path=video_path,
|
||||
width=args.width,
|
||||
gif_duration=args.duration,
|
||||
fps=args.fps,
|
||||
optimize=args.optimize,
|
||||
interval_seconds=args.interval,
|
||||
from_time=args.from_time,
|
||||
to_time=args.to_time,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user