feat: harden pipeline UX — preprocess, rembg alpha, remix/lightbox

Downscale to 2000px JPEG before rembg, random blend opacity, timeout
retries, per-variant remix prefill, lightbox, and longer SFTP idle.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Frank Schwenk
2026-07-16 23:35:06 +02:00
parent 8ef123393a
commit 83d4468b69
16 changed files with 594 additions and 50 deletions
+40 -2
View File
@@ -8,7 +8,7 @@ from pathlib import Path
from typing import Any
from urllib.parse import quote
from fastapi import FastAPI, Form, HTTPException, Request
from fastapi import FastAPI, Form, HTTPException, Query, Request
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
@@ -119,13 +119,49 @@ def job_file(job_id: str, rel_path: str) -> FileResponse:
@app.get("/jobs/{job_id}/remix", response_class=HTMLResponse)
def remix_form(request: Request, job_id: str, error: str | None = None) -> HTMLResponse:
def remix_form(
request: Request,
job_id: str,
error: str | None = None,
from_variant: str | None = Query(None, alias="from"),
) -> HTMLResponse:
job_root = _job_root(job_id)
if not (job_root / "rembg.png").exists():
raise HTTPException(status_code=409, detail="Job hat noch kein Rembg-Ergebnis, Remix noch nicht moeglich.")
assets = pipeline.load_assets()
options = remix.build_remix_options(assets)
prefill: dict[str, str] = {
"bg_filter": "",
"bg_blend": "",
"fg_filter": "",
"fg_blend": "",
"opacity": config.BLEND_OPACITY if config.BLEND_OPACITY in config.OPACITY_CHOICES else "30%",
}
if from_variant:
manifest = pipeline.read_manifest(job_id) or {}
match = next((v for v in manifest.get("variants", []) if v.get("id") == from_variant), None)
if match:
opacity = match.get("blend_opacity") or prefill["opacity"]
if opacity not in config.OPACITY_CHOICES:
# Snap odd random values (e.g. 37%) to nearest offered choice.
try:
pct = int(str(opacity).rstrip("%"))
nearest = min(
config.OPACITY_CHOICES,
key=lambda c: abs(int(c.rstrip("%")) - pct),
)
opacity = nearest
except ValueError:
opacity = prefill["opacity"]
prefill = {
"bg_filter": match.get("background_filter") or "",
"bg_blend": match.get("background_blend") or "",
"fg_filter": match.get("foreground_filter") or "",
"fg_blend": match.get("foreground_blend") or "",
"opacity": opacity,
}
return templates.TemplateResponse(
"remix.html",
{
@@ -134,6 +170,8 @@ def remix_form(request: Request, job_id: str, error: str | None = None) -> HTMLR
"job_id": job_id,
"options": options,
"opacity_choices": config.OPACITY_CHOICES,
"prefill": prefill,
"from_variant": from_variant,
"error": error,
},
)