Add explicit detectors and optional auth

This commit is contained in:
2026-04-11 17:23:25 +02:00
parent 19a5ac16b7
commit 09119e8c0e
7 changed files with 189 additions and 65 deletions

View File

@@ -1,21 +1,36 @@
from io import BytesIO
from fastapi import FastAPI, File, Form, HTTPException, UploadFile
from fastapi import Depends, FastAPI, File, Form, HTTPException, Request, UploadFile
from fastapi.responses import HTMLResponse, StreamingResponse
from app.config import settings
app = FastAPI(title="face-lock", version="0.2.0")
app = FastAPI(title="face-lock", version="0.3.0")
def require_auth(request: Request) -> None:
if not settings.auth_enabled:
return
header_name = settings.auth_header_name.lower()
provided = request.headers.get(header_name)
if not provided and request.headers.get("authorization", "").lower().startswith("bearer "):
provided = request.headers.get("authorization", "")[7:].strip()
if provided != settings.auth_token:
raise HTTPException(status_code=401, detail="unauthorized")
@app.get("/health")
def health():
return {"ok": True, "env": settings.env}
return {
"ok": True,
"env": settings.env,
"auth_enabled": settings.auth_enabled,
"auth_header": settings.auth_header_name if settings.auth_enabled else None,
}
@app.get("/", response_class=HTMLResponse)
def index():
if settings.env != "dev":
return HTMLResponse("<h1>face-lock</h1><p>Set ENV=dev for the UI.</p>")
return HTMLResponse(
"""
<!doctype html>
@@ -28,9 +43,12 @@ def index():
</head>
<body class="bg-slate-950 text-slate-100 min-h-screen">
<main class="mx-auto max-w-6xl p-6">
<div class="mb-6">
<h1 class="text-3xl font-bold">face-lock</h1>
<p class="text-slate-400">Auto-detect the subject, square it up, and crop with buffer.</p>
<div class="mb-6 flex items-center justify-between gap-4">
<div>
<h1 class="text-3xl font-bold">face-lock</h1>
<p class="text-slate-400">Square the subject, crop it, and keep the raw blobs out of sight.</p>
</div>
<a class="rounded-lg border border-slate-700 px-3 py-2 text-sm text-cyan-300 hover:bg-slate-900" href="/docs" target="_blank">Docs</a>
</div>
<div class="grid gap-6 md:grid-cols-2">
<section class="rounded-2xl border border-slate-800 bg-slate-900 p-4">
@@ -40,10 +58,10 @@ def index():
<div>
<label class="block text-sm text-slate-400">Detector</label>
<select id="detector" class="mt-2 block w-full rounded-lg border border-slate-700 bg-slate-950 p-3">
<option value="auto">Auto</option>
<option value="face">Face</option>
<option value="animal">Animal</option>
<option value="person">Person</option>
<option value="salient">Subject</option>
<option value="subject" selected>Subject</option>
</select>
</div>
<div>
@@ -51,6 +69,10 @@ def index():
<input id="buffer_ratio" type="number" step="0.05" min="0" max="0.6" value="0.20" class="mt-2 block w-full rounded-lg border border-slate-700 bg-slate-950 p-3" />
</div>
</div>
<div class="mt-4">
<label class="block text-sm text-slate-400">Auth token (only if enabled)</label>
<input id="auth_token" type="password" placeholder="paste token here" class="mt-2 block w-full rounded-lg border border-slate-700 bg-slate-950 p-3" />
</div>
<button id="go" class="mt-4 rounded-lg bg-cyan-500 px-4 py-2 font-semibold text-slate-950">Process</button>
<pre id="meta" class="mt-4 whitespace-pre-wrap rounded-lg bg-slate-950 p-3 text-xs text-slate-300"></pre>
</section>
@@ -82,8 +104,15 @@ def index():
form.append('detector', document.getElementById('detector').value);
form.append('buffer_ratio', document.getElementById('buffer_ratio').value);
meta.textContent = 'Working...';
const resp = await fetch('/api/focus', { method: 'POST', body: form });
const headers = {};
const token = document.getElementById('auth_token').value.trim();
if (token) headers['__AUTH_HEADER_NAME__'] = token;
const resp = await fetch('/api/focus', { method: 'POST', body: form, headers });
const data = await resp.json();
if (!resp.ok) {
meta.textContent = JSON.stringify(data, null, 2);
return;
}
meta.textContent = JSON.stringify({
filename: data.filename,
detector: data.detector,
@@ -101,15 +130,17 @@ def index():
</script>
</body>
</html>
"""
""".replace("__AUTH_HEADER_NAME__", settings.auth_header_name)
)
@app.post("/api/focus")
async def focus(
request: Request,
file: UploadFile = File(...),
buffer_ratio: float = Form(0.15),
detector: str = Form("auto"),
detector: str = Form("subject"),
_auth: None = Depends(require_auth),
):
from app.vision import process_image
@@ -133,15 +164,17 @@ async def focus(
@app.post("/api/focus/image")
async def focus_image(
request: Request,
file: UploadFile = File(...),
buffer_ratio: float = Form(0.15),
detector: str = Form("auto"),
detector: str = Form("subject"),
_auth: None = Depends(require_auth),
):
from app.vision import process_image
try:
payload = await file.read()
result = process_image(payload, file.filename or "upload", buffer_ratio=buffer_ratio, detector=detector)
return StreamingResponse(BytesIO(result["crop_bytes"]), media_type=result["mime_type"])
return StreamingResponse(BytesIO(result["crop_bytes"]), media_type="image/jpeg")
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc