DetectX / app /main.py
solvion-iq's picture
Fix audio CNN+GRU model and return all analyzed frames.
26817f1
Raw
History Blame Contribute Delete
3.47 kB
from __future__ import annotations
import hashlib
import os
from pathlib import Path
from fastapi import FastAPI, File, HTTPException, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from app.config import settings
from app.jobs import JobStatus, create_job, get_job, job_to_dict
from app.pipeline.orchestrator import run_pipeline
app = FastAPI(
title=settings.app_name,
version=settings.app_version,
description="Detect-X forensic media analysis API (Alpha)",
)
origins = [o.strip() for o in settings.cors_origins.split(",") if o.strip()]
app.add_middleware(
CORSMiddleware,
allow_origins=origins if origins != ["*"] else ["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
def models_ready() -> dict[str, bool]:
models_path = Path(settings.models_dir)
visual = models_path / settings.visual_model_file
audio = models_path / settings.audio_model_file
return {
"visual_model": visual.exists(),
"audio_model": audio.exists(),
"models_dir": str(models_path),
}
@app.get("/")
def root():
return {
"service": settings.app_name,
"version": settings.app_version,
"status": "online",
"docs": "/docs",
}
@app.get("/health")
def health():
ready = models_ready()
return {
"status": "healthy" if all(ready.values()) else "degraded",
"models": ready,
"limits": {
"max_upload_mb": settings.max_upload_mb,
"max_video_seconds": settings.max_video_seconds,
},
}
@app.post("/analyze")
async def analyze(file: UploadFile = File(...)):
if not file.filename:
raise HTTPException(status_code=400, detail="Filename is required")
content = await file.read()
size_mb = len(content) / (1024 * 1024)
if size_mb > settings.max_upload_mb:
raise HTTPException(
status_code=413,
detail=f"File exceeds {settings.max_upload_mb} MB limit",
)
file_hash = hashlib.sha256(content).hexdigest()
job = create_job(file.filename)
job.status = JobStatus.RUNNING
job.progress = 5
job.result = {"sha256": file_hash, "size_bytes": len(content)}
def on_progress(stage: str, progress: int) -> None:
job.current_stage = stage
job.progress = progress
try:
job.result = run_pipeline(
job=job,
content=content,
filename=file.filename,
on_progress=on_progress,
)
job.result["sha256"] = file_hash
job.result["size_bytes"] = len(content)
job.status = JobStatus.COMPLETED
job.progress = 100
job.current_stage = "report"
except ValueError as exc:
job.status = JobStatus.FAILED
job.error = str(exc)
raise HTTPException(status_code=400, detail=str(exc)) from exc
except Exception as exc:
job.status = JobStatus.FAILED
job.error = str(exc)
raise HTTPException(status_code=500, detail=f"Analysis failed: {exc}") from exc
return JSONResponse(job_to_dict(job))
@app.get("/jobs/{job_id}")
def job_status(job_id: str):
job = get_job(job_id)
if not job:
raise HTTPException(status_code=404, detail="Job not found")
return job_to_dict(job)
if os.getenv("SPACE_ID"):
@app.get("/spaces-config")
def spaces_config():
return {"ready": True}