Последние пару месяцев у меня случилось много разных созвонов на английском. В целом, я обычно нормально все понимаю, но боюсь упустить что-то важное. Даже субтитры помогают лишь частично. То есть нужен костыль (или аугментация).
Поискал, что есть из того, что может пригодиться. Нашел статью на Хабре про расшифровку собеседований. Идея простая: записал аудио, прогнал через Whisper, скопировал текст в ChatGPT, получил анализ. В целом ок, но pipeline выглядел так:
Запустить запись в OBS / аудасити / что-то ещё
Дождаться конца звонка, сохранить файл
Запустить скрипт с Whisper
Открыть result.txt
Скопировать в ChatGPT
Прочитать ответ, куда-то сохранить
Шесть ручных шагов. И главное - в финальном тексте нет понимания, кто что сказал. Whisper отдаёт сплошной поток текста, в котором реплики всех собеседников перемешаны. Для собеса это ок (там обычно один собеседник), а для созвона уже на 3-4-х - бесполезно.
Мне захотелось автоматизировать (упростить для себя процесс): одна команда на старте, в результате размеченный транскрипт с именами спикеров и саммари в Obsidian.
Также думал, как результат усилий монетизировать :) Не придумал :) Так в итоге появился мой опенсорсный проектик tapeback

Что под капотом
tapeback - CLI-инструмент для Linux, который захватывает аудио на уровне ОС через PipeWire/PulseAudio, транскрибирует локально через faster-whisper, определяет спикеров и (опционально) генерирует саммари через LLM.
Весь результат - Markdown-файл в Obsidian-хранилище (обычная директория).
Никакого облака для транскрипции. Никакого Electron. Никакого Docker.
tapeback start # начать запись # ... созвон идёт ... # Ctrl+C # -> транскрипция -> диаризация -> саммари -> файл в Obsidian
Работает с любой платформой для созвонов: Google Meet, Zoom, Teams, Telegram, Discord.
Захват аудио происходит на уровне PulseAudio sink/source, а не через API конкретного сервиса.
Стерео-каналы как основное решение
Тут стоит объяснить подробнее, потому что это решение определило всю архитектуру.
Классический подход к определению спикеров - взять моно-запись и прогнать через модель диаризации (pyannote, например). Модель кластеризует фрагменты по голосовым эмбеддингам и присваивает каждому кластеру номер: Speaker 0, Speaker 1, Speaker 2…
Проблема: кластеризация голосов - это вероятностная задача. Модель может перепутать два похожих мужских голоса. Может разбить одного спикера на два, если он в начале звонка говорил тихо, а потом громче. И самое неприятное - вы никогда точно не знаете, какой кластер - это вы.
tapeback записывает стерео WAV: левый канал = микрофон (вы), правый = monitor source (все остальные). Это физическое разделение, а не статистическое. Ваш голос на левом канале - это факт, а не гипотеза.
┌─────────────┐ ┌──────────────┐ ┌────────────────────┐ │ Стерео │ │ Whisper │ │ Классификация │ │ WAV-файл │───▶│ (на каждый │───▶│ по каналам: │ │ L: mic │ │ канал │ │ L -> "You" │ │ R: monitor │ │ отдельно) │ │ R -> pyannote │ └─────────────┘ └──────────────┘ │ для нумерации │ │ среди "Others"│ └────────────────────┘
Whisper работает на каждом канале независимо. Микрофонный канал = “You”. Если подключена диаризация, то сегменты классифицируются по RMS-энергии. pyannote применяется только к monitor-каналу, чтобы пронумеровать спикеров среди “Others” (Speaker 1, Speaker 2…). По возможности используется GPU, но при ошибках cuda или GPU-памяти переходит на CPU.
Ранняя версия мержила стерео в моно перед транскрипцией - и это была ошибка. Текущая архитектура сохраняет канальное разделение на всём протяжении pipeline.
Опциональные зависимости, или как не заставлять качать 2 ГБ PyTorch
pyannote-audio тащит за собой PyTorch, а это ~2 ГБ. Не каждому это нужно: если вы записываете один на один, вам хватит стерео-разделения без pyannote. LLM-саммари тоже нужны не всем - кто-то хочет только транскрипт.
А если все-таки нужны, то в энвах (про них чуть ниже) надо прописать соответствующие токены. А для диаризации еще и зарегиться на huggingface.co и поставить галочки принятия в 3-х местах (подробнее в README.md проекта), потому что лучшая (из тех что нашел) моделька гейтится)
Поэтому tapeback разбит на опциональные extras:
Вариант | Что добавляет | Размер |
|---|---|---|
| Запись + транскрипция | ~320 МБ |
| + саммари (Anthropic, OpenAI, Gemini и др.) | +50 МБ |
| + диаризация спикеров (pyannote + PyTorch) | +2 ГБ |
| + системный трей | +1 МБ |
Важно, что tapeback[diarize] не ломает работу без себя: если pyannote не установлен, monitor-канал просто помечается как “Other” без нумерации. Graceful degradation, а не crash.
В коде это ленивые импорты: pyannote грузится внутри '__init__' диаризатора, а не на уровне модуля. Не пришлось разбивать diarizer.py на два файла или городить абстрактные классы.
LLM-саммари с fallback-цепочкой
После транскрипции tapeback может отправить текст в LLM для извлечения краткого содержания, экшн-айтемов и ключевых решений. Поддерживается 7 провайдеров: Anthropic, OpenAI, Groq, Gemini, DeepSeek, OpenRouter, Qwen.
Интересный момент: все провайдеры, кроме Anthropic, используют OpenAI-совместимый API. Поэтому вместо абстрактной иерархии классов достаточно простого if/else и словаря с base URL:
# Вместо ProviderFactory -> AbstractProvider -> AnthropicProvider -> ... _OPENAI_COMPATIBLE_BASE_URLS = { "groq": "https://api.groq.com/openai/v1", "gemini": "https://generativelanguage.googleapis.com/v1beta/openai/", "deepseek": "https://api.deepseek.com", # ... }
Если основной провайдер отвечает ошибкой - tapeback автоматически пробует следующий доступный из цепочки. Тестировал в основном на {“gemini”: “gemini-2.5-flash”}
Транскрипт сохраняется всегда, даже если все LLM-провайдеры легли. Результат - структурированный JSON, который вставляется в начало Markdown-файла:
## Summary Brief overview of the meeting. ### Action Items - [ ] **You:** Send the report by Friday - [ ] **Speaker 1:** Review the PR ### Key decisions - Use PostgreSQL instead of MongoDB
История с переименованиями
Первая версия называлась meetrec = meeting recorder. Тогда еще не думал, что буду проект регить как Pypi/AUR пакет. А когда подумал - то обнаружил что такой проект уже существует. Имя занято и у него даже сайт есть.
Переименовал в echo-vault. Переименовал всё: модули, CLI, конфиги, README. Запушил. Пошёл регистрировать на Pypi.
echo-vault is too similar to an existing project echovault.
Pypi нормализует имена: дефисы, подчёркивания и точки считаются одним и тем же символом. echo-vault, echo_vault, echovault - для Pypi это один пакет.
И echovault уже занято :)pip install echo-vault и pip install echovault скачают один и тот же пакет.
Третья попытка: tapeback. Ещё раз переименовал всё - модули, CLI, пути к конфигам, temp-директории, AUR-пакеты.
В общем всегда выбирайте/проверяйте имя на Pypi/AUR заранее. И про нормализованное не забывайте. И не только там - везде проверяйте.
AUR: 4 пакета вместо одного
Для Arch Linux tapeback опубликован в AUR как 4 отдельных пакета:
tapeback- базовый (запись + транскрипция)tapeback-tray- мета-пакет, добавляющий удобство работы через системный трейtapeback-llm- мета-пакет, добавляющий LLM-зависимостиtapeback-diarize- мета-пакет, добавляющий PyTorch + pyannote
Причина та же: не заставлять пользователя качать 2 ГБ PyTorch ради транскрипции.
В AUR нельзя нормально упаковать pip-зависимости как нативные пакеты (PyTorch-колёса ~2 ГБ, это не ложится в PKGBUILD), поэтому tapeback-diarize, tapeback-tray и tapeback-llm - мета-пакеты, которые доустанавливают pip-зависимости в venv через install-хуки.
yay -S tapeback # ~320 МБ, базовая функциональность yay -S tapeback-tray # + system tray icon yay -S tapeback-diarize # + ~2 ГБ PyTorch yay -S tapeback-llm # + LLM SDK
Релизный процесс довольно автоматизирован: scripts/release.sh бампает версию во всех файлах, CI публикует на PyPI через Trusted Publisher (OIDC, без токенов в секретах), scripts/aur-publish.sh обновляет 4 AUR-репозитория. Actions запинены на SHA коммитов - паранойя по поводу supply-chain атак через перезапись тегов.
Процесс разработки
От первого коммита до кое-как первой работающей локально версии прошло примерно 3 дня.
До версии на момент написания v0.8.9 прошло еще 2 недели - пока допиливал все баги и тестил пакеты.
Подход — spec-driven: сначала через веб-проект Антропика готовлю детальную спеку (ресерчи, обсуждения и тп.) на каждую фазу (capture, diarization, summarization).
Спеки — .md-файлы с секциями «разрешённые действия», «критерии готовности», и тп.
Почти весь код по этим спекам писал Claude Code.
Я - в основном как ревьювер и QA. Ну и в гит я еще не готов агентам разрешать писать :)
Ну еще немного играл роль злого техлида со строгим подходом к качеству кода и архитектурными прошлыми травмами.
Про монетизацию :)
Когда проект начал обретать форму, я задумался: а можно ли это продавать? Локальная транскрипция, диаризация, интеграция с Obsidian - звучит как SaaS за $10/мес.
Но чем дольше думал, тем яснее становилось, что это не тот продукт.
Целевая аудитория - линуксоиды, которые записывают созвоны из терминала. Это не массовый рынок.
Платить за CLI-тул, который требует ffmpeg, parecord и опционально 2 ГБ PyTorch? Вряд ли.
А упаковывать это в Electron с кнопками “Start” и “Stop” и продавать как дешёвую копию коммерческих транскриберов - значит убить то, что мне самому в нём нравится.
В итоге просто выложил в open source. Если кому-то пригодится - отлично. Если нет - у меня всё равно есть инструмент, который решает мою задачу.
Что дальше
tapeback пока что делает уже достаточно для меня: записал созвон - получил транскрипт с разделением по голосам и саммари в Obsidian.
Но есть вещи, которые может быть соберусь позже доделать:
Улучшение диаризации: pyannote иногда разбивает одного спикера на двух. Уже есть spectral similarity merging (сравнение спектральных профилей голосов), но пороги и алгоритмы надо тюнить
Профили спикеров: запоминаем имена спикеров из прошлых созвонов
Транскрибация в реальном времени: может даже выводить текст в отдельном окне
Поддержка macOS/Windows: заменить parecord на CoreAudio / WASAPI loopback захват. Pipeline после записи платформонезависимый.
Поддержка других языков кроме английского: пока не знаю, зачем мне это нужно :) Whisper и так работает со 100+ языками, но тестировал только на английском - за остальное не ручаюсь
Попробовать
# Минимальная установка — запись + транскрипция uv tool install tapeback # Со всем uv tool install "tapeback[tray,diarize,llm]" # Arch Linux yay -S tapeback tapeback-diarize tapeback-llm tapeback-tray
Энвы с путем и токенами хранятся .env или в ~/.config/tapeback/.env
# Прописать путь, куда сохранять аудио и транскрибацию. Это не обязательно обсидиан, подойдет любая директория # Если не прописать энв, то по дефолту будет ~/tapeback mkdir -p ~/.config/tapeback echo 'TAPEBACK_VAULT_PATH=~/Documents/obsidian/vault' > ~/.config/tapeback/.env
# Естественно, что перед созвоном всегда спрашивайте у собеседников, # не против ли они записи tapeback start # ... созвон ... # -> press Ctrl+C to stop and transcribe now
tapeback tray # или можно запускать запись через иконку в систем трей
Исходники: github.com/yastcher/tapeback
Если проект показался полезным - поставьте звездочку на GitHub.
Если остались вопросы - пишите в комментарии или открывайте issue.
