Spaces:
Sleeping
Sleeping
| """ | |
| DeepGuard β FastAPI Backend | |
| Real-time, stateless deepfake detection API. | |
| Endpoints: | |
| GET /health β Liveness check (used by extension popup) | |
| POST /analyze β Analyze an image for AI-generation artifacts | |
| All data is processed in RAM and dropped immediately after the response. | |
| """ | |
| import io | |
| import base64 | |
| import traceback | |
| from contextlib import asynccontextmanager | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel | |
| from PIL import Image | |
| import inference | |
| import metadata as meta_module | |
| import heatmap as heatmap_module | |
| # --------------------------------------------------------------------------- | |
| # Lifespan: load model once at startup | |
| # --------------------------------------------------------------------------- | |
| async def lifespan(app: FastAPI): | |
| print("[DeepGuard] Starting up β loading ONNX model...") | |
| try: | |
| inference.load_model() | |
| print("[DeepGuard] Model ready. Server is live at http://localhost:8000") | |
| except FileNotFoundError as e: | |
| print(f"[DeepGuard] WARNING: {e}") | |
| print("[DeepGuard] Run 'python download_model.py' to fetch the model.") | |
| yield | |
| print("[DeepGuard] Shutting down.") | |
| # --------------------------------------------------------------------------- | |
| # App setup | |
| # --------------------------------------------------------------------------- | |
| app = FastAPI( | |
| title="DeepGuard API", | |
| description="Real-time stateless deepfake detection using ViT + ONNX Runtime", | |
| version="1.0.0", | |
| lifespan=lifespan, | |
| ) | |
| # Aggressive CORS β required because Chrome extensions use a chrome-extension:// origin | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=False, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| import ela as ela_module | |
| # --------------------------------------------------------------------------- | |
| # Request / Response schemas | |
| # --------------------------------------------------------------------------- | |
| class AnalyzeRequest(BaseModel): | |
| image_data: str # data:image/...;base64,<payload> OR raw base64 | |
| class MetadataResult(BaseModel): | |
| exif_data_present: bool | |
| software_signature_found: str | |
| warning: str | |
| class ForensicsResult(BaseModel): | |
| model_reasoning: str | |
| metadata: MetadataResult | |
| class AnalyzeResponse(BaseModel): | |
| status: str | |
| threat_level: str | |
| confidence_score: float | |
| heatmap_overlay_url: str | |
| ela_overlay_url: str | |
| forensics: ForensicsResult | |
| # --------------------------------------------------------------------------- | |
| # Helpers | |
| # --------------------------------------------------------------------------- | |
| def _decode_image(image_data: str) -> tuple[bytes, Image.Image]: | |
| """ | |
| Decode a base64 data-URI or raw base64 string into (raw_bytes, PIL Image). | |
| """ | |
| if image_data.startswith("data:"): | |
| # Strip "data:image/jpeg;base64," prefix | |
| header, b64_str = image_data.split(",", 1) | |
| else: | |
| b64_str = image_data | |
| try: | |
| raw_bytes = base64.b64decode(b64_str) | |
| except Exception: | |
| raise HTTPException(status_code=400, detail="Invalid base64 image data.") | |
| try: | |
| image = Image.open(io.BytesIO(raw_bytes)).convert("RGB") | |
| except Exception: | |
| raise HTTPException(status_code=400, detail="Could not decode image from base64 payload.") | |
| return raw_bytes, image | |
| # --------------------------------------------------------------------------- | |
| # Endpoints | |
| # --------------------------------------------------------------------------- | |
| async def health(): | |
| """Liveness check. Returns model load status.""" | |
| try: | |
| session = inference.get_session() | |
| return { | |
| "status": "ok", | |
| "model_loaded": True, | |
| "attention_heatmap": inference.has_attention_outputs(), | |
| } | |
| except RuntimeError: | |
| return { | |
| "status": "degraded", | |
| "model_loaded": False, | |
| "attention_heatmap": False, | |
| "message": "Model not loaded. Run python download_model.py", | |
| } | |
| async def analyze(request: AnalyzeRequest): | |
| """ | |
| Main analysis endpoint. | |
| Accepts a base64-encoded image and returns: | |
| - Deepfake confidence score | |
| - Threat level classification | |
| - Grad-CAM / attention heatmap overlay (base64 PNG) | |
| - EXIF metadata forensics | |
| """ | |
| # ββ 1. Decode image ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| try: | |
| raw_bytes, image = _decode_image(request.image_data) | |
| except HTTPException: | |
| raise | |
| except Exception as e: | |
| raise HTTPException(status_code=400, detail=f"Image decode error: {e}") | |
| # ββ 2. Metadata forensics (DFIR) βββββββββββββββββββββββββββββββββββββ | |
| try: | |
| forensic_meta = meta_module.extract_metadata(raw_bytes) | |
| except Exception: | |
| forensic_meta = { | |
| "exif_data_present": False, | |
| "software_signature_found": "None", | |
| "warning": "Metadata extraction failed.", | |
| } | |
| # ββ 3. AI inference βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| try: | |
| confidence_score, output_dict = inference.run_inference(image) | |
| except RuntimeError as e: | |
| raise HTTPException( | |
| status_code=503, | |
| detail=f"Model not loaded: {e}. Run python download_model.py first.", | |
| ) | |
| except Exception as e: | |
| import traceback | |
| traceback.print_exc() | |
| raise HTTPException(status_code=500, detail=f"Inference error: {e}") | |
| # ββ 4. Heatmap & ELA generation ββββββββββββββββββββββββββββββββββββββββ | |
| try: | |
| heatmap_url = heatmap_module.generate_heatmap(image, output_dict, confidence_score) | |
| except Exception: | |
| import traceback | |
| traceback.print_exc() | |
| # Fallback: return a 1Γ1 transparent PNG | |
| heatmap_url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" | |
| # Run Error Level Analysis (ELA) | |
| ela_url = ela_module.generate_ela(image) | |
| # ββ 5. Build response βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| threat_level = inference.get_threat_level(confidence_score) | |
| model_reasoning = inference.get_model_reasoning( | |
| confidence_score, | |
| forensic_meta["exif_data_present"], | |
| forensic_meta["software_signature_found"], | |
| ) | |
| return AnalyzeResponse( | |
| status="success", | |
| threat_level=threat_level, | |
| confidence_score=round(confidence_score, 4), | |
| heatmap_overlay_url=heatmap_url, | |
| ela_overlay_url=ela_url, | |
| forensics=ForensicsResult( | |
| model_reasoning=model_reasoning, | |
| metadata=MetadataResult(**forensic_meta), | |
| ), | |
| ) | |