Add explicit detectors and optional auth
This commit is contained in:
63
app/main.py
63
app/main.py
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user