Files

39 lines
1.6 KiB
Python

"""Size and a still frame of a clip, read straight from Zipline with ffprobe/ffmpeg, for Discord's link preview."""
import json
import subprocess
import threading
from functools import lru_cache
# /v/ and /_site/thumb need no login: cap how many ffmpeg/ffprobe processes they can start at once
SLOTS = threading.BoundedSemaphore(2)
@lru_cache(maxsize=256)
def video_size(url: str, length: str) -> tuple[int, int] | None:
"""(width, height) of the first video stream; `length` is only part of the cache key (a reused name)."""
cmd = ["ffprobe", "-v", "error", "-select_streams", "v:0",
"-show_entries", "stream=width,height", "-of", "json", url]
try:
with SLOTS:
result = subprocess.run(cmd, capture_output=True, timeout=10)
stream = json.loads(result.stdout)["streams"][0]
return int(stream["width"]), int(stream["height"])
except (OSError, subprocess.TimeoutExpired, ValueError, KeyError, IndexError):
return None
@lru_cache(maxsize=64)
def still_jpeg(url: str, length: str) -> bytes | None:
"""One frame (1 s in, or the first one for very short clips) as a JPEG at most 720 px tall."""
for seek in ("1", "0"):
cmd = ["ffmpeg", "-v", "error", "-ss", seek, "-i", url, "-frames:v", "1",
"-vf", "scale=-2:'min(720,ih)'", "-f", "image2", "-c:v", "mjpeg", "pipe:1"]
try:
with SLOTS:
result = subprocess.run(cmd, capture_output=True, timeout=30)
except (OSError, subprocess.TimeoutExpired):
return None
if result.returncode == 0 and result.stdout:
return result.stdout
return None