Spaces:
Sleeping
Sleeping
| 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 | |
| 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 | |
| def health_check(): | |
| return {"status": "online", "model_loaded": True} | |
| 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) | |