"""OpenAI Whisper speech-to-text service.""" import logging import tempfile import time _logger = logging.getLogger(__name__) try: import whisper as _whisper_mod except ImportError: _whisper_mod = None try: import openai as _openai_mod except ImportError: _openai_mod = None class WhisperService: """Speech-to-text via local Whisper model or OpenAI Whisper API.""" def __init__(self, env): self.env = env self._get_param = env["ir.config_parameter"].sudo().get_param self._local_model = None api_key = self._get_param("encoach_ai.openai_api_key", "") if not api_key: import os api_key = os.environ.get("OPENAI_API_KEY", "") self._api_key = api_key def _get_local_model(self): if not _whisper_mod: return None if self._local_model is None: self._local_model = _whisper_mod.load_model("base") return self._local_model def _log(self, action, latency, status="success", error=None): try: self.env["encoach.ai.log"].sudo().create({ "service": "whisper", "action": action, "latency_ms": latency, "status": status, "error_message": error, }) except Exception: pass def transcribe(self, audio_data, *, language="en", use_api=False): """Transcribe audio bytes to text. Args: audio_data: Raw audio bytes (wav, mp3, webm, etc.) language: Language code use_api: If True, use OpenAI Whisper API instead of local model Returns: dict with 'text', 'language', 'segments' keys """ t0 = time.time() if use_api and self._api_key and _openai_mod: return self._transcribe_api(audio_data, language, t0) model = self._get_local_model() if model: return self._transcribe_local(model, audio_data, language, t0) if self._api_key and _openai_mod: return self._transcribe_api(audio_data, language, t0) raise RuntimeError("Whisper not available — install whisper package or set OpenAI API key") def _transcribe_local(self, model, audio_data, language, t0): with tempfile.NamedTemporaryFile(suffix=".webm", delete=True) as tmp: tmp.write(audio_data) tmp.flush() result = model.transcribe(tmp.name, language=language) latency = int((time.time() - t0) * 1000) self._log("transcribe_local", latency) return { "text": result["text"].strip(), "language": result.get("language", language), "segments": [ {"start": s["start"], "end": s["end"], "text": s["text"]} for s in result.get("segments", []) ], } def _transcribe_api(self, audio_data, language, t0): client = _openai_mod.OpenAI(api_key=self._api_key) with tempfile.NamedTemporaryFile(suffix=".webm", delete=True) as tmp: tmp.write(audio_data) tmp.flush() tmp.seek(0) result = client.audio.transcriptions.create( model="whisper-1", file=tmp, language=language, response_format="verbose_json", ) latency = int((time.time() - t0) * 1000) self._log("transcribe_api", latency) return { "text": result.text.strip() if hasattr(result, "text") else str(result), "language": language, "segments": getattr(result, "segments", []), }