transcription/README.md

21 KiB
Raw Blame History

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 зависимости

pip install -r requirements.txt

2. ffmpeg (обязателен для видео)

Программа нуждается в ffmpeg для извлечения аудио из видео файлов.

macOS:

brew install ffmpeg

Ubuntu/Debian:

sudo apt-get update && sudo apt-get install ffmpeg

Windows: Скачайте с ffmpeg.org/download.html и добавьте в PATH.

3. HuggingFace токен

Нужен для диаризации (см. раздел ниже).

HuggingFace Token (обязателен для диаризации)

Зачем нужен токен?

WhisperX для определения спикеров (диаризация) использует модели pyannote.audio, которые хранятся на платформе HuggingFace. Эти модели:

  • НЕ являются публично доступными без регистрации
  • Требуют принятия пользовательского соглашения (license)
  • Требуют аутентификации через токен при скачивании

Без токена диаризация не будет работать — вы получите ошибку авторизации.

Как получить токен (пошагово)

Шаг 1: Регистрация

  1. Перейдите на huggingface.co
  2. Нажмите "Sign Up" (регистрация через email или GitHub/Google)
  3. Подтвердите email

Шаг 2: Создание токена

  1. Войдите в аккаунт
  2. Перейдите в Settings → Access Tokens
  3. Нажмите "New token"
  4. Введите название (например, transcription)
  5. Выберите тип: Read (только чтение — достаточно)
  6. Нажмите "Generate token"
  7. Скопируйте токен сразу — он показывается только один раз!

Шаг 3: Принятие соглашений

Нужно принять соглашение для каждой из этих моделей (зайдите по ссылкам и нажмите "Access repository", затем согласитесь с условиями):

  1. pyannote/speaker-diarization-3.1
  2. pyannote/segmentation-3.0

Важно: Если не принять соглашения, даже с правильным токеном будет ошибка 403 (Forbidden)!

Шаг 4: Установка токена

Вариант A — через переменную окружения (рекомендуется):

export HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxx

Вариант B — в config.yaml (менее безопасно, токен попадёт в git):

hf_token: "hf_xxxxxxxxxxxxxxxxxxxxxxxx"

Вариант C — в .env файл (если добавить .env в .gitignore):

# .env
HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxx

Безопасность: Токен — это ваш пароль от HuggingFace. Никогда не коммитьте его в публичный репозиторий!

Проверка токена

После установки можно проверить:

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 Точные таймкоды слов
Итого ~45 GB Скачиваются один раз

Время скачивания зависит от скорости интернета (обычно 1030 минут).
Все последующие запуски используют локальные файлы и работают без интернета.

Где хранятся модели

Модели сохраняются в системный кэш:

  • Linux/Mac: ~/.cache/
  • Windows: %USERPROFILE%\.cache\

Работа офлайн — всё локально!

Да, все модели работают полностью локально.

Токен HuggingFace нужен только один раз — чтобы скачать модели при первом запуске. После этого:

  • Интернет не нужен — можно отключить Wi-Fi
  • Аудио не уходит никуда — обработка только на вашем устройстве
  • Текст не уходит в облако — результат только у вас
  • Подходит для конфиденциальных совещаний

Что скачивается при первом запуске

Компонент Размер Зачем
Whisper large-v3 ~3 GB Распознавание речи
Pyannote диаризация ~400 MB Разделение спикеров
Wav2Vec2 (русский) ~1 GB Точные таймкоды слов
Итого ~45 GB Скачиваются один раз

Модели сохраняются в системный кэш (~/.cache/ на Linux/Mac, %USERPROFILE%\.cache\ на Windows) и переиспользуются при каждом запуске.

Использование

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

Примеры

Аудио файлы:

# Базовый запуск (по умолчанию: 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

Видео файлы (автоматически извлекается аудио):

# Из видео совещания
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

Вывод в несколько форматов одновременно:

# 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

Продвинутые опции:

# Сменить профиль
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:

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

Проект полностью контейнеризирован — все модели ИИ внутри образа.

Быстрый старт

# Одна команда — сборка и запуск
docker compose up --build -d

# Готово! Откройте http://localhost:8000

Первая установка

Токен HuggingFace уже настроен в .env файле (не коммитится в git). Если нужно сменить токен:

# Отредактируйте .env
nano .env
# HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxx

# Пересобрать образ с новым токеном
docker compose up --build -d

Команды

# Сборка и запуск
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):

pip install -r requirements.txt
# Скачает ~50 MB модели при первом запуске (sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2)

Конфигурация

Секция rag: в config.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

# 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 файлы доступны для скачивания

Запуск веб-сервера

# Установите 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.

На исходной машине (один раз после изменений)

# Коммит и 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)

# 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-цели (шпаргалка)

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 мин обработки)