Spaces:
Running
Running
| """DHVANI inference API for Hugging Face Spaces.""" | |
| from __future__ import annotations | |
| import logging | |
| import tempfile | |
| import time | |
| import uuid | |
| from collections import defaultdict | |
| from pathlib import Path | |
| from fastapi import FastAPI, File, HTTPException, Request, UploadFile | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from analyzer import VoiceAnalyzer | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format="%(asctime)s %(levelname)s %(name)s: %(message)s", | |
| ) | |
| logger = logging.getLogger("dhvani") | |
| ALLOWED = {".wav", ".mp3", ".m4a", ".ogg", ".flac", ".webm"} | |
| MAX_BYTES = 20 * 1024 * 1024 | |
| VERSION = "2.1.2" # multi-segment ML + premium-TTS safeguard for unseen voices | |
| RATE_LIMIT_WINDOW_SEC = 60 | |
| RATE_LIMIT_MAX = 20 | |
| app = FastAPI(title="DHVANI Inference", version=VERSION) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_methods=["GET", "POST"], | |
| allow_headers=["*"], | |
| ) | |
| analyzer = VoiceAnalyzer() | |
| _request_log: dict[str, list[float]] = defaultdict(list) | |
| def startup_warmup(): | |
| try: | |
| analyzer.warmup() | |
| logger.info("Models warmed up: %s", analyzer.models_loaded) | |
| except Exception as exc: | |
| logger.error("Model warmup failed: %s", exc) | |
| def rate_limited(client: str) -> bool: | |
| now = time.time() | |
| window = _request_log[client] | |
| _request_log[client] = [t for t in window if now - t < RATE_LIMIT_WINDOW_SEC] | |
| if len(_request_log[client]) >= RATE_LIMIT_MAX: | |
| return True | |
| _request_log[client].append(now) | |
| return False | |
| def health(): | |
| custom = analyzer._custom # noqa: SLF001 | |
| custom_info = None | |
| if custom.enabled: | |
| custom_info = { | |
| "loaded": custom.loaded, | |
| "version": custom.version if custom.loaded else "pending", | |
| "metadata": custom.metadata if custom.loaded else {}, | |
| } | |
| e2e = analyzer._e2e # noqa: SLF001 | |
| return { | |
| "status": "ok", | |
| "product": "DHVANI", | |
| "mode": "hf-space", | |
| "version": VERSION, | |
| "sovereign": bool(e2e.enabled and e2e.loaded), | |
| "mixed_media_aware": VERSION >= "2.0.1", | |
| "ensemble": analyzer.models_loaded or "loading", | |
| "e2e_model": e2e.model_id if e2e.loaded else None, | |
| "custom_model": custom_info, | |
| } | |
| async def analyze(request: Request, audio: UploadFile = File(...)): | |
| client = request.client.host if request.client else "unknown" | |
| if rate_limited(client): | |
| raise HTTPException(status_code=429, detail="Rate limit exceeded. Try again in a minute.") | |
| if not audio.filename: | |
| raise HTTPException(status_code=400, detail="Empty filename.") | |
| suffix = Path(audio.filename).suffix.lower() | |
| if suffix and suffix not in ALLOWED: | |
| raise HTTPException(status_code=400, detail=f"Unsupported file type: {suffix}") | |
| data = await audio.read() | |
| if not data: | |
| raise HTTPException(status_code=400, detail="No audio file provided.") | |
| if len(data) > MAX_BYTES: | |
| raise HTTPException(status_code=400, detail="File exceeds 20 MB limit.") | |
| saved_path = Path(tempfile.gettempdir()) / f"{uuid.uuid4().hex}{suffix or '.wav'}" | |
| saved_path.write_bytes(data) | |
| try: | |
| result = analyzer.analyze_file(saved_path) | |
| payload = result.to_api_dict() | |
| payload["backend_version"] = VERSION | |
| return payload | |
| except ValueError as exc: | |
| raise HTTPException(status_code=400, detail=str(exc)) from exc | |
| except Exception as exc: | |
| logger.exception("Analysis failed") | |
| raise HTTPException(status_code=500, detail=f"Analysis failed: {exc}") from exc | |
| finally: | |
| if saved_path.exists(): | |
| saved_path.unlink() |