from fastapi import FastAPI, HTTPException, Header, Depends from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse import uvicorn import base64 import os import sys from dotenv import load_dotenv # Load env load_dotenv(os.path.join(os.path.dirname(__file__), '../../.env')) # Add src to path sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) from src.api.schemas import DetectionRequest, DetectionResponse, ErrorResponse from src.api.inference import predict_pipeline, load_resources app = FastAPI( title="AI Voice Detection API", description="Detects AI-generated voice samples in Tamil, English, Hindi, Malayalam, Telugu.", version="1.0.0" ) # CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Startup @app.on_event("startup") async def startup_event(): print("Initializing API...") load_resources() # Auth - Load from environment API_KEY = os.getenv("API_KEY", "12345") # Default for local testing # STRICT: Only accept full language names as per competition rules # Short codes like 'en', 'ta', 'hi' are NOT allowed SUPPORTED_LANGUAGES = { 'tamil': 'Tamil', 'english': 'English', 'hindi': 'Hindi', 'malayalam': 'Malayalam', 'telugu': 'Telugu' } def fix_base64_padding(b64_string: str) -> str: """ Fix Base64 padding if missing. Some clients send Base64 without proper padding (== or =). """ # Remove any whitespace b64_string = b64_string.strip() # Add padding if needed padding_needed = len(b64_string) % 4 if padding_needed: b64_string += '=' * (4 - padding_needed) return b64_string async def verify_api_key(x_api_key: str = Header(None)): if not x_api_key or x_api_key != API_KEY: return None # Will be handled in endpoint return x_api_key @app.get("/") def health_check(): return {"status": "online", "model_loaded": True} @app.post("/api/voice-detection", responses={ 200: {"model": DetectionResponse}, 400: {"model": ErrorResponse}, 403: {"model": ErrorResponse} }) async def detect_voice(request: DetectionRequest, x_api_key: str = Header(None)): """ Detect whether a voice sample is AI-generated or Human. Required headers: - x-api-key: Your API key Supported languages: Tamil, English, Hindi, Malayalam, Telugu """ try: # Validate API key first if not x_api_key or x_api_key != API_KEY: return JSONResponse( status_code=403, content={"status": "error", "message": "Invalid API key or malformed request"} ) # STRICT language validation - only full names allowed lang_normalized = request.language.lower().strip() if lang_normalized not in SUPPORTED_LANGUAGES: return JSONResponse( status_code=400, content={ "status": "error", "message": f"Unsupported language: {request.language}. Supported languages are: Tamil, English, Hindi, Malayalam, Telugu (exact names only)" } ) language_name = SUPPORTED_LANGUAGES[lang_normalized] # Validate audio format (expanded for flexibility) supported_formats = ['mp3', 'wav', 'flac', 'ogg', 'm4a'] if request.audio_format.lower() not in supported_formats: return JSONResponse( status_code=400, content={ "status": "error", "message": f"Unsupported format: {request.audio_format}. Supported: {', '.join(supported_formats)}" } ) # Decode base64 with padding fix try: # Fix padding if missing (some clients don't include proper padding) b64_fixed = fix_base64_padding(request.audio_base64) audio_bytes = base64.b64decode(b64_fixed) except Exception as decode_err: return JSONResponse( status_code=400, content={"status": "error", "message": f"Invalid Base64 string: {str(decode_err)}"} ) # Validate audio size (max 10MB) if len(audio_bytes) > 10 * 1024 * 1024: return JSONResponse( status_code=400, content={"status": "error", "message": "Audio file too large. Max 10MB."} ) # Predict result = predict_pipeline(audio_bytes) # Build response matching competition specification exactly response = { "status": "success", "language": language_name, "classification": result['result'], # AI_GENERATED or HUMAN "confidenceScore": round(result['confidence'], 2), "explanation": result['explanation'] } return JSONResponse(status_code=200, content=response) except Exception as e: print(f"Error: {e}") return JSONResponse( status_code=500, content={"status": "error", "message": str(e)} ) if __name__ == "__main__": uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)