Open source сервис для локальной обработки аудиозаписей звонков в .m4a.
Приложение следит за папкой calls/, расшифровывает новые файлы через локальный faster-whisper, отправляет транскрипцию в локально запущенный ollama для summary и сохраняет результат в .md рядом с аудиофайлом.
- отслеживает новые
.m4aв папкеcalls/ - обрабатывает уже существующие записи при старте
- ждёт завершения записи файла перед обработкой
- пропускает файл, если рядом уже есть готовый
.md - сохраняет summary и полную транскрипцию в одном markdown-файле
- умеет размечать разных спикеров через
pyannoteпри включённом diarization - работает локально и через Docker
- ваши
.env-файлы с локальными настройками - папку
calls/с аудио и готовыми markdown-результатами - кэши моделей, build-артефакты и служебные рабочие заметки
Для файла:
calls/demo.m4a
будет создан файл:
calls/demo.md
Внутри будут:
- имя исходного файла
- мета-информация о времени обработки, языке и длительности
- признак, удалось ли определить спикеров
- раздел
## Summary - раздел
## Transcript
- Python 3.11+ для локального запуска
ffmpeg- локально запущенный
ollama - хотя бы одна доступная модель в
ollama - достаточно CPU или GPU для запуска
faster-whisper - для diarization: optional extra
.[diarization]для локального запуска, модельpyannoteи, как правило, токен Hugging Face для первой загрузки
faster-whisper работает полностью локально и бесплатно, без обращения к API OpenAI. Для русскоязычных звонков quality-default в этом проекте это large-v3: это мультиязычная модель, в отличие от distil-large-v3, который не подходит как default для русского контура.
Установка ffmpeg на macOS:
brew install ffmpegПример загрузки модели:
ollama pull llama3.1:8bПроверить доступные локальные модели:
ollama listpython3 -m venv .venv
source .venv/bin/activate
pip install -e .
cp .env.example .env
meeting-summaryПосле запуска просто положите .m4a в папку calls/.
Если нужен speaker diarization, установите optional extra:
pip install -e .[diarization]Если ollama уже запущена на хост-машине, можно поднять только контейнер приложения:
docker compose up --build -dDocker-образ устанавливает diarization-зависимости сразу во время сборки, поэтому при ENABLE_DIARIZATION=true контейнер не должен молча работать без спикеров из-за отсутствующего pyannote.
Если pyannote не инициализируется из-за токена, непринятого model access или runtime-проблемы, сервис завершится на старте с явной ошибкой конфигурации.
Полезные команды:
docker compose logs -f meeting-summary
docker compose downЕсли проект уже локально скачан и контейнер раньше уже запускался, базовый сценарий обновления такой:
git switch main
git fetch origin
git pull --ff-only origin main
docker compose up --build -d
docker compose logs -f meeting-summaryЧто делают команды:
git fetch originобновляет ссылки на удалённые веткиgit pull --ff-only origin mainподтягивает свежийmainбез merge-коммитаdocker compose up --build -dпересобирает локальный образ из обновлённого кода и перезапускает контейнер
Если у вас есть локальные незакоммиченные изменения, сначала временно уберите их в stash:
git stash push -u
git switch main
git fetch origin
git pull --ff-only origin main
docker compose up --build -d
git stash popdocker compose pull здесь не нужен: образ не скачивается из registry, а собирается локально из текущего состояния репозитория.
В Docker сервис автоматически использует:
CALLS_DIR=/app/callsOLLAMA_BASE_URL=http://host.docker.internal:11434OLLAMA_PROMPT_PATH=/app/runtime-prompts/summary.md
Папки ./calls и ./meeting_summary/prompts монтируются в контейнер, а кэши whisper и huggingface сохраняются в docker volume.
Prompt можно менять на хосте без пересборки образа и без рестарта сервиса: следующий summary возьмёт уже новую версию файла.
Пример .env:
OLLAMA_MODEL=auto
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_PROMPT_PATH=./meeting_summary/prompts/summary.md
WHISPER_MODEL=large-v3
WHISPER_DEVICE=auto
WHISPER_COMPUTE_TYPE=auto
WHISPER_LANGUAGE=ru
WHISPER_INITIAL_PROMPT=
WHISPER_TERMS=Docker,GitHub,API,backend,frontend,deploy,pipeline,OpenAI,Whisper,Ollama
WHISPER_BEAM_SIZE=5
WHISPER_BEST_OF=5
WHISPER_TEMPERATURE=0
WHISPER_VAD_FILTER=true
ENABLE_DIARIZATION=false
HF_TOKEN=
PYANNOTE_DEVICE=auto
CALLS_DIR=./calls
FILE_READY_CHECKS=3
FILE_READY_INTERVAL_SECONDS=2
FILE_DUPLICATE_COOLDOWN_SECONDS=120
INITIAL_SCAN=trueОписание параметров:
OLLAMA_MODEL: модель для summary; значениеautoвыбирает первую локально установленную модельOLLAMA_BASE_URL: адрес локального APIollamaOLLAMA_PROMPT_PATH: путь до markdown-шаблона prompt дляollama; файл перечитывается при каждом новом summary- если переменная не задана, используется встроенный prompt из ресурсов пакета
meeting_summary/prompts/summary.md - если путь относительный, он резолвится относительно директории загрузки конфигурации (
base_dir/ текущая директория запуска) - в шаблоне подставляются только
{language},{duration_seconds}и{transcript_block}; остальные{}остаются обычным текстом WHISPER_MODEL: имя моделиfaster-whisper, напримерlarge-v3,mediumилиsmall;distil-large-v3не использовать как default для русскоязычных звонковWHISPER_DEVICE: устройство дляfaster-whisper, обычноauto,cpuилиcudaWHISPER_COMPUTE_TYPE: compute type дляfaster-whisper; значениеautoвыбираетint8на CPU иfloat16на CUDAWHISPER_LANGUAGE: язык распознавания; для русскоязычных звонков рекомендуетсяru, чтобы модель не блуждала между языкамиWHISPER_INITIAL_PROMPT: optional glossary prompt для имен, брендов и англоязычных технических терминов; лучше держать его коротким списком терминов, а не длинной инструкциейWHISPER_TERMS: короткий список терминов для auto-glossary prompt; используется только еслиWHISPER_INITIAL_PROMPTне заданWHISPER_BEAM_SIZE: ширина beam search; больше значение обычно улучшает качество, но замедляет обработкуWHISPER_BEST_OF: сколько кандидатов сравнивать при декодированииWHISPER_TEMPERATURE: температура декодирования; для стабильной транскрибации рекомендуется0WHISPER_VAD_FILTER: включает VAD-фильтрацию до декодирования, чтобы убирать тишину и снижать hallucinations на длинных паузахWHISPER_MODEL_SIZE: deprecated fallback для обратной совместимости; используется только еслиWHISPER_MODELне заданENABLE_DIARIZATION: включает speaker diarization черезpyannote- пустое значение трактуется как
false - если значение
true, сервис ожидает, чтоpyannoteи связанные зависимости доступны уже на старте; при misconfig он завершится с ошибкой, а не продолжит работу без спикеров HF_TOKEN: токен Hugging Face для первой загрузки модели diarization; если модель уже закеширована локально, может не понадобитьсяPYANNOTE_DEVICE: устройство для diarization, напримерauto,cpuилиcudaCALLS_DIR: папка, за которой следит сервисFILE_READY_CHECKS: сколько стабильных проверок размера файла нужно до старта обработкиFILE_READY_INTERVAL_SECONDS: интервал между проверками готовности файлаFILE_DUPLICATE_COOLDOWN_SECONDS: окно в секундах, в течение которого одинаковый.m4aс тем же размером иmtimeне будет повторно обрабатываться после новогоcreated/movedсобытияINITIAL_SCAN: обрабатывать ли существующие файлы при старте
- если указано некорректное имя модели
whisper, загрузка завершится ошибкой при старте сервиса - ошибка по одному файлу не останавливает watcher
- если
ollamaнедоступна, сервис продолжает работать и пишет ошибку в лог - если
.mdуже существует, файл считается обработанным - watcher подавляет повторную обработку того же файла в пределах
FILE_DUPLICATE_COOLDOWN_SECONDS, если fingerprint файла не изменился - если
ENABLE_DIARIZATION=true, ноpyannote/HF_TOKEN/доступ к моделям настроены некорректно, сервис завершится на старте с понятной ошибкой конфигурации - если diarization включен, но конкретный файл слишком короткий или пустой для надежного speaker split, сервис пропустит diarization только для этого файла и продолжит обработку без спикеров
- для
.m4adiarization использует ffmpeg/faster-whisperdecode как штатный путь, а не как шумный runtime fallback - в логах по каждому файлу печатаются stage-based статусы прогресса обработки
- Для русскоязычных звонков с техническими терминами не используйте
smallкак quality-first режим; мультиязычный quality-first default здесь этоlarge-v3 - Если железо позволяет и latency не критична, для максимального качества лучше
large-v3 - Для mixed-language звонков лучше фиксировать
WHISPER_LANGUAGE=ru, а glossary задавать коротким prompt илиWHISPER_TERMSвродеDocker,GitHub,API,backend,frontend,deploy - В Docker quality-default задаётся через
docker-compose.yml, даже если в локальном.envостался legacyWHISPER_MODEL_SIZE=small distil-large-v3не подходит как quality-default для русского контура: если нужен русский, выбирайте мультиязычныйlarge-v3или компромиссныйmedium
Во время обработки сервис пишет stage-progress по файлу, например:
[record_test.m4a] 10% | processing_started | File is stable. Starting analysis.
[record_test.m4a] 20% | transcribing | Transcribing audio with faster-whisper.
[record_test.m4a] 34% | transcribing_progress | Processed 12.4 / 37.1 min of audio.
[record_test.m4a] 75% | diarization_complete | Diarization finished with 2 speakers.
[record_test.m4a] 85% | summarizing | Generating summary with Ollama.
[record_test.m4a] 100% | completed | Saved markdown to record_test.md.
Это не внутренний процент самой модели Whisper, а устойчивый прогресс по стадиям пайплайна.
Стадия transcribing начинается на 20%, а во время долгого CPU/GPU-распознавания сервис дополнительно пишет heartbeat-обновления transcribing_progress до 55%, чтобы было видно, что обработка не зависла и не стартовала заново.
- Если
WHISPER_VAD_FILTER=true, в Docker/VM может появляться строка видаonnxruntime cpuid_info warning: Unknown CPU vendor... - Это native warning сторонней библиотеки ONNXRuntime/VAD, а не ошибка пайплайна обработки файла
- Если после этой строки лог доходит до
transcription_completeиcompleted, распознавание отработало штатно - Если нужна полностью тихая лог-лента без этой строки, единственный надежный вариант сейчас:
WHISPER_VAD_FILTER=false - Повтор
0% -> 20%должен происходить только при новом filesystem event, новом fingerprint файла или после истеченияFILE_DUPLICATE_COOLDOWN_SECONDS
По умолчанию diarization выключен. Чтобы включить определение спикеров:
pip install -e .[diarization]ENABLE_DIARIZATION=true
HF_TOKEN=hf_...
PYANNOTE_DEVICE=autoПеред первым запуском нужно:
- иметь аккаунт Hugging Face
- принять доступ к моделям
pyannote/segmentation-3.0иpyannote/speaker-diarization-3.1 - создать
HF_TOKENс правами чтения
После первой успешной загрузки модель остаётся в локальном кэше, поэтому на той же машине diarization обычно может работать оффлайн.
Если ENABLE_DIARIZATION=true, но pyannote не установлен в окружении контейнера/venv или не инициализировался, сервис завершится на старте с явной ошибкой конфигурации. Это сделано специально, чтобы не было ложного впечатления, что speaker diarization работает.
После этого раздел ## Transcript будет выглядеть примерно так:
[00:00-00:04] Speaker 1: Добрый день, начинаем.
[00:04-00:07] Speaker 2: Да, давайте.
meeting-summarypython -m meeting_summarypython -m meeting_summary.maindocker compose up --build- созвоны и стендапы
- интервью
- клиентские звонки
- голосовые заметки в
.m4a