feat(downloads): a clip or an edit can be a GIF, shared as a picture

A clip and an editor range can come out as a silent GIF (long side
320/480/640, 12 fps, at most 30 s, one palette per GIF); one over 30 MB
is not kept. A shared picture is served inline under the preview's
sandbox, and a GIF's link card is the GIF itself up to 15 MB, the still
for Facebook's crawler.
This commit is contained in:
2026-11-11 01:50:55 +01:00
parent fe38c07e7e
commit c221452a00
24 changed files with 885 additions and 58 deletions
+23 -2
View File
@@ -109,6 +109,12 @@ def normalize_edit_spec(spec: dict | None) -> dict:
spec = spec or {}
out: dict = {}
# A GIF output (downloads/gif) keeps its size; one it cannot name is dropped here and refused by
# the service, which reads the raw spec for exactly that (`gif_side`).
side = gif_side(spec)
if side is not None:
out["gif"] = {"side": side}
# Crop applies to both a single trim and a multi-segment join.
crop = spec.get("crop") or {}
if crop:
@@ -197,6 +203,21 @@ def normalize_edit_spec(spec: dict | None) -> dict:
return out
def gif_side(spec: dict | None) -> int | None:
"""The GIF size a raw edit spec asks for, or None when it asks for none — or for one that is not
offered (the service tells the two apart by `"gif" in spec`)."""
# Imported here: `gif` builds its command on this module's input guard.
from app.downloads import gif as gifmod
raw = (spec or {}).get("gif")
if not isinstance(raw, dict):
return None
try:
return gifmod.normalize_side(raw.get("side"))
except ValueError: # silent-ok: an unoffered size is refused by the service (`bad_gif`)
return None
def edit_sig(spec: dict) -> str:
"""Stable 24-char signature of a (normalized) edit spec — the per-user cache identity."""
payload = json.dumps(normalize_edit_spec(spec), sort_keys=True, separators=(",", ":"))
@@ -208,8 +229,8 @@ def has_crop(spec: dict) -> bool:
def needs_reencode(spec: dict) -> bool:
"""A crop always re-encodes; a trim re-encodes only when the user asked for an accurate cut."""
return bool(spec.get("crop")) or bool(spec.get("accurate"))
"""A crop or a GIF always re-encodes; a trim only when the user asked for an accurate cut."""
return bool(spec.get("crop")) or bool(spec.get("gif")) or bool(spec.get("accurate"))
def clip_duration(spec: dict, source_duration: int | None) -> int | None: