feat(diarization): добавлено разделение транскрипта по говорящим

Зачем:
- локальным транскриптам нужна структура реплик для конспектов и протоколов.

Что:
- добавлены пословные таймкоды для всех ASR-бэкендов и сведение с Sherpa-ONNX.
- реализованы CLI-флаги, деградация без потери ASR и speaker Markdown.
- добавлены проверяемый кеш моделей, тесты и документация.

Проверка:
- `pytest` — 283 passed, 1 skipped.
- `pyright` — 0 errors.
- Ruff и `git diff --check` — без ошибок.
- выполнены три контрольных прогона на реальных записях.
This commit is contained in:
Dmitriy Dementiev
2026-08-14 18:55:42 +03:00
parent 63ba41d906
commit 726718197f
23 changed files with 2522 additions and 187 deletions
+44
View File
@@ -10,6 +10,7 @@ transcribe meeting.mp4
- **Полностью локально** — данные не покидают машину
- **Авто-ускорение** — NVIDIA CUDA при наличии GPU, иначе ONNX на CPU
- **Батч-режим** — обработка нескольких файлов за один вызов
- **Разделение говорящих** — локальная диаризация по флагу `--diarize`
- **Из проводника Windows** — пункт Transcribe в меню «Отправить» ([установка](#контекстное-меню-проводника-windows))
- **Markdown с таймкодами** — удобен для суммаризации ИИ
- **Аудио и видео** — mp3, wav, mp4, mkv и [другие форматы](#поддерживаемые-форматы)
@@ -135,8 +136,36 @@ transcribe meeting.wav --device onnx --model gigaam-multilingual-large-ctc
# Сохранить в конкретный файл
transcribe interview.m4a --output result.md
# Разделить встречу на реплики говорящих
transcribe meeting.mp4 --diarize
# Если число участников известно заранее
transcribe interview.m4a --speakers 2
```
### Разделение говорящих
`--diarize` добавляет к транскрипту реплики `Speaker 1`, `Speaker 2` и так
далее. `--speakers N` задаёт ожидаемое число участников и автоматически включает
диаризацию; без него число кластеров определяется автоматически.
При первом таком запуске дополнительно скачиваются две ONNX-модели Sherpa-ONNX:
сегментация (~6 МБ) и голосовые эмбеддинги (~27 МБ). Они сохраняются в кеше
Hugging Face и используются повторно. Диаризация выполняется после распознавания
речи и добавляет отдельный проход по записи. На измеренном слабом Intel Core
i7-6820HQ последовательные ASR и диаризация увеличивали полное время примерно в
2,4 раза, но оставались быстрее реального времени; фактическая скорость зависит
от процессора и режима питания ([замеры](docs/benchmarks/2026-08-14-diarization-intel-i7.md)).
Если найдено меньше двух говорящих или диаризация конкретного файла завершилась
ошибкой, текст не теряется: сохраняется обычный транскрипт, в Markdown
записывается причина, а команда завершается с кодом `1`. Если выбранный ASR-путь
не поддерживает пословные таймкоды или диаризатор не удалось инициализировать,
запуск останавливается до первого ASR и не создаёт частичных транскриптов. Малый
кластер только отмечается предупреждением и не удаляется. Слова без однозначного
говорящего попадают в реплику `Speaker ?`.
### Батч-режим
Обработка нескольких файлов за один вызов — модель загружается один раз:
@@ -155,6 +184,8 @@ transcribe *.mp4 --force
- Файлы с существующим транскриптом (`*-transcript.md`) автоматически пропускаются
- `--force` / `-f` — перезаписать существующие транскрипты
- При ошибке в одном файле остальные продолжают обрабатываться
- При ошибке диаризации сохраняется обычный транскрипт, остальные файлы
продолжают обрабатываться; итоговый код батча — `1`
- `--output` несовместим с несколькими файлами
### Контекстное меню проводника (Windows)
@@ -192,6 +223,8 @@ transcribe --uninstall-menu
| `--device` | `-d` | `auto` | Устройство (auto, cpu, cuda, openvino, openvino-gpu, openvino-cpu, onnx) |
| `--compute-type` | — | float16 (CUDA) / int8 (ONNX/OpenVINO) / float32 (CPU) | Тип вычислений |
| `--threads` | `-t` | 0 (авто) | Потоки CPU (рекомендуется = число физ. ядер) |
| `--diarize` | — | — | Разделить текст на реплики говорящих |
| `--speakers` | — | авто | Ожидаемое число говорящих; включает `--diarize` |
| `--force` | `-f` | — | Перезаписать существующие транскрипты |
| `--verbose` | `-v` | — | Подробный вывод |
@@ -404,6 +437,17 @@ device-aware дефолт недоступен для выбранной мод
Если язык определить не удалось, строка выглядит так: `- **Язык**: не определён`.
С `--diarize` при успешном обнаружении нескольких говорящих основная часть
выглядит так:
```markdown
[00:00] Speaker 1: Добрый день, коллеги.
[00:04] Speaker 2: Начнём с результатов квартала.
```
Таймкод реплики показывает начало: `MM:SS`, а после часа — `HH:MM:SS`.
</details>
## Поддерживаемые форматы