Первоначальная версия VidConf

This commit is contained in:
2026-07-23 01:04:01 +03:00
commit 896455381a
335 changed files with 61527 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
"""Плагины `Transcriber` на основе faster-whisper: CPU (`min`) и GPU (`medium`/`max`).
Оба плагина используют встроенный в faster-whisper Silero VAD (`vad_filter=True`)
и дополнительно отбрасывают сегменты короче `MIN_SEGMENT_DURATION_S` —
типичные галлюцинации Whisper на тишине/шуме (ТЗ §1.4). Общая логика
(ленивая загрузка модели-синглтона процесса, вызов `transcribe` с VAD,
фильтрация коротких сегментов) вынесена в `_FasterWhisperBase`; CPU/GPU-варианты
отличаются только параметрами устройства/квантизации (ADR-004,
`docs/architecture/adr/004-ai-tier-matrix.md`).
"""
from typing import TYPE_CHECKING, ClassVar
from core.plugins.factory import register_transcriber
from core.plugins.transcriber import Segment, Transcriber
if TYPE_CHECKING:
# Импорт только для проверки типов: рантайм-импорт — ленивый, см. `_get_model`,
# чтобы API-процесс, где транскрибация не используется, не тянул тяжёлую
# зависимость (ctranslate2 и т.п.) в память.
from faster_whisper import WhisperModel
MIN_SEGMENT_DURATION_S = 0.3
"""Минимальная длительность сегмента (сек); короче — отбрасывается как
вероятная галлюцинация Whisper на тишине/шуме."""
VAD_MIN_SILENCE_DURATION_MS = 500
"""Порог Silero VAD (мс) для разбиения на речевые куски внутри трека."""
class _FasterWhisperBase(Transcriber):
"""Общая логика плагинов faster-whisper: синглтон модели процесса + VAD-транскрибация.
Модель-синглтон принадлежит конкретному подклассу (`FasterWhisperCPU`,
`FasterWhisperGPU`), а не общему базовому классу: присваивание
`cls._model = ...` в `_get_model` всегда происходит через `type(self)`,
поэтому у каждого подкласса — свой атрибут класса, и CPU/GPU-плагины не
делят один кэшированный инстанс модели, даже если оба сконфигурированы в
одном процессе.
"""
MIN_SEGMENT_S: ClassVar[float] = MIN_SEGMENT_DURATION_S
_model: "ClassVar[WhisperModel | None]" = None
# Задаются наследниками в `__init__` (device — фиксированно классом,
# compute_type — либо фиксированно, либо конструкторская опция).
device: str
compute_type: str
def __init__(
self,
model: str,
language: str = "ru",
download_root: str | None = None,
) -> None:
self.model_name = model
self.language = language
self.download_root = download_root
def _get_model(self) -> "WhisperModel":
"""Лениво создать (или переиспользовать) синглтон `WhisperModel` конкретного подкласса."""
cls = type(self)
if cls._model is None:
from faster_whisper import WhisperModel # ленивый импорт тяжёлой зависимости
cls._model = WhisperModel(
self.model_name,
device=self.device,
compute_type=self.compute_type,
download_root=self.download_root,
)
return cls._model
def transcribe(self, audio_path: str, language: str = "ru") -> list[Segment]:
"""Транскрибировать аудиофайл трека, отбросив короткие сегменты-галлюцинации.
VAD (Silero, встроен в faster-whisper) включён с порогом тишины
`VAD_MIN_SILENCE_DURATION_MS`; дополнительно отбрасываются сегменты
короче `MIN_SEGMENT_DURATION_S`.
"""
model = self._get_model()
raw_segments, _info = model.transcribe(
audio_path,
language=language,
vad_filter=True,
vad_parameters={"min_silence_duration_ms": VAD_MIN_SILENCE_DURATION_MS},
)
return [
Segment(start=segment.start, end=segment.end, text=segment.text)
for segment in raw_segments
if (segment.end - segment.start) >= MIN_SEGMENT_DURATION_S
]
@register_transcriber
class FasterWhisperCPU(_FasterWhisperBase):
"""Транскрибер faster-whisper (CTranslate2) на CPU с int8-квантизацией (уровень `min`).
Модель — синглтон на процесс: создаётся лениво при первом вызове
`transcribe` и переиспользуется всеми последующими вызовами в рамках
одного процесса воркера (процесс запускается
в Celery-очереди `transcription` с `--pool=solo --concurrency=1`, поэтому
гонок за атрибут класса не возникает).
"""
provider: ClassVar[str] = "faster_whisper_cpu"
_model: "ClassVar[WhisperModel | None]" = None
def __init__(
self,
model: str | None = None,
language: str = "ru",
download_root: str | None = None,
) -> None:
super().__init__(model=model or "small", language=language, download_root=download_root)
self.device = "cpu"
self.compute_type = "int8"
@register_transcriber
class FasterWhisperGPU(_FasterWhisperBase):
"""Транскрибер faster-whisper на GPU (CUDA, уровни `medium`/`max`, ADR-004).
`compute_type` — конструкторская опция (дефолт `float16`, как в матрице
ADR-004); для экономии VRAM конфиг уровня может задать `int8_float16`
(options плагина в `TIERS`/`plugins.yaml`).
"""
provider: ClassVar[str] = "faster_whisper_gpu"
_model: "ClassVar[WhisperModel | None]" = None
def __init__(
self,
model: str | None = None,
language: str = "ru",
download_root: str | None = None,
compute_type: str = "float16",
) -> None:
super().__init__(model=model or "medium", language=language, download_root=download_root)
self.device = "cuda"
self.compute_type = compute_type