view dictation/transcriber.py @ 279:b3b547563ec7

Add Google connector service and agent wiki Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code. Co-authored-by: Copilot <[email protected]> Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:22:36 -0700
parents 78699f810817
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)