Mercurial
view dictation/transcriber.py @ 280:49e9e591c9bb
Add persistent dictation, prewarmed WebRTC speech input, Copilot SDK routing, animated conversation lifecycle controls, parking, and architecture coverage.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Tue, 18 Aug 2026 19:14:53 -0700 |
| parents | 78699f810817 |
| children |
line wrap: on
line source
from __future__ import annotations import asyncio from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass from pathlib import Path from typing import Protocol import numpy as np @dataclass(frozen=True) class Transcript: text: str language: str probability: float class Transcriber(Protocol): async def warmup(self) -> None: ... async def transcribe( self, samples: np.ndarray, *, final: bool, ) -> Transcript: ... class FasterWhisperTranscriber: def __init__(self, model_dir: Path, compute_type: str) -> None: self._model_dir = model_dir self._compute_type = compute_type self._executor = ThreadPoolExecutor( max_workers=1, thread_name_prefix="dictation", ) self._model = None self._load_lock = asyncio.Lock() async def warmup(self) -> None: if self._model is not None: return async with self._load_lock: if self._model is not None: return loop = asyncio.get_running_loop() self._model = await loop.run_in_executor( self._executor, self._load_and_warmup, ) def _load_and_warmup(self): from faster_whisper import WhisperModel if not (self._model_dir / "model.bin").is_file(): raise FileNotFoundError( f"Model not found at {self._model_dir}. " "Run: bazel run //dictation:download_model" ) model = WhisperModel( str(self._model_dir), device="cuda", compute_type=self._compute_type, local_files_only=True, ) # Loading weights does not initialize all CUDA kernels. Execute and # consume one short silent inference now so the user's first utterance # does not pay the one-time GPU setup cost. segments, _ = model.transcribe( np.zeros(8000, dtype=np.float32), beam_size=1, best_of=1, condition_on_previous_text=False, vad_filter=False, without_timestamps=True, ) list(segments) return model async def transcribe( self, samples: np.ndarray, *, final: bool, ) -> Transcript: await self.warmup() loop = asyncio.get_running_loop() return await loop.run_in_executor( self._executor, self._transcribe_sync, np.asarray(samples, dtype=np.float32), final, ) def _transcribe_sync( self, samples: np.ndarray, final: bool, ) -> Transcript: segments, info = self._model.transcribe( samples, beam_size=1, best_of=1, condition_on_previous_text=False, vad_filter=False, without_timestamps=True, ) text = "".join(segment.text for segment in segments).strip() return Transcript( text=text, language=info.language or "", probability=float(info.language_probability or 0.0), ) def close(self) -> None: self._executor.shutdown(wait=False, cancel_futures=True)