docs(readme): редизайн README и удаление зависимости от ffmpeg
- Зачем: - README перегружен деталями GPU/бенчмарков, нет Quick Start, macOS/Windows — второй класс. - системный ffmpeg не нужен — PyAV (зависимость faster-whisper) включает FFmpeg в wheels. - Что: - переписан README: Quick Start в первых строках, установка без клонирования через uv tool install, паритет платформ, progressive disclosure через <details>. - удалена функция check_ffmpeg() из utils.py, убраны импорт и вызовы из cli.py. - убраны моки check_ffmpeg и тест ffmpeg-ошибки из test_cli.py. - GPU-контент вынесен в docs/gpu.md, добавлен CONTRIBUTING.md. - Проверка: - uv run pytest — 97 passed, 1 skipped. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,45 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
## Быстрый старт для разработчика
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/dementev-dev/local-transcriber
|
||||||
|
cd local-transcriber
|
||||||
|
uv sync
|
||||||
|
```
|
||||||
|
|
||||||
|
## Запуск
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run transcribe meeting.mp4 # CLI
|
||||||
|
uv run pytest # тесты
|
||||||
|
uv run pytest -v # подробный вывод тестов
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
src/local_transcriber/
|
||||||
|
├── cli.py # Точка входа CLI (typer)
|
||||||
|
├── config.py # Загрузка .transcriber.toml, device-aware дефолты
|
||||||
|
├── formatter.py # Форматирование результата в markdown
|
||||||
|
├── transcriber.py # Обёртка над faster-whisper (загрузка модели, транскрипция)
|
||||||
|
└── utils.py # Утилиты: валидация файлов, детект устройства, глобы
|
||||||
|
tests/
|
||||||
|
├── test_cli.py # Тесты CLI (typer runner + моки)
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Соглашения
|
||||||
|
|
||||||
|
- Тесты: `uv run pytest` должен проходить перед PR
|
||||||
|
- Стиль: стандартный Python (ruff-совместимый)
|
||||||
|
- Коммиты: [Conventional Commits](https://www.conventionalcommits.org/)
|
||||||
|
- Язык кода: английский (имена, комментарии); UI-строки для пользователя — русский
|
||||||
|
|
||||||
|
## Как сделать PR
|
||||||
|
|
||||||
|
1. Форкните репозиторий
|
||||||
|
2. Создайте ветку: `git checkout -b feat/my-feature`
|
||||||
|
3. Убедитесь, что тесты проходят: `uv run pytest`
|
||||||
|
4. Откройте Pull Request с описанием изменений
|
||||||
@@ -1,44 +1,54 @@
|
|||||||
# local-transcriber
|
# local-transcriber
|
||||||
|
|
||||||
Локальный CLI для транскрипции аудио и видео через [faster-whisper](https://github.com/SYSTRAN/faster-whisper).
|
Локальная транскрипция аудио и видео в markdown — без облака, без API-ключей.
|
||||||
Принимает файл, распознаёт речь на машине (без внешних API) и создаёт markdown с таймкодами.
|
|
||||||
Результат удобен для последующей обработки ИИ — суммаризация, action items и т.д.
|
|
||||||
Работает с GPU (NVIDIA CUDA, быстро) и CPU (медленнее).
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
transcribe meeting.mp4
|
transcribe meeting.mp4
|
||||||
# → meeting-transcript.md
|
# → meeting-transcript.md
|
||||||
```
|
```
|
||||||
|
|
||||||
## Требования
|
- **Полностью локально** — данные не покидают машину
|
||||||
|
- **Авто-GPU** — автоматически использует NVIDIA CUDA, если доступен
|
||||||
- Python ≥ 3.10
|
- **Батч-режим** — обработка нескольких файлов за один вызов
|
||||||
- [uv](https://docs.astral.sh/uv/) — менеджер пакетов
|
- **Markdown с таймкодами** — удобен для суммаризации ИИ
|
||||||
- ffmpeg в PATH
|
- **Аудио и видео** — mp3, wav, mp4, mkv и [другие форматы](#поддерживаемые-форматы)
|
||||||
- (Опционально) NVIDIA GPU + установленный драйвер (проверка: `nvidia-smi`)
|
|
||||||
|
|
||||||
## Установка
|
## Установка
|
||||||
|
|
||||||
```bash
|
**1. Установить [uv](https://docs.astral.sh/uv/getting-started/installation/)** (если ещё нет):
|
||||||
git clone <repo>
|
|
||||||
cd local-transcriber
|
|
||||||
uv tool install .
|
|
||||||
```
|
|
||||||
|
|
||||||
После этого команда `transcribe` доступна глобально в PATH.
|
|
||||||
|
|
||||||
Обновление после `git pull`:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv tool install --force .
|
curl -LsSf https://astral.sh/uv/install.sh | sh # Linux / macOS
|
||||||
|
```
|
||||||
|
```powershell
|
||||||
|
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" # Windows
|
||||||
```
|
```
|
||||||
|
|
||||||
Модели скачиваются автоматически при первом запуске (~1.5 GB для medium, ~3 GB для large-v3),
|
**2. Установить transcriber:**
|
||||||
нужен доступ в интернет (Hugging Face Hub).
|
|
||||||
|
|
||||||
> **Если `transcribe: command not found`** — убедитесь, что директория
|
```bash
|
||||||
> инструментов uv добавлена в PATH. Выполните `uv tool dir --bin`
|
uv tool install git+https://github.com/dementev-dev/local-transcriber
|
||||||
> чтобы узнать путь, и добавьте его в PATH вашего shell.
|
```
|
||||||
|
|
||||||
|
**3. Готово:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
transcribe meeting.mp4
|
||||||
|
```
|
||||||
|
Модели скачиваются автоматически при первом запуске (~1.5 GB для medium), нужен доступ в интернет.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><code>transcribe: command not found</code></summary>
|
||||||
|
|
||||||
|
Выполните `uv tool update-shell` — это добавит нужный путь в PATH автоматически.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
**Обновление:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv tool install --force git+https://github.com/dementev-dev/local-transcriber
|
||||||
|
```
|
||||||
|
|
||||||
## Использование
|
## Использование
|
||||||
|
|
||||||
@@ -73,34 +83,9 @@ transcribe *.mp4 --force
|
|||||||
|
|
||||||
- Файлы с существующим транскриптом (`*-transcript.md`) автоматически пропускаются
|
- Файлы с существующим транскриптом (`*-transcript.md`) автоматически пропускаются
|
||||||
- `--force` / `-f` — перезаписать существующие транскрипты
|
- `--force` / `-f` — перезаписать существующие транскрипты
|
||||||
- В конце выводится итоговая статистика: обработано, пропущено, ошибок
|
|
||||||
- При ошибке в одном файле остальные продолжают обрабатываться
|
- При ошибке в одном файле остальные продолжают обрабатываться
|
||||||
- `--output` несовместим с несколькими файлами
|
- `--output` несовместим с несколькими файлами
|
||||||
|
|
||||||
### Конфигурационный файл
|
|
||||||
|
|
||||||
Дефолтные параметры можно задать в `.transcriber.toml`:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
language = "ru"
|
|
||||||
model = "large-v3"
|
|
||||||
compute_type = "float16"
|
|
||||||
```
|
|
||||||
|
|
||||||
Порядок поиска:
|
|
||||||
1. `.transcriber.toml` в текущей директории (проектный конфиг)
|
|
||||||
2. `~/.config/transcriber/config.toml` (глобальный конфиг пользователя)
|
|
||||||
|
|
||||||
Приоритет: **CLI-аргумент > конфиг > device-aware дефолт > встроенный дефолт**.
|
|
||||||
|
|
||||||
Дефолты зависят от устройства (если не заданы явно):
|
|
||||||
|
|
||||||
| Параметр | GPU (CUDA) | CPU |
|
|
||||||
|----------|-----------|-----|
|
|
||||||
| model | medium | medium |
|
|
||||||
| compute_type | float16 | float32 |
|
|
||||||
| language | ru | ru |
|
|
||||||
|
|
||||||
### Опции CLI
|
### Опции CLI
|
||||||
|
|
||||||
| Опция | Сокращение | По умолчанию | Описание |
|
| Опция | Сокращение | По умолчанию | Описание |
|
||||||
@@ -113,7 +98,76 @@ compute_type = "float16"
|
|||||||
| `--force` | `-f` | — | Перезаписать существующие транскрипты |
|
| `--force` | `-f` | — | Перезаписать существующие транскрипты |
|
||||||
| `--verbose` | `-v` | — | Подробный вывод |
|
| `--verbose` | `-v` | — | Подробный вывод |
|
||||||
|
|
||||||
## Модели
|
## Платформы
|
||||||
|
|
||||||
|
| | Linux / WSL2 | macOS | Windows |
|
||||||
|
|---|---|---|---|
|
||||||
|
| CPU | ✅ | ✅ | ✅ |
|
||||||
|
| GPU (NVIDIA) | ✅ авто | — | ✅ (нужен CUDA toolkit) |
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Linux / WSL2</summary>
|
||||||
|
|
||||||
|
- GPU работает из коробки — cuBLAS ставится автоматически как зависимость
|
||||||
|
- Нужен только драйвер NVIDIA (проверка: `nvidia-smi`)
|
||||||
|
- На ARM (aarch64) cuBLAS через pip недоступен — нужен системный CUDA toolkit
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>macOS</summary>
|
||||||
|
|
||||||
|
- Работает на CPU (Intel и Apple Silicon)
|
||||||
|
- GPU (CUDA) недоступен — NVIDIA не поддерживает macOS
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Windows</summary>
|
||||||
|
|
||||||
|
- CPU работает из коробки
|
||||||
|
- Для GPU нужен CUDA toolkit:
|
||||||
|
```
|
||||||
|
winget install -e --id Nvidia.CUDA
|
||||||
|
```
|
||||||
|
> `winget install` требует запуска от имени администратора (elevated terminal).
|
||||||
|
> `uv tool install` работает без админа (ставит в пользовательскую директорию).
|
||||||
|
- После установки CUDA перезапустите терминал
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Конфигурация
|
||||||
|
|
||||||
|
Дефолтные параметры можно задать в `.transcriber.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
model = "large-v3"
|
||||||
|
language = "en"
|
||||||
|
```
|
||||||
|
|
||||||
|
Порядок поиска:
|
||||||
|
1. `.transcriber.toml` в текущей директории
|
||||||
|
2. `~/.config/transcriber/config.toml`
|
||||||
|
|
||||||
|
Приоритет: **CLI-аргумент > конфиг > device-aware дефолт > встроенный дефолт**.
|
||||||
|
|
||||||
|
Дефолты зависят от устройства:
|
||||||
|
|
||||||
|
| Параметр | GPU (CUDA) | CPU |
|
||||||
|
|----------|-----------|-----|
|
||||||
|
| model | medium | medium |
|
||||||
|
| compute_type | float16 | float32 |
|
||||||
|
| language | ru | ru |
|
||||||
|
|
||||||
|
## Модели и GPU
|
||||||
|
|
||||||
|
Рекомендации:
|
||||||
|
- **По умолчанию:** `medium` — хороший баланс скорости и качества
|
||||||
|
- **Макс. качество:** `large-v3` + `--compute-type float16` (GPU)
|
||||||
|
- **Быстрый тест:** `tiny` — для проверки пайплайна
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Таблица моделей</summary>
|
||||||
|
|
||||||
| Модель | Размер на диске | VRAM (int8) | Скорость (GPU) | Качество |
|
| Модель | Размер на диске | VRAM (int8) | Скорость (GPU) | Качество |
|
||||||
|--------|----------------|-------------|----------------|----------|
|
|--------|----------------|-------------|----------------|----------|
|
||||||
@@ -123,7 +177,10 @@ compute_type = "float16"
|
|||||||
| `medium` | ~1.5 GB | ~2.5 GB | ★★ | ★★★★ |
|
| `medium` | ~1.5 GB | ~2.5 GB | ★★ | ★★★★ |
|
||||||
| `large-v3` | ~3 GB | ~2.5 GB | ★ | ★★★★★ |
|
| `large-v3` | ~3 GB | ~2.5 GB | ★ | ★★★★★ |
|
||||||
|
|
||||||
### Типы квантизации (`--compute-type`)
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Типы квантизации (--compute-type)</summary>
|
||||||
|
|
||||||
| Тип | Устройство | VRAM/RAM | Качество | Когда использовать |
|
| Тип | Устройство | VRAM/RAM | Качество | Когда использовать |
|
||||||
|-----|-----------|----------|----------|--------------------|
|
|-----|-----------|----------|----------|--------------------|
|
||||||
@@ -133,77 +190,14 @@ compute_type = "float16"
|
|||||||
| `float32` | CPU | Среднее | Отлично | **По умолчанию для CPU** |
|
| `float32` | CPU | Среднее | Отлично | **По умолчанию для CPU** |
|
||||||
|
|
||||||
**Важно:** `int8` на длинных записях может давать галлюцинации (повтор фраз, потеря контента).
|
**Важно:** `int8` на длинных записях может давать галлюцинации (повтор фраз, потеря контента).
|
||||||
`float16` и `float32` значительно стабильнее на записях >20 минут с техническими терминами.
|
`float16` и `float32` значительно стабильнее на записях >20 минут.
|
||||||
|
|
||||||
## Установка ffmpeg
|
</details>
|
||||||
|
|
||||||
- **Linux / WSL2**: `sudo apt install ffmpeg`
|
Подробнее: бенчмарки, совместимость GPU, результаты тестирования — [docs/gpu.md](docs/gpu.md).
|
||||||
- **macOS**: `brew install ffmpeg`
|
|
||||||
- **Windows**: `winget install ffmpeg`
|
|
||||||
|
|
||||||
## GPU и CUDA
|
<details>
|
||||||
|
<summary>Формат вывода</summary>
|
||||||
Для работы с GPU необходим установленный NVIDIA драйвер
|
|
||||||
(проверка: `nvidia-smi` в терминале). Драйвер предоставляет `libcuda.so.1`,
|
|
||||||
без которого CUDA не работает — его нельзя поставить через pip.
|
|
||||||
|
|
||||||
### По платформам
|
|
||||||
|
|
||||||
- **Linux / WSL2 (x86_64)**: библиотека cuBLAS ставится автоматически
|
|
||||||
(зависимость `nvidia-cublas-cu12` подтягивается при установке).
|
|
||||||
Дополнительных шагов не требуется.
|
|
||||||
На ARM (aarch64) cuBLAS через pip недоступен — нужен системный CUDA toolkit.
|
|
||||||
- **Windows**: нужен системный CUDA toolkit
|
|
||||||
(`choco install cuda` или `winget install -e --id Nvidia.CUDA`)
|
|
||||||
|
|
||||||
### Режимы `--device`
|
|
||||||
|
|
||||||
- `auto` (по умолчанию) — выберет GPU если `nvidia-smi` доступен, иначе CPU
|
|
||||||
- `cuda` — строго GPU, ошибка если недоступен (без silent fallback)
|
|
||||||
- `cpu` — строго CPU
|
|
||||||
|
|
||||||
### Совместимость GPU
|
|
||||||
|
|
||||||
| GPU | VRAM | medium float16 | large-v3 float16 | Рекомендация |
|
|
||||||
|-----|------|---------------|-----------------|--------------|
|
|
||||||
| RTX 3060 | 6 GB | ✅ | ✅ | medium float16 (дефолт) |
|
|
||||||
| RTX 4050 | 6 GB | ✅ | ✅ | medium float16 |
|
|
||||||
| Quadro M3000M | 4 GB | ✅ | ⚠️ tight | medium float16 или int8 |
|
|
||||||
|
|
||||||
### Ожидаемая скорость
|
|
||||||
|
|
||||||
Замеры на RTX 3060 Laptop (6 GB) и Intel CPU (WSL2):
|
|
||||||
|
|
||||||
| Конфигурация | 16 мин файл | 42 мин файл | Отн. скорость |
|
|
||||||
|-------------|-------------|-------------|---------------|
|
|
||||||
| GPU + medium float16 | ~35с | ~133с | ~19x реалтайм |
|
|
||||||
| GPU + large-v3 float16 | ~90с | ~350с | ~7x реалтайм |
|
|
||||||
| CPU + medium float32 | 613с (10 мин) | ~26 мин* | ~1.5x реалтайм |
|
|
||||||
| CPU + large-v3 int8 | 839с (14 мин) | ~37 мин* | ~1:1 реалтайм |
|
|
||||||
|
|
||||||
*Оценка на основе пропорции.
|
|
||||||
|
|
||||||
### Результаты тестирования качества
|
|
||||||
|
|
||||||
Тесты проведены на реальных записях рабочих созвонов (русский язык, технические термины:
|
|
||||||
SQL, PostgreSQL, Greenplum, Airflow, ClickHouse, Docker, CDR, GTP, MAP).
|
|
||||||
|
|
||||||
| Конфигурация | Качество (длинная запись, 42 мин) | Проблемы |
|
|
||||||
|---|---|---|
|
|
||||||
| large-v3 int8 GPU | Плохо | Галлюцинации (фразы ×25), потеря контента |
|
|
||||||
| large-v3 float16 GPU | Отлично | — |
|
|
||||||
| medium float16 GPU | Хорошо | Редкие мелкие ляпы в терминах |
|
|
||||||
| medium float32 CPU | Хорошо | Сопоставимо с large-v3 int8, без галлюцинаций |
|
|
||||||
| large-v3 int8 CPU | Хорошо | Без галлюцинаций (на коротких файлах) |
|
|
||||||
|
|
||||||
**Ключевые выводы:**
|
|
||||||
|
|
||||||
1. **Указание языка (`--language ru`) критично** — auto-detect может ошибиться и выдать мусор
|
|
||||||
2. **float16/float32 стабильнее int8** — особенно на записях >20 минут
|
|
||||||
3. **medium + float16 на GPU — лучший баланс** скорости и качества для повседневного использования
|
|
||||||
4. **large-v3 + float16 на GPU** — для максимального качества важных записей
|
|
||||||
|
|
||||||
## Формат выходного файла
|
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# Транскрипт: meeting.mp4
|
# Транскрипт: meeting.mp4
|
||||||
@@ -217,20 +211,29 @@ SQL, PostgreSQL, Greenplum, Airflow, ClickHouse, Docker, CDR, GTP, MAP).
|
|||||||
---
|
---
|
||||||
|
|
||||||
[00:00:00.00 - 00:00:15.40] Добрый день, коллеги. Сегодня мы обсудим результаты
|
[00:00:00.00 - 00:00:15.40] Добрый день, коллеги. Сегодня мы обсудим результаты
|
||||||
квартала. Первый вопрос — по метрикам продукта. Как вы видите на слайде, MAU вырос
|
квартала. Первый вопрос — по метрикам продукта.
|
||||||
на двадцать три процента по сравнению с предыдущим кварталом.
|
|
||||||
|
|
||||||
[00:00:18.10 - 00:00:25.73] Теперь перейдём к финансовым показателям.
|
[00:00:18.10 - 00:00:25.73] Теперь перейдём к финансовым показателям.
|
||||||
|
|
||||||
...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Близкие по времени сегменты автоматически объединяются в абзацы (пауза > 2 сек или длительность > 60 сек разделяет абзацы).
|
Близкие по времени сегменты автоматически объединяются в абзацы (пауза > 2 сек или длительность > 60 сек разделяет абзацы).
|
||||||
Таймкоды: `MM:SS.ss`, для записей длиннее 1 часа — `HH:MM:SS.ss`.
|
Таймкоды: `MM:SS.ss`, для записей длиннее 1 часа — `HH:MM:SS.ss`.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Поддерживаемые форматы
|
## Поддерживаемые форматы
|
||||||
|
|
||||||
- **Аудио**: mp3, wav, flac, ogg, m4a, wma, aac
|
- **Аудио**: mp3, wav, flac, ogg, m4a, wma, aac
|
||||||
- **Видео**: mp4, mkv, avi, mov, webm, ts
|
- **Видео**: mp4, mkv, avi, mov, webm, ts
|
||||||
|
|
||||||
Формат определяется по расширению, декодирование выполняет ffmpeg.
|
## Для разработчиков
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/dementev-dev/local-transcriber
|
||||||
|
cd local-transcriber
|
||||||
|
uv sync
|
||||||
|
uv run transcribe meeting.mp4 # запуск CLI
|
||||||
|
uv run pytest # тесты
|
||||||
|
```
|
||||||
|
|
||||||
|
Подробнее — [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||||
|
|||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
# GPU и CUDA
|
||||||
|
|
||||||
|
## Режимы `--device`
|
||||||
|
|
||||||
|
- `auto` (по умолчанию) — выберет GPU если `nvidia-smi` доступен, иначе CPU
|
||||||
|
- `cuda` — строго GPU, ошибка если недоступен (без silent fallback)
|
||||||
|
- `cpu` — строго CPU
|
||||||
|
|
||||||
|
## Настройка по платформам
|
||||||
|
|
||||||
|
### Linux / WSL2 (x86_64)
|
||||||
|
|
||||||
|
Библиотека cuBLAS ставится автоматически (зависимость `nvidia-cublas-cu12` подтягивается при установке).
|
||||||
|
Дополнительных шагов не требуется.
|
||||||
|
|
||||||
|
> На ARM (aarch64) cuBLAS через pip недоступен — нужен системный CUDA toolkit.
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
Нужен системный CUDA toolkit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
choco install cuda
|
||||||
|
# или
|
||||||
|
winget install -e --id Nvidia.CUDA # требует запуска от имени администратора
|
||||||
|
```
|
||||||
|
|
||||||
|
После установки перезапустите терминал.
|
||||||
|
|
||||||
|
## Совместимость GPU
|
||||||
|
|
||||||
|
| GPU | VRAM | medium float16 | large-v3 float16 | Рекомендация |
|
||||||
|
|-----|------|---------------|-----------------|--------------|
|
||||||
|
| RTX 3060 | 6 GB | ✅ | ✅ | medium float16 (дефолт) |
|
||||||
|
| RTX 4050 | 6 GB | ✅ | ✅ | medium float16 |
|
||||||
|
| Quadro M3000M | 4 GB | ✅ | ⚠️ tight | medium float16 или int8 |
|
||||||
|
|
||||||
|
## Ожидаемая скорость
|
||||||
|
|
||||||
|
Замеры на RTX 3060 Laptop (6 GB) и Intel CPU (WSL2):
|
||||||
|
|
||||||
|
| Конфигурация | 16 мин файл | 42 мин файл | Отн. скорость |
|
||||||
|
|-------------|-------------|-------------|---------------|
|
||||||
|
| GPU + medium float16 | ~35с | ~133с | ~19x реалтайм |
|
||||||
|
| GPU + large-v3 float16 | ~90с | ~350с | ~7x реалтайм |
|
||||||
|
| CPU + medium float32 | 613с (10 мин) | ~26 мин* | ~1.5x реалтайм |
|
||||||
|
| CPU + large-v3 int8 | 839с (14 мин) | ~37 мин* | ~1:1 реалтайм |
|
||||||
|
|
||||||
|
*Оценка на основе пропорции.
|
||||||
|
|
||||||
|
## Результаты тестирования качества
|
||||||
|
|
||||||
|
Тесты проведены на реальных записях рабочих созвонов (русский язык, технические термины:
|
||||||
|
SQL, PostgreSQL, Greenplum, Airflow, ClickHouse, Docker, CDR, GTP, MAP).
|
||||||
|
|
||||||
|
| Конфигурация | Качество (длинная запись, 42 мин) | Проблемы |
|
||||||
|
|---|---|---|
|
||||||
|
| large-v3 int8 GPU | Плохо | Галлюцинации (фразы ×25), потеря контента |
|
||||||
|
| large-v3 float16 GPU | Отлично | — |
|
||||||
|
| medium float16 GPU | Хорошо | Редкие мелкие ляпы в терминах |
|
||||||
|
| medium float32 CPU | Хорошо | Сопоставимо с large-v3 int8, без галлюцинаций |
|
||||||
|
| large-v3 int8 CPU | Хорошо | Без галлюцинаций (на коротких файлах) |
|
||||||
|
|
||||||
|
### Ключевые выводы
|
||||||
|
|
||||||
|
1. **Указание языка (`--language ru`) критично** — auto-detect может ошибиться и выдать мусор
|
||||||
|
2. **float16/float32 стабильнее int8** — особенно на записях >20 минут
|
||||||
|
3. **medium + float16 на GPU — лучший баланс** скорости и качества для повседневного использования
|
||||||
|
4. **large-v3 + float16 на GPU** — для максимального качества важных записей
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### CUDA не обнаружен
|
||||||
|
|
||||||
|
Убедитесь, что `nvidia-smi` возвращает информацию о GPU. Драйвер NVIDIA предоставляет `libcuda.so.1`,
|
||||||
|
без которого CUDA не работает — его нельзя поставить через pip.
|
||||||
|
|
||||||
|
### Windows: ошибка при загрузке модели на GPU
|
||||||
|
|
||||||
|
GPU на Windows требует CUDA toolkit (включает cuBLAS). Установите:
|
||||||
|
```bash
|
||||||
|
choco install cuda
|
||||||
|
# или
|
||||||
|
winget install -e --id Nvidia.CUDA
|
||||||
|
```
|
||||||
|
После установки перезапустите терминал.
|
||||||
|
|
||||||
|
### ARM (aarch64)
|
||||||
|
|
||||||
|
cuBLAS через pip недоступен на ARM — нужен системный CUDA toolkit.
|
||||||
@@ -18,7 +18,6 @@ from .transcriber import (
|
|||||||
)
|
)
|
||||||
from .utils import (
|
from .utils import (
|
||||||
build_output_path,
|
build_output_path,
|
||||||
check_ffmpeg,
|
|
||||||
detect_device,
|
detect_device,
|
||||||
expand_globs,
|
expand_globs,
|
||||||
get_gpu_name,
|
get_gpu_name,
|
||||||
@@ -111,7 +110,6 @@ def _run_single(
|
|||||||
) -> None:
|
) -> None:
|
||||||
start = time.monotonic()
|
start = time.monotonic()
|
||||||
|
|
||||||
check_ffmpeg()
|
|
||||||
validated_file = validate_input_file(file)
|
validated_file = validate_input_file(file)
|
||||||
requested_device = defaults["device"]
|
requested_device = defaults["device"]
|
||||||
resolved_device = detect_device(requested_device)
|
resolved_device = detect_device(requested_device)
|
||||||
@@ -198,8 +196,6 @@ def _run_batch(
|
|||||||
verbose: bool,
|
verbose: bool,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
check_ffmpeg()
|
|
||||||
|
|
||||||
# Phase 1: Prescan
|
# Phase 1: Prescan
|
||||||
to_process: list[Path] = []
|
to_process: list[Path] = []
|
||||||
skipped = 0
|
skipped = 0
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import glob
|
import glob
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
|
||||||
import warnings
|
import warnings
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -11,18 +10,6 @@ SUPPORTED_EXTENSIONS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def check_ffmpeg() -> None:
|
|
||||||
try:
|
|
||||||
subprocess.run(["ffmpeg", "-version"], capture_output=True, check=False)
|
|
||||||
except FileNotFoundError:
|
|
||||||
sys.exit(
|
|
||||||
"ffmpeg не найден в PATH. Установите ffmpeg:\n"
|
|
||||||
" Linux: apt install ffmpeg\n"
|
|
||||||
" Windows: winget install ffmpeg\n"
|
|
||||||
" macOS: brew install ffmpeg"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def detect_device(requested: str = "auto") -> str:
|
def detect_device(requested: str = "auto") -> str:
|
||||||
if requested != "auto":
|
if requested != "auto":
|
||||||
return requested
|
return requested
|
||||||
|
|||||||
+2
-43
@@ -40,7 +40,6 @@ def _single_patches(result=None, tmp_file=None, actual_device="cpu"):
|
|||||||
tfr = TranscribeFileResult(result=result, model=model, actual_device=actual_device)
|
tfr = TranscribeFileResult(result=result, model=model, actual_device=actual_device)
|
||||||
return [
|
return [
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", return_value=tmp_file),
|
patch("local_transcriber.cli.validate_input_file", return_value=tmp_file),
|
||||||
patch("local_transcriber.cli.detect_device", return_value=actual_device),
|
patch("local_transcriber.cli.detect_device", return_value=actual_device),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -55,7 +54,7 @@ def test_cli_happy_path_exit_code_zero(tmp_path):
|
|||||||
audio.write_bytes(b"fake")
|
audio.write_bytes(b"fake")
|
||||||
|
|
||||||
patches = _single_patches(tmp_file=audio)
|
patches = _single_patches(tmp_file=audio)
|
||||||
with patches[0], patches[1], patches[2], patches[3], patches[4], patches[5], patches[6], patches[7]:
|
with patches[0], patches[1], patches[2], patches[3], patches[4], patches[5], patches[6]:
|
||||||
out = runner.invoke(app, [str(audio)])
|
out = runner.invoke(app, [str(audio)])
|
||||||
|
|
||||||
assert out.exit_code == 0
|
assert out.exit_code == 0
|
||||||
@@ -71,7 +70,6 @@ def test_cli_default_options_passed_to_transcribe(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -98,7 +96,6 @@ def test_cli_custom_options(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cuda"),
|
patch("local_transcriber.cli.detect_device", return_value="cuda"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/small"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/small"),
|
||||||
@@ -131,7 +128,6 @@ def test_cli_verbose_passes_on_segment_callback(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -152,7 +148,7 @@ def test_cli_empty_speech_warning(tmp_path):
|
|||||||
result = _make_result(segments=[])
|
result = _make_result(segments=[])
|
||||||
|
|
||||||
patches = _single_patches(result=result, tmp_file=audio)
|
patches = _single_patches(result=result, tmp_file=audio)
|
||||||
with patches[0], patches[1], patches[2], patches[3], patches[4], patches[5], patches[6], patches[7]:
|
with patches[0], patches[1], patches[2], patches[3], patches[4], patches[5], patches[6]:
|
||||||
out = runner.invoke(app, [str(audio)])
|
out = runner.invoke(app, [str(audio)])
|
||||||
|
|
||||||
assert out.exit_code == 0
|
assert out.exit_code == 0
|
||||||
@@ -170,7 +166,6 @@ def test_cli_default_output_path(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -196,7 +191,6 @@ def test_cli_custom_output_path(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -210,19 +204,6 @@ def test_cli_custom_output_path(tmp_path):
|
|||||||
assert written_path == out_file
|
assert written_path == out_file
|
||||||
|
|
||||||
|
|
||||||
def test_cli_error_exit_code_one(tmp_path):
|
|
||||||
audio = tmp_path / "test.mp3"
|
|
||||||
audio.write_bytes(b"fake")
|
|
||||||
|
|
||||||
with (
|
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
|
||||||
patch("local_transcriber.cli.check_ffmpeg", side_effect=SystemExit(1)),
|
|
||||||
):
|
|
||||||
out = runner.invoke(app, [str(audio)])
|
|
||||||
|
|
||||||
assert out.exit_code == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_cli_passes_status_callback_to_transcribe(tmp_path):
|
def test_cli_passes_status_callback_to_transcribe(tmp_path):
|
||||||
audio = tmp_path / "test.mp3"
|
audio = tmp_path / "test.mp3"
|
||||||
audio.write_bytes(b"fake")
|
audio.write_bytes(b"fake")
|
||||||
@@ -233,7 +214,6 @@ def test_cli_passes_status_callback_to_transcribe(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -258,7 +238,6 @@ def test_cli_resolves_model_before_transcribe(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/large-v3") as mock_ensure,
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/large-v3") as mock_ensure,
|
||||||
@@ -281,7 +260,6 @@ def test_cli_windows_cuda_diagnostic(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cuda"),
|
patch("local_transcriber.cli.detect_device", return_value="cuda"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -305,7 +283,6 @@ def test_cli_linux_cuda_error_no_windows_hint(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cuda"),
|
patch("local_transcriber.cli.detect_device", return_value="cuda"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -330,7 +307,6 @@ def test_cli_device_fallback_warning(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cuda"),
|
patch("local_transcriber.cli.detect_device", return_value="cuda"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -354,7 +330,6 @@ def test_cli_strict_device_passed_to_transcribe(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cuda"),
|
patch("local_transcriber.cli.detect_device", return_value="cuda"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -374,7 +349,6 @@ def test_cli_strict_device_passed_to_transcribe(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -395,7 +369,6 @@ def test_cli_keyboard_interrupt(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -429,7 +402,6 @@ def test_cli_unexpected_error_verbose_traceback(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -451,7 +423,6 @@ def test_cli_unexpected_error_no_verbose_hint(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -481,7 +452,6 @@ def test_cli_batch_two_files(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", side_effect=lambda p: p),
|
patch("local_transcriber.cli.validate_input_file", side_effect=lambda p: p),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -509,7 +479,6 @@ def test_cli_batch_skips_existing(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", side_effect=lambda p: p),
|
patch("local_transcriber.cli.validate_input_file", side_effect=lambda p: p),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -537,7 +506,6 @@ def test_cli_batch_all_skipped_no_model_load(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", side_effect=lambda p: p),
|
patch("local_transcriber.cli.validate_input_file", side_effect=lambda p: p),
|
||||||
patch("local_transcriber.cli.load_model", mock_load_model),
|
patch("local_transcriber.cli.load_model", mock_load_model),
|
||||||
):
|
):
|
||||||
@@ -560,7 +528,6 @@ def test_cli_batch_force_overwrites(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", side_effect=lambda p: p),
|
patch("local_transcriber.cli.validate_input_file", side_effect=lambda p: p),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -595,7 +562,6 @@ def test_cli_batch_per_file_error(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", side_effect=lambda p: p),
|
patch("local_transcriber.cli.validate_input_file", side_effect=lambda p: p),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -627,7 +593,6 @@ def test_cli_batch_invalid_in_prescan(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", side_effect=validate_side_effect),
|
patch("local_transcriber.cli.validate_input_file", side_effect=validate_side_effect),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -674,7 +639,6 @@ def test_cli_config_applied(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={"model": "tiny"}),
|
patch("local_transcriber.cli.load_config", return_value={"model": "tiny"}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/tiny") as mock_ensure,
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/tiny") as mock_ensure,
|
||||||
@@ -696,7 +660,6 @@ def test_cli_cli_overrides_config(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={"model": "tiny"}),
|
patch("local_transcriber.cli.load_config", return_value={"model": "tiny"}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
patch("local_transcriber.cli.validate_input_file", return_value=audio),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/small") as mock_ensure,
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/small") as mock_ensure,
|
||||||
@@ -722,7 +685,6 @@ def test_cli_batch_fallback_warning(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", side_effect=lambda p: p),
|
patch("local_transcriber.cli.validate_input_file", side_effect=lambda p: p),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cuda"),
|
patch("local_transcriber.cli.detect_device", return_value="cuda"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -750,7 +712,6 @@ def test_cli_batch_empty_speech_warning(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", side_effect=lambda p: p),
|
patch("local_transcriber.cli.validate_input_file", side_effect=lambda p: p),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -781,7 +742,6 @@ def test_cli_batch_midstream_fallback_warning(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", side_effect=lambda p: p),
|
patch("local_transcriber.cli.validate_input_file", side_effect=lambda p: p),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cuda"),
|
patch("local_transcriber.cli.detect_device", return_value="cuda"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
@@ -808,7 +768,6 @@ def test_cli_batch_model_loaded_once(tmp_path):
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("local_transcriber.cli.load_config", return_value={}),
|
patch("local_transcriber.cli.load_config", return_value={}),
|
||||||
patch("local_transcriber.cli.check_ffmpeg"),
|
|
||||||
patch("local_transcriber.cli.validate_input_file", side_effect=lambda p: p),
|
patch("local_transcriber.cli.validate_input_file", side_effect=lambda p: p),
|
||||||
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
patch("local_transcriber.cli.detect_device", return_value="cpu"),
|
||||||
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
patch("local_transcriber.cli.ensure_model_available", return_value="/models/medium"),
|
||||||
|
|||||||
Reference in New Issue
Block a user