Mercurial
view dictation/transcriber.py @ 275:78699f810817
Add Qwen3-VL and WebRTC dictation services
Add Bazel targets for the CUDA-backed Qwen3-VL server and a local WebRTC faster-whisper dictation service.
Co-authored-by: Copilot <[email protected]>
Copilot-Session: e3d8cb06-6c95-4ae0-9757-651d3796ab00
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 17 Aug 2026 10:58:47 -0700 |
| parents | |
| children | 49e9e591c9bb |
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, ) def _load(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" ) return WhisperModel( str(self._model_dir), device="cuda", compute_type=self._compute_type, local_files_only=True, ) 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=5 if final else 1, best_of=5 if final else 1, condition_on_previous_text=False, vad_filter=False, ) 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)