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
+5 -4
View File
@@ -387,18 +387,19 @@ async def index() -> FileResponse:
@app.get("/watch/{token}")
@app.get("/file/{token}")
@app.get("/folder/{token}")
async def share_page(token: str):
async def share_page(token: str, request: Request):
"""Serve the public page of a share link, with per-link Open Graph tags injected so the
link unfurls richly (title/channel/thumbnail) in Messenger/social crawlers. A real browser
ignores the extra tags and boots WatchPage; invalid/expired/password links fall back to the
generic card."""
generic card. A GIF's card picture depends on which crawler asks (`og._moving_image`), so the
answer varies by User-Agent."""
from app.downloads import og
with SessionLocal() as db:
rendered = og.render_watch_html(token, WATCH_HTML, db)
rendered = og.render_watch_html(token, WATCH_HTML, db, request.headers.get("user-agent"))
if rendered is None:
return FileResponse(WATCH_HTML, headers=INDEX_HEADERS)
return HTMLResponse(rendered, headers=INDEX_HEADERS)
return HTMLResponse(rendered, headers={**INDEX_HEADERS, "Vary": "User-Agent"})
@app.get("/{full_path:path}")