89 lines
3.2 KiB
Python
89 lines
3.2 KiB
Python
from fastapi import FastAPI, File, HTTPException, UploadFile
|
|
from fastapi.responses import HTMLResponse, StreamingResponse
|
|
from app.config import settings
|
|
|
|
app = FastAPI(title="face-lock", version="0.1.0")
|
|
|
|
|
|
@app.get("/health")
|
|
def health():
|
|
return {"ok": True, "env": settings.env}
|
|
|
|
|
|
@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>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<title>face-lock</title>
|
|
</head>
|
|
<body class="bg-slate-950 text-slate-100 min-h-screen">
|
|
<main class="mx-auto max-w-5xl p-6">
|
|
<div class="mb-6">
|
|
<h1 class="text-3xl font-bold">face-lock</h1>
|
|
<p class="text-slate-400">Drop an image, get the primary subject squared and cropped.</p>
|
|
</div>
|
|
<div class="grid gap-6 md:grid-cols-2">
|
|
<section class="rounded-2xl border border-slate-800 bg-slate-900 p-4">
|
|
<input id="file" type="file" accept="image/*" class="block w-full rounded-lg border border-slate-700 bg-slate-950 p-3" />
|
|
<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>
|
|
<section class="rounded-2xl border border-slate-800 bg-slate-900 p-4">
|
|
<div class="mb-3 text-sm font-semibold text-slate-400">Result</div>
|
|
<img id="result" class="hidden w-full rounded-xl border border-slate-800" />
|
|
</section>
|
|
</div>
|
|
</main>
|
|
<script>
|
|
const file = document.getElementById('file');
|
|
const go = document.getElementById('go');
|
|
const result = document.getElementById('result');
|
|
const meta = document.getElementById('meta');
|
|
go.onclick = async () => {
|
|
if (!file.files.length) return;
|
|
const form = new FormData();
|
|
form.append('file', file.files[0]);
|
|
meta.textContent = 'Working...';
|
|
const resp = await fetch('/api/focus', { method: 'POST', body: form });
|
|
const data = await resp.json();
|
|
meta.textContent = JSON.stringify(data, null, 2);
|
|
result.src = data.crop_data_url;
|
|
result.classList.remove('hidden');
|
|
};
|
|
</script>
|
|
</body>
|
|
</html>
|
|
"""
|
|
)
|
|
|
|
|
|
@app.post("/api/focus")
|
|
async def focus(file: UploadFile = File(...)):
|
|
from app.vision import process_image
|
|
|
|
try:
|
|
payload = await file.read()
|
|
return process_image(payload, file.filename or "upload")
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
|
|
|
|
@app.post("/api/focus/image")
|
|
async def focus_image(file: UploadFile = File(...)):
|
|
from app.vision import process_image
|
|
|
|
try:
|
|
payload = await file.read()
|
|
result = process_image(payload, file.filename or "upload")
|
|
return StreamingResponse(result["crop_bytes_io"], media_type=result["mime_type"])
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|