535 lines
21 KiB
Markdown
535 lines
21 KiB
Markdown
# WhisperX Meeting Transcription
|
||
|
||
Пайплайн для транскрибации аудиозаписей совещаний с диаризацией (кто говорил) и таймкодами.
|
||
|
||
## Стек
|
||
|
||
- **WhisperX** — ASR + alignment + диаризация (всё-в-одном)
|
||
- **python-docx** — генерация `.docx`
|
||
- **PyYAML** — конфигурация
|
||
- **Native Python RAG engine** — гибридный поиск BM25 (FTS5) + vector (sqlite-vec) + LLM-реранкер
|
||
- **OpenCode / DeepSeek** — LLM для классификации и чат-ответов
|
||
|
||
## Установка
|
||
|
||
### 1. Python зависимости
|
||
|
||
```bash
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
### 2. ffmpeg (обязателен для видео)
|
||
|
||
Программа нуждается в ffmpeg для извлечения аудио из видео файлов.
|
||
|
||
**macOS:**
|
||
```bash
|
||
brew install ffmpeg
|
||
```
|
||
|
||
**Ubuntu/Debian:**
|
||
```bash
|
||
sudo apt-get update && sudo apt-get install ffmpeg
|
||
```
|
||
|
||
**Windows:**
|
||
Скачайте с [ffmpeg.org/download.html](https://ffmpeg.org/download.html) и добавьте в PATH.
|
||
|
||
### 3. HuggingFace токен
|
||
|
||
Нужен для диаризации (см. раздел ниже).
|
||
|
||
## HuggingFace Token (обязателен для диаризации)
|
||
|
||
### Зачем нужен токен?
|
||
|
||
WhisperX для определения спикеров (диаризация) использует модели `pyannote.audio`, которые хранятся на платформе HuggingFace. Эти модели:
|
||
- **НЕ являются публично доступными** без регистрации
|
||
- Требуют принятия пользовательского соглашения (license)
|
||
- Требуют аутентификации через токен при скачивании
|
||
|
||
Без токена диаризация **не будет работать** — вы получите ошибку авторизации.
|
||
|
||
### Как получить токен (пошагово)
|
||
|
||
**Шаг 1: Регистрация**
|
||
1. Перейдите на [huggingface.co](https://huggingface.co)
|
||
2. Нажмите "Sign Up" (регистрация через email или GitHub/Google)
|
||
3. Подтвердите email
|
||
|
||
**Шаг 2: Создание токена**
|
||
1. Войдите в аккаунт
|
||
2. Перейдите в [Settings → Access Tokens](https://huggingface.co/settings/tokens)
|
||
3. Нажмите "New token"
|
||
4. Введите название (например, `transcription`)
|
||
5. Выберите тип: **`Read`** (только чтение — достаточно)
|
||
6. Нажмите "Generate token"
|
||
7. **Скопируйте токен сразу** — он показывается только один раз!
|
||
|
||
**Шаг 3: Принятие соглашений**
|
||
|
||
Нужно принять соглашение для **каждой** из этих моделей (зайдите по ссылкам и нажмите "Access repository", затем согласитесь с условиями):
|
||
|
||
1. [pyannote/speaker-diarization-3.1](https://huggingface.co/pyannote/speaker-diarization-3.1)
|
||
2. [pyannote/segmentation-3.0](https://huggingface.co/pyannote/segmentation-3.0)
|
||
|
||
> **Важно:** Если не принять соглашения, даже с правильным токеном будет ошибка 403 (Forbidden)!
|
||
|
||
**Шаг 4: Установка токена**
|
||
|
||
Вариант A — через переменную окружения (рекомендуется):
|
||
```bash
|
||
export HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxx
|
||
```
|
||
|
||
Вариант B — в `config.yaml` (менее безопасно, токен попадёт в git):
|
||
```yaml
|
||
hf_token: "hf_xxxxxxxxxxxxxxxxxxxxxxxx"
|
||
```
|
||
|
||
Вариант C — в `.env` файл (если добавить `.env` в `.gitignore`):
|
||
```bash
|
||
# .env
|
||
HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxx
|
||
```
|
||
|
||
> **Безопасность:** Токен — это ваш пароль от HuggingFace. Никогда не коммитьте его в публичный репозиторий!
|
||
|
||
### Проверка токена
|
||
|
||
После установки можно проверить:
|
||
```bash
|
||
python -c "import os; print('Token установлен:', bool(os.environ.get('HF_TOKEN')))"
|
||
```
|
||
|
||
## Первый запуск
|
||
|
||
**Важно:** при первом запуске программа скачает модели искусственного интеллекта. Это **нормально** и происходит только один time.
|
||
|
||
### Что скачивается
|
||
|
||
| Компонент | Размер | Назначение |
|
||
|-----------|--------|------------|
|
||
| Whisper `large-v3` | ~3.0 GB | Распознавание речи |
|
||
| Pyannote диаризация | ~0.4 GB | Разделение спикеров |
|
||
| Wav2Vec2 (русский) | ~1.0 GB | Точные таймкоды слов |
|
||
| **Итого** | **~4–5 GB** | **Скачиваются один раз** |
|
||
|
||
Время скачивания зависит от скорости интернета (обычно 10–30 минут).
|
||
Все последующие запуски используют локальные файлы и работают **без интернета**.
|
||
|
||
### Где хранятся модели
|
||
|
||
Модели сохраняются в системный кэш:
|
||
- **Linux/Mac:** `~/.cache/`
|
||
- **Windows:** `%USERPROFILE%\.cache\`
|
||
|
||
## Работа офлайн — всё локально!
|
||
|
||
**Да, все модели работают полностью локально.**
|
||
|
||
Токен HuggingFace нужен **только один раз** — чтобы скачать модели при первом запуске. После этого:
|
||
|
||
- ✅ **Интернет не нужен** — можно отключить Wi-Fi
|
||
- ✅ **Аудио не уходит никуда** — обработка только на вашем устройстве
|
||
- ✅ **Текст не уходит в облако** — результат только у вас
|
||
- ✅ **Подходит для конфиденциальных совещаний**
|
||
|
||
### Что скачивается при первом запуске
|
||
|
||
| Компонент | Размер | Зачем |
|
||
|-----------|--------|-------|
|
||
| Whisper `large-v3` | ~3 GB | Распознавание речи |
|
||
| Pyannote диаризация | ~400 MB | Разделение спикеров |
|
||
| Wav2Vec2 (русский) | ~1 GB | Точные таймкоды слов |
|
||
| **Итого** | **~4–5 GB** | **Скачиваются один раз** |
|
||
|
||
Модели сохраняются в системный кэш (`~/.cache/` на Linux/Mac, `%USERPROFILE%\.cache\` на Windows) и переиспользуются при каждом запуске.
|
||
|
||
## Использование
|
||
|
||
```bash
|
||
python run.py -i meeting.wav -o meeting.docx
|
||
```
|
||
|
||
### Аргументы
|
||
|
||
| Аргумент | Описание |
|
||
|----------|----------|
|
||
| `-i, --input` | Путь к аудиофайлу (обязательный) |
|
||
| `-o, --output` | Путь к выходному файлу (если один формат) |
|
||
| `-p, --profile` | Профиль: `mac_m4`, `gpu_8gb`, `cpu_best` |
|
||
| `-c, --config` | Путь к `config.yaml` |
|
||
| `-d, --device` | Принудительно: `cpu`, `cuda`, `mps` |
|
||
| `-m, --model` | Модель: `tiny`, `base`, `small`, `medium`, `large-v3` |
|
||
| `-l, --language` | Язык: `ru`, `en`, ... |
|
||
| `-f, --format` | Форматы через запятую: `docx,md,txt` |
|
||
|
||
### Примеры
|
||
|
||
**Аудио файлы:**
|
||
```bash
|
||
# Базовый запуск (по умолчанию: docx + md)
|
||
python run.py -i meeting.wav
|
||
|
||
# Быстрый тест на маленькой модели
|
||
python run.py -i meeting.wav -m base
|
||
|
||
# Только один формат
|
||
python run.py -i meeting.wav -f docx
|
||
|
||
# Markdown выход
|
||
python run.py -i meeting.wav -f md -o meeting.md
|
||
```
|
||
|
||
**Видео файлы (автоматически извлекается аудио):**
|
||
```bash
|
||
# Из видео совещания
|
||
python run.py -i recording.mp4
|
||
|
||
# Из Zoom записи
|
||
python run.py -i zoom_meeting.mp4 -o protocol.docx
|
||
|
||
# Из Teams записи (MKV формат)
|
||
python run.py -i teams_recording.mkv
|
||
```
|
||
|
||
**Вывод в несколько форматов одновременно:**
|
||
```bash
|
||
# docx + md (по умолчанию из конфига)
|
||
python run.py -i meeting.wav
|
||
|
||
# docx + md + txt — все сразу
|
||
python run.py -i meeting.wav -f docx,md,txt
|
||
|
||
# Только docx и md
|
||
python run.py -i meeting.wav -f docx,md
|
||
|
||
# Явно указать выход только для одного формата, остальные рядом
|
||
python run.py -i meeting.wav -f docx,md -o output/meeting.docx
|
||
# Создаст: output/meeting.docx и output/meeting.md
|
||
```
|
||
|
||
**Продвинутые опции:**
|
||
```bash
|
||
# Сменить профиль
|
||
python run.py -i meeting.wav -p gpu_8gb
|
||
|
||
# Только CPU
|
||
python run.py -i meeting.wav -d cpu -m small
|
||
|
||
# Отключить диаризацию (быстрее, но без разделения спикеров)
|
||
# (нужно изменить diarize: false в config.yaml)
|
||
```
|
||
|
||
## Профили оборудования
|
||
|
||
Профили настроены в `config.yaml`:
|
||
|
||
- **`mac_m4`** (по умолчанию): CPU + int8, `large-v3`. Оптимально для MacBook Air M4 16GB.
|
||
- **`gpu_8gb`**: CUDA + float16/int8, `large-v3`, `batch_size=1`. Для видеокарты с 8GB VRAM.
|
||
- **`cpu_best`**: CPU + int8, `large-v3`. Универсальный CPU.
|
||
|
||
## Выходной формат
|
||
|
||
### DOCX
|
||
- Заголовок "Протокол совещания"
|
||
- Каждый спикер — отдельный абзац
|
||
- Таймкоды в формате `[HH:MM:SS.mmm]`
|
||
- Новый абзац при смене спикера или паузе > 2 сек
|
||
|
||
### Markdown / TXT
|
||
- Аналогичная структура
|
||
- Таймкоды опционально (включаются в `config.yaml`)
|
||
|
||
## Настройка
|
||
|
||
Измените `config.yaml`:
|
||
|
||
```yaml
|
||
active_profile: mac_m4 # или gpu_8gb
|
||
|
||
profiles:
|
||
mac_m4:
|
||
device: cpu
|
||
compute_type: int8
|
||
model: large-v3
|
||
language: ru
|
||
|
||
output:
|
||
formats: [docx, md] # Можно указать один или несколько
|
||
include_timestamps: true
|
||
paragraph_pause_sec: 2.0
|
||
```
|
||
|
||
## 🐳 Docker
|
||
|
||
Проект полностью контейнеризирован — все модели ИИ внутри образа.
|
||
|
||
### Быстрый старт
|
||
|
||
```bash
|
||
# Одна команда — сборка и запуск
|
||
docker compose up --build -d
|
||
|
||
# Готово! Откройте http://localhost:8000
|
||
```
|
||
|
||
### Первая установка
|
||
|
||
Токен HuggingFace уже настроен в `.env` файле (не коммитится в git). Если нужно сменить токен:
|
||
|
||
```bash
|
||
# Отредактируйте .env
|
||
nano .env
|
||
# HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxx
|
||
|
||
# Пересобрать образ с новым токеном
|
||
docker compose up --build -d
|
||
```
|
||
|
||
### Команды
|
||
|
||
```bash
|
||
# Сборка и запуск
|
||
docker compose up --build -d
|
||
|
||
# Только запуск (если образ уже собран)
|
||
docker compose up -d
|
||
|
||
# Просмотр логов
|
||
docker compose logs -f
|
||
|
||
# Остановка
|
||
docker compose down
|
||
|
||
# Полная очистка (удалит данные!)
|
||
docker compose down -v
|
||
```
|
||
|
||
### Volumes
|
||
|
||
| Volume | Описание |
|
||
|--------|----------|
|
||
| `uploads` | Загруженные файлы |
|
||
| `processed` | Результаты транскрибации |
|
||
| `tmp` | Временные файлы |
|
||
|
||
Данные сохраняются между перезапусками контейнера.
|
||
|
||
## 🧠 База знаний / RAG (Native Python Engine)
|
||
|
||
База знаний — **in-process Python-движок**: гибридный поиск BM25 (SQLite FTS5) + vector (sqlite-vec с numpy fallback) + LLM-реранкер через OpenCode. Хранение: один `index.sqlite` на коллекцию. **Внешних сервисов не требуется.**
|
||
|
||
### Архитектура
|
||
|
||
```
|
||
аудио/видео → WhisperX → extracted.md, summary.md
|
||
документы (PDF/DOCX/XLSX/...) → extracted.md
|
||
бинарники (.mp4, .zip) → stub_writer → *.md с YAML frontmatter
|
||
↓
|
||
Native Python RAG engine (in-process)
|
||
├─ chunker (markdown-aware, 900 chars, 15% overlap)
|
||
├─ embeddings (sentence-transformers, 384 dim, мультиязычный)
|
||
├─ FTS5 BM25 + sqlite-vec cosine
|
||
├─ RRF fusion (k=60)
|
||
└─ LLM rerank (OpenCode/DeepSeek, опционально)
|
||
↓
|
||
OpenCode / DeepSeek chat-completions
|
||
↓
|
||
WebSocket → rag_context → rag_chunk* → rag_response
|
||
```
|
||
|
||
### Установка зависимостей
|
||
|
||
Native engine использует только Python-пакеты (никаких npm/node):
|
||
|
||
```bash
|
||
pip install -r requirements.txt
|
||
# Скачает ~50 MB модели при первом запуске (sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2)
|
||
```
|
||
|
||
### Конфигурация
|
||
|
||
Секция `rag:` в `config.yaml`:
|
||
|
||
```yaml
|
||
rag:
|
||
enabled: true
|
||
auto_index: true
|
||
qmd_collection_root: ./processed # корень коллекций
|
||
qmd_use_rerank: true # LLM-реранкер через OpenCode
|
||
embed_model: sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
|
||
chat_model: deepseek-v4-flash-free
|
||
chat_max_tokens: 8192
|
||
```
|
||
|
||
ENV-переменные: `QMD_COLLECTION_ROOT`, `RAG_EMBED_MODEL`, `OPENCODE_API_KEY`, `OPENCODE_URL`.
|
||
|
||
### Где хранятся индексы
|
||
|
||
```
|
||
processed/
|
||
<org_slug>/
|
||
qmd_collections/ # ← native engine (in-process, без демонов)
|
||
<project_slug>/
|
||
index.sqlite # FTS5 + sqlite-vec + chunks
|
||
_global/
|
||
index.sqlite # cross-project поиск
|
||
meetings/<folder>/ # .docx, .md, .json для совещаний
|
||
documents/<doc_id>/ # .pdf, extracted.md, metadata.json
|
||
lightrag_caches/ # legacy: для миграции
|
||
```
|
||
|
||
### Поддерживаемые форматы и stub'ы
|
||
|
||
Движок индексирует `.md`/`.txt` нативно. Для бинарных форматов
|
||
(видео, не-OCR PDF, архивы) `src/ingest/stub_writer.py` создаёт
|
||
`<filename>.md` со ссылкой на оригинал. Пользователь видит stub в результатах
|
||
поиска и кликает на ссылку — открывается оригинал.
|
||
|
||
### Fallback-стратегии
|
||
|
||
- **FTS5 недоступен** в системной сборке Python → `rank_bm25` in-memory.
|
||
- **sqlite-vec недоступен** → numpy cosine in-memory.
|
||
- **Embedding-модель не загрузилась** → BM25-only режим, `qmd: degraded` в healthcheck.
|
||
|
||
### Legacy-миграция с LightRAG
|
||
|
||
```bash
|
||
# 1. Снапшот
|
||
tar -czf ../processed-pre-qmd.tar.gz processed
|
||
|
||
# 2. dry-run
|
||
python scripts/migrate_lightrag_to_qmd.py --org merakom --dry-run
|
||
|
||
# 3. реальная миграция
|
||
python scripts/migrate_lightrag_to_qmd.py --org merakom
|
||
```
|
||
|
||
Скрипт идемпотентен: повторный запуск безопасен. См. `scripts/README.md`.
|
||
|
||
## 🌐 Веб-интерфейс
|
||
|
||
Проект включает веб-сервис с минималистичным фронтендом для удобной работы через браузер.
|
||
|
||
### Функции веб-интерфейса
|
||
|
||
- 📤 **Drag & Drop загрузка** — перетащите файлы или выберите через диалог
|
||
- 📦 **Пакетная загрузка** — загружайте несколько файлов одновременно
|
||
- 📊 **Прогресс в реальном времени** — WebSocket показывает статус обработки каждого файла
|
||
- 🌳 **Файловый менеджер** — дерево обработанных совещаний с датами
|
||
- 📝 **Просмотр Markdown** — встроенный рендерер с подсветкой синтаксиса
|
||
- ⬇️ **Скачивание** — docx и md файлы доступны для скачивания
|
||
|
||
### Запуск веб-сервера
|
||
|
||
```bash
|
||
# Установите HF_TOKEN
|
||
export HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxx
|
||
|
||
# Запустите сервер
|
||
python start_server.py
|
||
```
|
||
|
||
Сервер поднимается на `http://localhost:8000`
|
||
|
||
Откройте браузер и перетащите файлы в зону загрузки. Обработка происходит в фоне, прогресс отображается в реальном времени.
|
||
|
||
### API Endpoints
|
||
|
||
| Endpoint | Метод | Описание |
|
||
|----------|-------|----------|
|
||
| `/` | GET | Фронтенд |
|
||
| `/upload` | POST | Загрузка одного файла |
|
||
| `/upload-batch` | POST | Пакетная загрузка |
|
||
| `/ws` | WebSocket | Прогресс обработки |
|
||
| `/api/tasks` | GET | Список задач |
|
||
| `/api/files` | GET | Дерево обработанных файлов |
|
||
| `/api/files/content?path=...` | GET | Содержимое файла |
|
||
| `/api/files/download?path=...` | GET | Скачивание файла |
|
||
|
||
## 🚀 Деплой (git + rsync моделей)
|
||
|
||
Архитектура: **только код в git** (~2 MB), **модели отдельно** (5.83 GB через bind-mount). `.env` с секретами исключён из git, разворачивается вручную или через `scripts/deploy.sh`.
|
||
|
||
### На исходной машине (один раз после изменений)
|
||
|
||
```bash
|
||
# Коммит и push кода
|
||
git add -A
|
||
git commit -m "..."
|
||
git push origin main
|
||
|
||
# Деплой на сервер (rsync + remote make)
|
||
./scripts/deploy.sh user@server /opt/transcription
|
||
```
|
||
|
||
`scripts/deploy.sh` делает:
|
||
1. `rsync` кода (исключая `models/`, `processed/`, `data/`, `migrate/`, `.git`)
|
||
2. Копирует `.env` (с секретами) на сервер
|
||
3. По SSH: `make pull-models MODELS_SOURCE=... && make up`
|
||
|
||
### На новом сервере (Linux + Docker)
|
||
|
||
```bash
|
||
# 1. Установить Docker
|
||
curl -fsSL https://get.docker.com | sh
|
||
sudo usermod -aG docker $USER # перелогиниться
|
||
|
||
# 2. Клонировать код
|
||
git clone https://gts.meratalk.online/keboss/transcription.git /opt/transcription
|
||
cd /opt/transcription
|
||
|
||
# 3. Создать .env
|
||
make init # cp .env.example .env
|
||
nano .env # вписать HF_TOKEN, OPENCODE_API_KEY, JWT_SECRET
|
||
|
||
# 4. Загрузить модели (5.83 GB) одним из способов:
|
||
make pull-models MODELS_SOURCE=user@dev-host:/opt/transcription/models/
|
||
# или
|
||
rsync -avz user@dev-host:/opt/transcription/models/ ./models/
|
||
|
||
# 5. Запустить
|
||
make up # docker compose up --build -d
|
||
|
||
# 6. Проверить
|
||
make status # docker compose ps + curl /api/health
|
||
make logs # docker compose logs -f transcription
|
||
```
|
||
|
||
### Make-цели (шпаргалка)
|
||
|
||
```bash
|
||
make help # список всех целей
|
||
make init # создать .env из .env.example
|
||
make pull-models # rsync моделей (нужна MODELS_SOURCE)
|
||
make up # build + запуск
|
||
make down # остановка
|
||
make restart # перезапуск без rebuild
|
||
make logs # логи в follow
|
||
make status # ps + /api/health
|
||
make test # pytest
|
||
make deploy # pull-models + up (всё вместе)
|
||
make clean # down -v (ОСТОРОЖНО: стирает volumes)
|
||
```
|
||
|
||
### Почему не Git LFS?
|
||
|
||
5.83 GB моделей в git = тяжёлый clone, раздутая история. Rsync быстрее, проще, безопаснее. Если позже понадобится — добавим Git LFS отдельным шагом.
|
||
|
||
### Где живут секреты
|
||
|
||
| Файл | В git? | Где на сервере |
|
||
|---|---|---|
|
||
| `.env.example` | ✅ да | — |
|
||
| `.env` | ❌ нет | копируется `scripts/deploy.sh` или вручную |
|
||
| `config.yaml` | ✅ да | bind-mount `:ro` в compose |
|
||
| `models/` | ❌ нет | bind-mount `./models/huggingface:/root/.cache/huggingface` |
|
||
|
||
## Ограничения
|
||
|
||
- Перекрывающаяся речь (overlap) распознаётся плохо
|
||
- Качество зависит от записи: тихий/шумный звук требует предобработки (шумоподавление, нормализация) или записи с лучшим микрофоном
|
||
- На CPU `large-v3` работает медленно (1 час записи ≈ 30-60 мин обработки)
|