feat: serve responsive WebP srcsets for faster mobile browsing

On-demand ?w= resize with a disk cache under webcache/; downloads stay full-resolution.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Frank Schwenk
2026-08-01 13:04:33 +02:00
parent b09f90bfcc
commit dec74a46d7
9 changed files with 217 additions and 12 deletions
+22 -3
View File
@@ -13,7 +13,7 @@ from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from . import config, pipeline, remix
from . import config, images, pipeline, remix
from .logging_config import configure_logging
configure_logging()
@@ -23,6 +23,9 @@ app = FastAPI(title=config.SITE_TITLE)
BASE_DIR = Path(__file__).resolve().parent
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
templates.env.globals["img_attrs"] = images.img_attrs
templates.env.globals["PAIR_SIZES"] = images.PAIR_SIZES
templates.env.globals["GRID_SIZES"] = images.GRID_SIZES
app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static")
# Job id = sanitized source stem (may include dots).
@@ -120,9 +123,25 @@ def job_detail(request: Request, job_id: str) -> HTMLResponse:
@app.get("/jobs/{job_id}/files/{filename:path}")
def job_file(job_id: str, filename: str) -> FileResponse:
def job_file(
job_id: str,
filename: str,
w: int | None = Query(None, description="Longest-edge width for srcset WebP"),
) -> FileResponse:
path = _safe_job_file(job_id, filename)
return FileResponse(path)
if w is None:
return FileResponse(path)
if w not in images.SRCSET_WIDTHS:
raise HTTPException(
status_code=400,
detail=f"Ungueltige Breite (erlaubt: {', '.join(map(str, images.SRCSET_WIDTHS))})",
)
try:
cached = images.get_or_create_resized(path, w)
except OSError as exc:
logger.warning("resize failed for %s w=%s: %s", path.name, w, exc)
raise HTTPException(status_code=500, detail="Bild konnte nicht skaliert werden") from exc
return FileResponse(cached, media_type="image/webp", filename=cached.name)
@app.get("/jobs/{job_id}/remix", response_class=HTMLResponse)