feat(transcriber): реализована обёртка над faster-whisper (шаг 3)
- Зачем: - необходим модуль транскрипции с CUDA fallback для основного flow приложения. - Что: - добавлена зависимость faster-whisper>=1.2.1 в pyproject.toml. - реализована функция transcribe() с fallback CUDA→CPU на всех этапах (загрузка модели, вызов transcribe, итерация сегментов). - исправлен IndexError в get_gpu_name() при пустом stdout nvidia-smi. - добавлено 6 тестов в test_transcriber.py и 1 тест в test_utils.py (16 тестов зелёные). - Проверка: - uv run pytest -v (16 passed). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import warnings
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from faster_whisper import WhisperModel
|
||||
|
||||
|
||||
@dataclass
|
||||
class Segment:
|
||||
@@ -27,4 +30,59 @@ def transcribe(
|
||||
language: str | None = None,
|
||||
on_segment: Callable[[Segment], None] | None = None,
|
||||
) -> TranscribeResult:
|
||||
raise NotImplementedError
|
||||
actual_device = device
|
||||
lang_arg = language if language and language != "auto" else None
|
||||
|
||||
try:
|
||||
model = WhisperModel(model_name, device=device, compute_type=compute_type)
|
||||
except (RuntimeError, ValueError) as exc:
|
||||
if device != "cpu" and _is_cuda_error(exc):
|
||||
warnings.warn(
|
||||
f"Не удалось загрузить модель на {device}: {exc}. "
|
||||
"Переключение на CPU.",
|
||||
stacklevel=2,
|
||||
)
|
||||
actual_device = "cpu"
|
||||
model = WhisperModel(model_name, device="cpu", compute_type=compute_type)
|
||||
else:
|
||||
raise
|
||||
|
||||
try:
|
||||
segments, info = _run_transcription(model, file_path, lang_arg, on_segment)
|
||||
except (RuntimeError, ValueError) as exc:
|
||||
if actual_device != "cpu" and _is_cuda_error(exc):
|
||||
warnings.warn(
|
||||
f"CUDA ошибка при транскрипции: {exc}. "
|
||||
"Переключение на CPU и повтор.",
|
||||
stacklevel=2,
|
||||
)
|
||||
actual_device = "cpu"
|
||||
model = WhisperModel(model_name, device="cpu", compute_type=compute_type)
|
||||
segments, info = _run_transcription(model, file_path, lang_arg, on_segment)
|
||||
else:
|
||||
raise
|
||||
|
||||
return TranscribeResult(
|
||||
segments=segments,
|
||||
language=info.language,
|
||||
language_probability=info.language_probability,
|
||||
duration=info.duration,
|
||||
device_used=actual_device,
|
||||
)
|
||||
|
||||
|
||||
def _run_transcription(model, file_path, lang_arg, on_segment):
|
||||
"""Run model.transcribe and iterate segments. Returns (segments, info)."""
|
||||
segment_generator, info = model.transcribe(str(file_path), language=lang_arg)
|
||||
segments: list[Segment] = []
|
||||
for raw_seg in segment_generator:
|
||||
seg = Segment(start=raw_seg.start, end=raw_seg.end, text=raw_seg.text)
|
||||
if on_segment is not None:
|
||||
on_segment(seg)
|
||||
segments.append(seg)
|
||||
return segments, info
|
||||
|
||||
|
||||
def _is_cuda_error(exc: BaseException) -> bool:
|
||||
msg = str(exc).lower()
|
||||
return "cuda" in msg or "out of memory" in msg
|
||||
|
||||
@@ -39,8 +39,10 @@ def get_gpu_name() -> str | None:
|
||||
check=False,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
name = result.stdout.strip().splitlines()[0].strip()
|
||||
return name if name else None
|
||||
lines = result.stdout.strip().splitlines()
|
||||
if lines:
|
||||
name = lines[0].strip()
|
||||
return name if name else None
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user