
У меня от трёх до пяти созвонов в день, почти все в браузере: Meet, Яндекс Телемост, ktalk, реже всего Zoom. Детали встреч мне нужны в тексте, иначе договорённости расползаются. Писал я звонки через OBS с захватом экрана, и на самих звонках началось веселье: звук временами жёстко квакал, всё чуть-чуть подвисало. Причину до конца не выяснял, моё предположение — процессор зажирался захватом и перебивал сам разговор; ktalk и браузер тут ни при чём. А экран, как потом дошло, мне вообще был не нужен.
Дальше вторая серия. Расшифровку я гонял своими Python-скриптами: конвертация → транскрибация → диаризация (разметка, кто когда говорил). На встрече с пятью участниками диаризация расставляла голоса крайне плохо, половину фраз всё равно восстанавливал по памяти.
Пошёл искать вариант, чтобы запись не мешала звонку, результат был приличный и всё крутилось локально: не хотелось ни отдавать записи в облако, ни платить подписку за минуты. Нашёл платный Audio Hijack, он подкупил лёгкостью и простотой настройки. Потом узнал, что на M-чипе быстрее всего у меня отрабатывает mlx-whisper на MLX: веса large-v3-turbo с Hugging Face, инференс на GPU Mac. Это архитектура Whisper, но рантайм — пакет mlx-whisper и бинарь mlx_whisper, не pip install openai-whisper и не репозиторий openai/whisper. Так собрался стек: Hijack пишет один mp3, mlx_whisper делает VTT, pyannote раскладывает по SPEAKER_XX. На 56-минутном звонке транскрипция заняла около пяти минут. Платный только Hijack — разовая лицензия, без подписки (цену смотрите на сайте Rogue Amoeba). Скрипты выложил в mac-call-transcribe под MIT. Ниже сборка, замеры с двух реальных звонков и грабли; повторяется на любом Mac с M-чипом.
Запись: Audio Hijack вместо OBS

Возвращаться к OBS и копать, что именно там зажирало процессор, я не стал: квакающий звук на живом звонке дороже любой бесплатности. Облачные транскрибаторы вроде Otter снимают возню с расшифровкой, только запись уезжает на чужой сервер, а подписка капает каждый месяц. И есть третий пункт, о котором вспоминаешь позже всех: для диаризации нужен один файл, где все голоса вместе. Два раздельных трека «микрофон» и «система» pyannote не переварит.
Audio Hijack закрывает всё разом. Сессия собирается один раз, дальше просто копируется. Три блока: Google Chrome → Recorder, микрофон → тот же Recorder, и Google Chrome → Output Device, чтобы слышать собеседника в наушниках во время записи. Automatic Connectors выключить обязательно. Микрофон на Output не вешать, иначе будете слушать сами себя. У Rogue Amoeba принцип сформулирован прямо: если звук слышно на Mac, Audio Hijack может его записать.
Сессия Audio Hijack: Chrome на Output (слышимость), Chrome и микрофон в один Recorder MP3
Дальше рутина. Перед звонком жму Run, после Stop переношу mp3 в рабочую папку. Один файл, все голоса внутри. Работает с любым созвоном, который идёт в Chrome.
Бесплатный путь записи тоже существует: виртуальное аудиоустройство BlackHole плюс любая записывалка. Сам не пробовал: сводить микрофон и звук системы в один файл там нужно руками, а Hijack ровно эту возню и убирает.
Расшифровка: один вызов вместо цепочки руками
Первую версию я гонял в два шага: transcribe-mlx (внутри — CLI mlx_whisper) и diarize-vtt.py поверх VTT. Работало, но имена и чистку мусора приходилось собирать руками. Сейчас вход один: transcribe-meeting.py из репозитория. Внутри STT — тот же mlx_whisper через stt.py (subprocess), не import whisper из openai/whisper. Скрипт выкидывает типовые галлюцинации на тишине (строки про «Субтитры», зацикленное слово), склеивает куски, если Hijack разбил запись на несколько файлов, и пишет meeting.json.
~/tools/mlx-whisper-env/bin/python ~/bin/transcribe-meeting.py run meeting.mp3 \ --out-dir ./transcripts --name 2026-07-20__meeting --diarize auto
(Скрипты из репозитория кладу в ~/bin/; путь к transcribe-meeting.py подставьте свой.)
JSON здесь главный файл, субтитры VTT и простой текст TXT собираются из него. VTT удобно читать глазами, но как протокол встречи он слабый: некуда положить список удалённого мусора и спорные стыки спикеров. В meeting.json лежат сегменты с таймкодами, поле removed с отфильтрованным и метка SPEAKER_UNKNOWN там, где pyannote не уверен. Имена пересобираются без повторного прогона mlx_whisper. На групповом разборе один кластер иногда цепляет двух людей, тогда правлю по времени в JSON, а не весь SPEAKER_06 махом. Старый двухшаговый путь (transcribe-mlx + diarize-vtt.py) в репозитории оставил для точечных правок уже готового VTT.
Двоим на звонке хватает --speakers 2. На групповом созвоне ставлю --diarize auto и не форсирую число: семь голосов, зажатые в два кластера, путаются ещё хуже.
Пара слов про формат. Модель speaker-diarization-3.1 обучена на телефонном звуке: один канал, 16 кГц. Стерео с разнесёнными каналами ломает её предположения о наложении голосов, поэтому один mp3 от Hijack, где все участники в одном канале, подходит идеально.
Минимальный STT без моего пайплайна — CLI из пакета mlx-whisper (именно так зовёт stt.py):
~/tools/mlx-whisper-env/bin/mlx_whisper meeting.mp3 \ --model ~/models/mlx-whisper-large-v3-turbo \ --language ru --output-format vtt \ --output-dir ./out --output-name meeting
В карточке mlx-community есть и Python-API mlx_whisper.transcribe(...). В репозитории я сознательно гоняю CLI mlx_whisper: проще кэшировать части multipart и не тащить openai-whisper.
Скорость на M4 по двум звонкам той же недели:
|
Звонок |
Длина аудио |
Время транскрипции |
|---|---|---|
|
SEO-созвон |
~14 мин |
1–2 мин |
|
Урок по Cursor |
~56 мин |
~5 мин |
Короткий SEO-созвон я разобрал сразу после Stop: договорённости, задачи на неделю. Урок по Cursor шёл почти час, там контекст плотнее и много пауз ученика. После прогона открываю начало VTT, смотрю, кто как представился, и подставляю имена руками. pyannote имена не угадывает, отдаёт только SPEAKER_00, SPEAKER_01 и дальше по списку.
Ставится на Mac M1–M4: pip install mlx-whisper (даёт бинарь mlx_whisper), pip install pyannote.audio, venv на ARM64 Python. Веса large-v3-turbo (1,5 ГБ) я держу в `/models/mlx-whisper-large-v3-turbo/`; суммарно под модели — 5–8 ГБ на диске.
Грабли первого вечера
Больше всего крови выпил Python. У меня стоял Homebrew ещё с Intel-времён, в /usr/local, и интерпретатор оттуда x86. MLX на нём не заводится, Rosetta нормального пути к Metal не даёт. Проверка простая: python3 -c "import platform; print(platform.machine())" должен вернуть arm64. Видите x86_64 — переставляйте Python на ARM64-native: Homebrew для M-чипов живёт в /opt/homebrew, оттуда brew install python. Мне это закрыло половину «почему ничего не работает».
Потом завис Hugging Face. Загрузка mlx-community/whisper-large-v3-turbo из скрипта стояла без прогресса. Подождал, плюнул, скачал веса прямым curl в ~/models, указал путь в обёртке. Заработало с первого раза. Если progress bar молчит дольше пары минут, час ждать смысла нет.
С pyannote отдельная история. Модели закрытые (gated): по README нужно принять условия на двух страницах, segmentation-3.0 и сама speaker-diarization-3.1, плюс токен доступа через hf auth login. Пропустили segmentation или не залогинились — на диаризации ждёт ошибка 403, доступ запрещён. Я в тот вечер накликал три Accept, потому что попробовал ещё speaker-diarization-community-1 для offline-режима. Для базового 3.1 третья страница не нужна.
А voxscriber так и не завёлся. Хотел готовую сборку из коробки, но связка torchcodec с Intel ffmpeg развалилась, и я написал свой консольный скрипт поверх VTT. Если хватает mlx_whisper + pyannote без красивого интерфейса, туда можно не ходить.
Что осталось кривым
На стыках коротких реплик pyannote путает SPEAKER_00 и SPEAKER_01. На групповых встречах один кластер иногда смешивает двух людей: проверяйте первые минуты и строки вида «Саша: Саш, привет». Это ограничение модели, настройками оно не лечится. Точные имена без ручной правки я никому не обещаю.
Whisper-модель на тишине выдумывает текст. Встроенная чистка в transcribe-meeting.py срезает типовой мусор; полный список удалённого — в meeting.json → removed (в двухшаговом пути ещё *.qc.json у diarize-vtt.py). Спорные стыки помечаются SPEAKER_UNKNOWN, а не приписываются случайному голосу.
Ещё есть флаг --extract-commitments, он вытаскивает черновик цифр и договорённостей. Это эвристика, ручной разбор она не заменяет. Итоговый текст я читаю как протокол: что решили, кто взял задачу, какие цифры прозвучали. По таймкодам удобно вернуться к спорному месту записи.
Чеклист перед первым прогоном
Если хотите попробовать мой вариант, проверьте у себя:
-
platform.machine()возвращаетarm64; Python из ARM64 Homebrew, не из/usr/local. -
Веса
mlx-community/whisper-large-v3-turboна диске (1,5 ГБ в `/models/…); если загрузка с Hugging Face зависает —curl` по ссылкам из карточки модели. -
Оба Accept на Hugging Face +
hf auth login; токен с доступом к закрытым моделям. -
Audio Hijack: Chrome и микрофон в один Recorder; Automatic Connectors = Off; Chrome → Output, чтобы слышать собеседника.
-
Один mp3 на звонок; для группы
--diarize auto, не--speakers 2. -
После прогона: первые реплики в VTT → имена руками; пробежать
removedвmeeting.json.
Остался вопрос, который я сам себе задаю: нужна ли диаризация вообще, если тот же текст можно скормить языковой модели и попросить разметить спикеров по контексту? Пока оставляю pyannote: таймкоды и SPEAKER_XX дают опору до всякой нейросети, и правится это по времени, без повторного прогона часа аудио. Для диалога двоих иногда хватает Whisper и ручной разметки по первым минутам. Если соберёте свой вариант, киньте ссылку на репо в комменты.
Источники
-
mlx-community/whisper-large-v3-turbo — веса + пакет mlx-whisper (рантайм STT в статье)
-
MLX — фреймворк для Apple Silicon
-
pyannote speaker-diarization-3.1 — диаризация, gated
-
pyannote segmentation-3.0 — зависимость, отдельный Accept
-
pyannote speaker-diarization-community-1 — community pipeline, offline (опционально)
-
Audio Hijack — запись звука приложений на Mac
-
mac-call-transcribe — скрипты из статьи, лицензия MIT
-
BlackHole — бесплатный виртуальный аудиодрайвер для Mac
ссылка на оригинал статьи https://habr.com/ru/articles/1062068/