# 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/ / qmd_collections/ # ← native engine (in-process, без демонов) / index.sqlite # FTS5 + sqlite-vec + chunks _global/ index.sqlite # cross-project поиск meetings// # .docx, .md, .json для совещаний documents// # .pdf, extracted.md, metadata.json lightrag_caches/ # legacy: для миграции ``` ### Поддерживаемые форматы и stub'ы Движок индексирует `.md`/`.txt` нативно. Для бинарных форматов (видео, не-OCR PDF, архивы) `src/ingest/stub_writer.py` создаёт `.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 мин обработки)