Spaces:
Running
Running
| import base64 | |
| import os | |
| import contextlib | |
| import logging | |
| from typing import Optional | |
| import uvicorn | |
| from fastapi import FastAPI, Header, HTTPException, Request, BackgroundTasks | |
| from fastapi.responses import JSONResponse | |
| from fastapi.concurrency import run_in_threadpool | |
| from pydantic import BaseModel, Field | |
| # Setup Logging | |
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - [AudioShield] - %(levelname)s - %(message)s') | |
| logger = logging.getLogger(__name__) | |
| # Import our detection engine | |
| from detect import detector | |
| # load_dotenv() - REMOVED per user request for Render deployment | |
| # LIFESPAN MANAGER (Resolves Cold Start) | |
| async def lifespan(app: FastAPI): | |
| # Startup Logic | |
| logger.info("--- Warming up the AI engine... ---") | |
| try: | |
| # Trigger model loading in a threadpool so it doesn't block startup completely if async | |
| # But here we want to block until ready. | |
| # Run a dummy inference to ensure weights are on device | |
| dummy_audio = b'\x00' * 16000 # 1 sec silent | |
| await run_in_threadpool(detector.analyze_audio, dummy_audio, "English") | |
| logger.info("--- AI Engine Ready & Warmed Up! ---") | |
| except Exception as e: | |
| logger.error(f"Warmup failed: {e}") | |
| yield | |
| # Shutdown Logic | |
| logger.info("--- Shutting down AudioShield ---") | |
| app = FastAPI( | |
| title="AudioShield AI: Voice Fraud Detector", | |
| version="2.0", | |
| docs_url="/docs", | |
| lifespan=lifespan | |
| ) | |
| # CONFIGURATION | |
| # Default key from problem statement example: sk_test_123456789 | |
| VALID_API_KEY = os.getenv("API_KEY", "sk_test_123456789") | |
| # MODELS (Strict Adherence to Spec) | |
| class VoiceDetectionRequest(BaseModel): | |
| language: str = Field(..., description="Language: Tamil, English, Hindi, Malayalam, Telugu") | |
| audioFormat: str = Field(..., pattern="^(?i)mp3$", description="Must be 'mp3'") | |
| audioBase64: str = Field(..., description="Base64 encoded MP3 audio") | |
| class VoiceDetectionResponse(BaseModel): | |
| status: str | |
| language: str | |
| classification: str # AI_GENERATED or HUMAN | |
| confidenceScore: float | |
| explanation: str | |
| # ROUTES | |
| async def detect_voice( | |
| request: VoiceDetectionRequest | |
| ): | |
| # 1. Security Check - REMOVED for Public Access per user request | |
| # logger.info(f"Public Access: Processing request for {request.language}") | |
| try: | |
| # 2. Basic Validation (Logic) | |
| if request.audioFormat.lower() != "mp3": | |
| # Just to be perfectly safe, though Pydantic regex handles it | |
| raise ValueError("Only MP3 format is supported.") | |
| # 3. Decode Base64 | |
| try: | |
| audio_data = base64.b64decode(request.audioBase64) | |
| except Exception: | |
| raise ValueError("Invalid Base64 encoding.") | |
| if not audio_data: | |
| raise ValueError("Empty audio data.") | |
| # 4. Perform Detection (Non-Blocking) | |
| # We run the synchronous detector.analyze_audio in a threadpool | |
| # so the API remains responsive to other requests. | |
| logger.info(f"Processing request for language: {request.language}") | |
| result = await run_in_threadpool(detector.analyze_audio, audio_data, request.language) | |
| if "error" in result: | |
| # If internal analysis failed, we still want to return a strict error format if possible, | |
| # or map it to the error response. | |
| raise ValueError(result["error"]) | |
| # 5. Return formatted response (Strict JSON) | |
| return VoiceDetectionResponse( | |
| status="success", | |
| language=request.language, | |
| classification=result["classification"], | |
| confidenceScore=result["confidenceScore"], | |
| explanation=result["explanation"] | |
| ) | |
| except ValueError as ve: | |
| logger.error(f"Validation Error: {ve}") | |
| return JSONResponse( | |
| status_code=400, | |
| content={"status": "error", "message": str(ve)} | |
| ) | |
| except Exception as e: | |
| logger.error(f"Internal Error: {e}") | |
| return JSONResponse( | |
| status_code=500, | |
| content={"status": "error", "message": "Internal server error processing audio."} | |
| ) | |
| def health_check(): | |
| return { | |
| "status": "online", | |
| "service": "AudioShield AI (Hackathon Edition)", | |
| "models_loaded": len(detector.pipelines) if hasattr(detector, 'pipelines') else 0 | |
| } | |
| # Standard execution for HF Spaces (uvicorn launched via Docker CMD) | |