"""Базовый интерфейс STT-модуля (речь → текст). Любой распознаватель (faster-whisper, vosk, ...) реализует SttProvider, чтобы остальной проект не зависел от конкретной библиотеки. Замена провайдера = правка одной строки в конфиге. """ from __future__ import annotations from abc import ABC, abstractmethod from dataclasses import dataclass from pathlib import Path from typing import Union import numpy as np # Что умеет принимать transcribe(): путь к файлу ЛИБО готовый массив float32 AudioInput = Union[str, Path, np.ndarray] # Все провайдеры ждут 16 кГц mono float32 ([-1 .. 1]) SAMPLE_RATE = 16000 @dataclass class TranscriptResult: """Результат распознавания одной фразы.""" text: str duration_sec: float # длина аудио в секундах processing_sec: float # сколько времени считали language: str = "ru" language_probability: float = 0.0 @property def realtime_factor(self) -> float: """processing / duration. Значение < 1.0 — быстрее реального времени.""" if self.duration_sec <= 0: return float("inf") return self.processing_sec / self.duration_sec def __str__(self) -> str: return ( f"[{self.duration_sec:.1f} c аудио → {self.processing_sec:.2f} c, " f"RTF {self.realtime_factor:.2f}] {self.text!r}" ) class SttProvider(ABC): """Интерфейс провайдера распознавания речи.""" name: str = "base" @abstractmethod def transcribe(self, audio: AudioInput, sample_rate: int = SAMPLE_RATE) -> TranscriptResult: """Распознать речь. audio — путь к файлу (wav/mp3/flac, декодирование внутри) ИЛИ numpy-массив float32 mono с частотой sample_rate. """ def close(self) -> None: """Освободить ресурсы (по умолчанию — ничего)."""