import base64 import logging import os import threading import httpx MODAL_ENDPOINT = os.environ.get("MODAL_ENDPOINT_WHISPER", "") MODAL_AUTH_TOKEN = os.environ.get("MODAL_AUTH_TOKEN", "") log = logging.getLogger(__name__) # Minimal 44-byte WAV header + 1 sample of silence — enough to trigger container spin-up _MINIMAL_WAV = ( b"RIFF$\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01\x00\x01\x00" b"\x44\xac\x00\x00\x88\x01\x00\x00\x02\x00\x10\x00data\x00\x00\x00\x00" ) _MINIMAL_B64 = base64.b64encode(_MINIMAL_WAV).decode() def warmup(): """Fire-and-forget request to wake the Whisper Modal container. Called once on page load from reset_session(). The container auto-scales down after scaledown_window (90s). """ if not MODAL_ENDPOINT: return def _ping(): try: resp = httpx.post( MODAL_ENDPOINT, json={"audio_base64": _MINIMAL_B64, "token": MODAL_AUTH_TOKEN}, timeout=60, ) log.info("Modal warmup: status=%d", resp.status_code) except Exception as e: log.warning("Modal warmup failed (non-critical): %s", e) threading.Thread(target=_ping, daemon=True).start() def transcribe(audio_path: str) -> str | None: if not MODAL_ENDPOINT: raise RuntimeError("MODAL_ENDPOINT_WHISPER not set") with open(audio_path, "rb") as f: b64 = base64.b64encode(f.read()).decode() resp = httpx.post( MODAL_ENDPOINT, json={"audio_base64": b64, "token": MODAL_AUTH_TOKEN}, timeout=30, ) resp.raise_for_status() return resp.json().get("text")