first commit
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
"""Провайдер STT на faster-whisper (CTranslate2).
|
||||
|
||||
Модель автоматически скачивается с HuggingFace при первом запуске
|
||||
и кэшируется в ~/.cache/huggingface (small ≈ 460 МБ).
|
||||
|
||||
Если скачивание из HuggingFace недоступно/медленное, можно указать зеркало:
|
||||
export HF_ENDPOINT=https://hf-mirror.com
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
from faster_whisper import WhisperModel
|
||||
|
||||
from .base import SAMPLE_RATE, AudioInput, SttProvider, TranscriptResult
|
||||
|
||||
|
||||
class FasterWhisperProvider(SttProvider):
|
||||
name = "faster-whisper"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_size: str = "small", # tiny | base | small | medium | large-v3
|
||||
device: str = "cpu", # cpu | cuda
|
||||
compute_type: str = "int8", # int8 на CPU — быстро и мало памяти
|
||||
language: str = "ru", # фиксируем язык: детекция не нужна
|
||||
beam_size: int = 1, # 1 = жадный поиск, заметно быстрее
|
||||
vad_filter: bool = True, # Silero-VAD внутри: отрезает тишину
|
||||
cpu_threads: int = 0, # 0 = по умолчанию движка
|
||||
) -> None:
|
||||
self._language = language
|
||||
self._beam_size = beam_size
|
||||
self._vad_filter = vad_filter
|
||||
|
||||
t0 = time.perf_counter()
|
||||
self._model = WhisperModel(
|
||||
model_size,
|
||||
device=device,
|
||||
compute_type=compute_type,
|
||||
cpu_threads=cpu_threads,
|
||||
)
|
||||
# Время загрузки (и первого скачивания) модели — отдельно от распознавания.
|
||||
self.load_sec = time.perf_counter() - t0
|
||||
|
||||
def transcribe(self, audio: AudioInput, sample_rate: int = SAMPLE_RATE) -> TranscriptResult:
|
||||
duration: Optional[float] = None
|
||||
|
||||
if isinstance(audio, np.ndarray):
|
||||
samples = np.asarray(audio, dtype=np.float32)
|
||||
if samples.ndim > 1: # (frames, channels) → mono
|
||||
samples = samples.mean(axis=1)
|
||||
duration = len(samples) / sample_rate
|
||||
source: AudioInput = samples
|
||||
else:
|
||||
path = Path(audio)
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError(f"Аудио-файл не найден: {path}")
|
||||
source = str(path)
|
||||
|
||||
t0 = time.perf_counter()
|
||||
segments, info = self._model.transcribe(
|
||||
source,
|
||||
language=self._language,
|
||||
beam_size=self._beam_size,
|
||||
vad_filter=self._vad_filter,
|
||||
)
|
||||
text = "".join(segment.text for segment in segments)
|
||||
text = " ".join(text.split()) # нормализуем пробелы между сегментами
|
||||
processing = time.perf_counter() - t0
|
||||
|
||||
if duration is None: # для файлов длина известна после transcribe
|
||||
duration = float(getattr(info, "duration", 0.0) or 0.0)
|
||||
|
||||
return TranscriptResult(
|
||||
text=text,
|
||||
duration_sec=duration,
|
||||
processing_sec=processing,
|
||||
language=info.language or self._language,
|
||||
language_probability=float(info.language_probability or 0.0),
|
||||
)
|
||||
Reference in New Issue
Block a user