Зачем мне это понадобилось

Нет, это не началось с транскрибирования митингов — ими закончилось.

Мне изначально нужен был инструмент, который я запускаю — и дальше просто комментирую вслух всё, что делаю с приложением на экране.

Например, тестирую какое-нибудь приложение и говорю: «Посмотрим, что будет, если я нажму эту кнопку. О, форма. А что если я введу сюда вот это значение? Ха, не сработало, пишет ошибку. Ну хорошо, а что будет, если я выделю вон ту опцию?..»

Дальше я хотел сгружать такие надиктованные сессии условно в час длиной вместе со снимками экрана в LLM и получать на выходе полезный документ или набор документов. А в случае обнаруженных багов — почти готовые JIRA-тикеты: контекст, шаги воспроизведения, ожидаемое и фактическое поведение, скриншот. Вместе с knowledge base по проекту и с исходниками, LLM бы довольно неплохо разобралась бы, что я комментировал, глядя на интерфейс.

Второй сценарий — туториалы. Надиктовать проще, чем написать. Если сказанное само превращается в текст, туториал по какой-нибудь теме получается с минимальными усилиями: я просто показываю и рассказываю, а потом отдаю это на редактуру модели.

Но закончилось это тем, что 9 из 10 применений инструмента — транскрипты митингов и помощь в том, чтобы эффективнее работать на звонке.

Забегая вперёд, скажу, что ещё оказалось очень удобно превращать лекции с YouTube в читабельные статьи со скриншотами и врезками.

Для затравки — вот как выглядит финальный результат: документ, собранный из записи доклада с YouTube. В автоматическом режиме это работает по принципу «включил видео и забыл»: инструмент сам слушает и сам выбирает кадры, а документ собирается по одной кнопке.

Дальше в статье речь в основном про консольную версию: она и есть ядро инструмента. Поверх неё я недавно сделал веб-интерфейс (./webui.sh), и видео и скриншоты ниже сняты уже в нём. Интерфейс запускает тот же main.py и добавляет живой транскрипт, панель скриншотов, выбор источника звука, области съёмки (весь экран, область или конкретное окно) и модели распознавания, автоматические кадры, обработку транскрипта по кнопке Beautify и чат по ещё идущей записи. А теперь тот же интерфейс есть и в виде приложения для macOS, со всем нужным внутри, — о нём в конце.

Beautify и чат работают через Claude Code или через API LLM, на выбор: Gemini, OpenAI, Anthropic, OpenRouter или любой OpenAI-совместимый сервер, включая локальные Ollama и LM Studio. С облачной моделью эта часть, в отличие от распознавания, не локальная.

Вот как это выглядит вживую — запись доклада с YouTube, скриншоты слайдов по хоткею и обработка транскрипта:

Так вот, мы упомянули, что тут не только аудиопоток, но и что-то на экране происходит, и возникает вопрос

Когда снимать экран

И как потом встроить релевантное с экрана в будущий текст? Одна картинка заменяет тысячу слов, и без неё ни баг-репорт, ни туториал не работают.

Изначально у меня была мечта сделать так, чтобы сама система как-то понимала, какой из кадров оставлять, потому что контекстуально он очень кстати в тексте. И я бы запустил youtube-ролик, оставил компьютер его слушать, и вернулся бы к готовому документу, описывающему то, что было в ролике, со скриншотами-кадрами. Долго казалось, что такое пока рано. Оказалось, не совсем: теперь в инструменте есть и автоматический режим, о нём ниже. Но начнём с ручного — он появился первым и остаётся основным.

Писать видео целиком — избыточно: потом как-то всё равно придётся искать нужные кадры, а тут пока непонятно как подступиться. Гораздо лучше, когда человек сам решает, какой кадр ключевой, раз он все равно сидит и слушает. Так появилась идея глобального хоткея: нажал — снимок экрана сохранился, а в транскрипт в этот же момент вставился маркер [screenshot: путь]. Текст и картинка оказываются связаны по времени, и LLM потом точно знает, к какой фразе относится кадр.

Неожиданно: оно слышит и динамики

По ходу я понял, что эта штука через микрофон прекрасно «слышит» то, что «произносит» динамик. Я вообще этого не ожидал. Мне казалось, что микрофон ноута как-то фильтрует то, что раздается из динамика. Но — работает и очень надежно. И сразу появился целый класс применений.

Есть и обратная сторона: через микрофон всё это работает, только пока звук идёт из динамиков. В наушниках или гарнитуре собеседника микрофон не услышит, и в транскрипт попадёт только ваша речь. Поэтому теперь звук звонка можно брать и напрямую, об этом ниже.

Отсюда же следует, что лучше всего он работает дома, в тихой комнате. В open space я его не проверял. Скорее всего, детектор речи будет пропускать в распознавание чужие разговоры. Часть такого мусора LLM уберёт на постобработке, потому что он не вяжется с темой звонка, но рассчитывать на это я бы не стал.

У встроенного транскрипта Teams есть несколько неудобств и удобств:

Teams Мой инструмент
Когда доступен текст Только после завершения звонка Сразу, по ходу разговора
Кто может скачать текст Насколько я понимаю, только тот, кто записывал; остальным — интерактивный просмотр, откуда нормально не скопируешь Обычный .txt у меня на диске
Ключевые кадры Нет — сохраняется всё видео целиком Скриншоты по хоткею, прямо в тексте
Кто что сказал Знает точно Опциональная диаризация по голосу, без имён; имена можно взять из транскрипта самого Teams

Последний пункт — единственное, в чём Teams определённо выигрывает. Но и его теперь можно забрать себе: транскрипт Teams загружается в проект как второй источник, об этом ниже. А тратить час на пересмотр часового звонка жалко. Хочется транскрипт и ключевые кадры.

Звук звонка — напрямую

Слушать динамики через микрофон оказалось удобно, но в наушниках это не работает. Поэтому у инструмента появился выбор источника звука — флаг --source в консоли или список Source в веб-интерфейсе:

Источник Что слушает Что в транскрипте
Microphone (по умолчанию) Микрофон, как раньше Всё вперемешку
App + mic Звук одного приложения, например Teams, и отдельно микрофон Строки с пометками [Them] и [Me]
App only Только звук приложения Всё, что оно играло
Audio file Готовый файл: mp3, m4a, wav, видео Метки времени — позиции в файле

Звук приложения забирает второй нативный хелпер, audio_helper, через Core Audio Process Taps (macOS 14.2+). Teams при этом играет в наушники как обычно, а в транскрипт идёт чистый звук звонка, без комнаты. Микрофон и приложение распознаются как два независимых канала, у каждого свой детектор речи, так что реплики не слипаются, даже когда говорят одновременно. И «кто что сказал» становится наполовину решён сразу: [Me] — это я, разделять по голосам нужно только [Them].

Из подводных камней: Teams играет звук не основным процессом, а вспомогательным ModuleHost, поэтому хелпер группирует процессы по приложению. А macOS спрашивает отдельное разрешение — System Audio Recording.

Режим Audio file — для записи, которую прислали после встречи: она проходит через тот же конвейер, что и живая, только не в темпе реального времени, а так быстро, как успевает распознавание. Скриншотов и кадров в этом режиме, понятно, нет.

Один вечер и только локальные модели

Все эти соображения привели к тому, что буквально за один вечер я сделал удобный для себя инструмент. Ну ок, не я — мы с Claude Code. А потом ещё несколько месяцев время от времени довешивал новые функции и исправлял ошибки — что из этого вышло, и описано дальше.

Важнее другое: распознавание работает только на локальных моделях. Звук ваших звонков и скриншоты вашего экрана никуда не уходят, и это огромный плюс. Постобработка через LLM опциональна, и даже для неё я обычно использую локальные модели.

Из чего состоит продукт:

Всё это крутится в нескольких потоках одновременно, так что текст появляется в файле практически в реальном времени, с метками [ЧЧ:ММ:СС].

Оговорюсь сразу: оценки скорости и качества в статье — мои личные наблюдения по опыту использования. Систематических замеров я не проводил. Исключения — сравнение моделей распознавания и цифры по автоматическим кадрам ниже.

Речь и скриншоты идут по двум независимым конвейерам и встречаются в транскрипте через маркеры; LLM получает оба потока сразу.

Скриншоты с OCR и вставка выделенного текста

У инструмента два глобальных хоткея, которые работают в любом приложении:

Хоткей Что делает Что попадает в транскрипт
Ctrl+Shift+S Сохраняет экран в PNG и запускает OCR; весь распознанный текст ложится в .txt рядом с картинкой [screenshot: путь к PNG]
Ctrl+Shift+W Берёт текст, выделенный в любом окне [selection: сам текст]

OCR-файл дальше работает как справочник при постобработке. Имена людей, бренды, названия классов и файлов на экране написаны правильно, и LLM исправляет по ним то, что распознавание услышало неверно. Это заметно надёжнее, чем распознавать такие вещи по одному только аудио.

Сочетания зашиты в хелпере (screenshot_helper.swift), и переназначить их пока можно только в коде. Знаю, что Ctrl+Shift+W в браузерах на Windows и Linux закрывает окно, так что для портов сочетания придётся выбирать заново, а лучше вынести их в настройки.

Вставка выделенного текста удобна в режиме диктовки. Говоришь вслух «а вот это список продуктов компании», выделяешь список на сайте и жмёшь хоткей. Список оказывается в тексте дословно, без ошибок распознавания.

Ещё пара возможностей:

Автоматические кадры

Нажимать хоткей на каждом слайде утомительно, а на длинном докладе обязательно что-нибудь пропустишь. Поэтому у инструмента появился второй режим — Auto frames. Включается переключателем перед записью, кнопкой Auto во время записи или флагом --auto-frames 1 в консоли.

Идея простая: снимать экран раз в секунду и оставлять только то, что стоит оставить. Гонять модель через каждый кадр расточительно — в презентации 95% соседних кадров одинаковые, и это видно за миллисекунду. Поэтому кадры идут через каскад, от дешёвого к дорогому:

Сохраняется не скриншот, а сцена: кадр плюс интервал, когда он был на экране. Сцены лежат рядом с транскриптом в frames/<сессия>/ и в сам файл транскрипта не пишутся; с речью они сопоставляются по времени. В веб-интерфейсе они показываются прямо в транскрипте, там, где появились, с пометкой «auto».

Здесь я замерял, на одной записи доклада: 11,5 минуты, слайды с вебкой в углу и лазерной указкой.

Что Результат
Кадры → сцены 692 → 10 за 2 секунды, ни один слайд не пропущен
Один и тот же слайд 4 минуты, по которому водят указкой Одна сцена
Вебка, растянутая на весь экран на 2 минуты Пиксели пропустили 2 кадра с лицом, CLIP выбросил оба

Когда видео — только часть окна

Если снимать не весь экран, а окно браузера или Teams, большая часть кадра — неподвижный текст страницы, а видео — один прямоугольник. Тогда смена слайда тонет в странице: тёмный слайд с парой слов меняет меньше 3% окна, и слайды сливаются.

Для этого в области съёмки есть кнопка Detect video. Инструмент несколько секунд смотрит на окно и находит плеер по неожиданному признаку: декодированное видео даже на неподвижном слайде чуть-чуть мерцает, на уровень-два яркости, а нарисованная браузером страница неподвижна до бита. В моих тестах внутри плеера менялись 3–8% пикселей, снаружи — ровно 0%. Если рамка плеера видна, кроп прилипает к ней; если нет (тёмный слайд на тёмной странице), область расширяется до пропорций видео с запасом. Видео на паузе находится по одной рамке.

Кроп рисуется на превью, и его можно двигать и растягивать мышкой. Действует он только на автоматические кадры: обычные скриншоты по-прежнему снимают окно целиком.

Кадров сотни, нужно десять

Даже после каскада сцен остаются десятки, и не все они нужны в документе. Поэтому любую реплику транскрипта, любой скриншот и любой кадр можно скрыть кнопкой с глазом. Скрытая реплика остаётся на месте, но бледнеет; картинка становится серой. В Beautify уходит всё нескрытое, включая автоматические кадры — как скриншоты, на своё время.

Скрывать по одному долго, поэтому рядом с глазом есть вторая кнопка — «эту и похожие». Для реплик она скрывает такие же или почти такие же фразы — удобно для галлюцинаций Whisper и бесконечных «угу». Для картинок — все скриншоты и кадры, похожие на выбранный, по эмбеддингу CLIP: одна и та же говорящая голова в разные моменты давала 0,93 и выше, разные слайды одного доклада — 0,84 и ниже. Ошибочный клик отменяется из уведомления.

Грубый отбор делает кнопка Pick в панели кадров. Она оставляет N кадров, про которые больше всего говорили, а остальные скрывает. Самый сильный сигнал — указательные слова в речи, пока кадр был на экране: «вот здесь», «как видите», «на этой схеме»; дальше идут время на экране и количество речи. Какой кадр важен, решает транскрипт, а не картинка.

И ещё одна перемена по ходу дела: финальный Beautify после записи больше не запускается сам. Сначала он спрашивает, а если ответить «не сейчас», кнопка Beautify остаётся на потом.

Как это устроено внутри

Главный принцип — запись никогда не ждёт распознавания. Звук пишется асинхронно, а вся тяжёлая работа идёт в отдельных потоках, связанных очередями.

Схема конвейера: микрофон, VAD, Whisper, скриншоты с OCR и LLM-постобработка
Поток Что делает Кому отдаёт
Захват звука Микрофон и/или звук приложения от audio_helper: 16 кГц, моно, блоки по 100 мс Очередь сырого звука; в режиме App + mic — своя очередь и свой детектор речи на канал
Детектор речи (VAD) Silero по окнам 32 мс; фраза закрывается после 1,5 с тишины (--pause), обрывки короче 250 мс выбрасываются Очередь на распознавание: звук фразы + время её начала
Распознавание faster-whisper (beam search 5), GigaAM или Parakeet, по одной фразе Транскрипт на диске, запись сразу после каждой фразы
Хелпер на Swift + читатель его вывода Хоткеи, PNG, выделенный текст Очередь маркеров и очередь OCR
OCR EasyOCR по каждому скриншоту .txt рядом с PNG
Эмбеддинги голоса (с --speakers) ECAPA-TDNN по коротким фразам Данные сессии для кластеризации в конце
Разбор кадров (с --auto-frames) Сравнение пикселей, потом CLIP по выжившим frames/<сессия>/scenes.json и JPEG сцен

Распознавание отстаёт, но догоняет. Whisper получает фразу только после паузы, и на CPU ему нужно время. Фразы копятся в очереди, а на каждой паузе в разговоре распознавание догоняет. В типичной записи пауз хватает, поэтому текст появляется практически в реальном времени. Долгий монолог без пауз уйдёт в распознавание одним куском и появится позже — по ощущениям, секунд через 30. Всё это про Whisper: GigaAM справляется с фразой за доли секунды, и очередь практически не копится. А в веб-интерфейсе на случай долгого монолога есть кнопка Cut: она закрывает текущую фразу сразу и отправляет её в распознавание, не дожидаясь паузы.

Время — по звуку, а не по часам. Метка [ЧЧ:ММ:СС] считается по номеру сэмпла, с которого началась фраза. Даже если распознавание отстало, метка показывает, когда фразу сказали.

Скриншот встаёт туда, куда распознавание дойдёт, а не туда, где оно было. Маркер [screenshot: …] ждёт в своей очереди, и в файл его выписывает тот же поток распознавания, между фразами. То есть маркер ложится не в место, распознанное на момент нажатия хоткея, а в место, до которого очередь дойдёт через пару секунд. Иначе скриншоты вставлялись бы слишком рано: в момент нажатия текущая фраза обычно ещё не распознана, а то и не договорена.

Протокол с хелпером — строки в stdout. Хелпер пишет SCREENSHOT:<путь> или SELECTION:<текст в base64>, Python читает их в отдельном потоке. В обратную сторону, через stdin, идут команды: SNAP — снять скриншот, AUTO 1 — включить автоматические кадры (JPEG раз в секунду, в ответ FRAME:<путь>), AUTO OFF — выключить. Выделенный текст хелпер забирает, имитируя Cmd+C и читая буфер обмена через 150 мс, поэтому буфер при этом перезаписывается. В консольном режиме по умолчанию скриншот снимается с основного монитора; в веб-интерфейсе можно выбрать область или отдельное окно, причём окно снимается, даже если его что-то перекрывает. У audio_helper протокол ещё проще: сырой float32 16 кГц в stdout, в темпе настенных часов. Когда приложение молчит, идут нули, так что число сэмплов всегда совпадает с прошедшим временем, и метки двух каналов не разъезжаются.

Язык — по доле кириллицы. До первой проверки язык каждой фразы определяет сам Whisper. Потом берутся последние 300 слов: от 80% кириллицы — русский, до 20% — английский, в промежутке язык не меняется. С GigaAM язык всегда русский: других он не понимает.

Спикеры — в конце сессии. Все движки распознавания отдают пословные таймстемпы. Слова режутся на фразы по паузам больше 0,4 с или каждые 3 с, короткие дополняются звуком до 1 с, и для каждой по ходу считается эмбеддинг голоса. Кластеризация идёт один раз, после остановки. В режиме App + mic реплики из микрофона сразу помечаются как [Me] и в кластеризацию не попадают. Исходный текст остаётся рядом как .raw.txt, данные сессии — в .pkl для rediarize.py.

Ctrl+C не обрывает запись, а завершает её. Система перестаёт слушать и отправляет недоговорённую фразу в распознавание. Потом дорабатывает всё, что накопилось в очередях: распознаёт оставшиеся фразы, дописывает маркеры, заканчивает OCR, сохраняет MP3 и размечает спикеров. Так же ведёт себя автоостановка по таймеру.

От сырого транскрипта к документу

Сырой транскрипт со скриншотами или с их OCR-текстами дальше можно превратить через LLM в аккуратный текст или HTML — интерактивный или обычный.

В репозитории для этого есть скрипт process_transcript.py. Он собирает транскрипты за день, диапазон дат или интервал времени, подкладывает к ним OCR скриншотов как справочный материал и просит модель:

  1. Исправить ошибки распознавания, сверяясь с текстом с экрана.
  2. Сопоставить обезличенных «Speaker 1», «Speaker 2» с реальными именами, если они видны на скриншотах или звучали в аудио в контексте. То есть, если Вася попросил Петю прокомментировать, и дальше комментирует Петя, то система знает, что Speaker 12 - это Петя. А если Петя поблагодарил Васю, то и то, что Speaker 7, который просил, — Вася.
  3. Собрать документ: ключевые решения, таблица задач и очищенный диалог с таймстемпами.
  4. Сделать из этого одностраничный HTML с вкладками, поиском по тексту и фильтром по спикерам.

Документ, который получился из записи в видео выше, можно открыть целиком. Это версия 2: запись в этот момент ещё шла, и модель отметила в callouts, что нового появилось по сравнению с первой версией. Сборка этой версии по девяти минутам записи и шести слайдам заняла 1 мин 42 с.

Обработанный транскрипт доклада: участники с реальными именами, легенда callouts, реплики по спикерам со встроенными слайдами; справа — скриншоты сессии со статусами OCR и обработки

Для нечувствительных данных я часто просто отдаю папку проекта Claude Code. Прошу сохранить все реплики и добавить callouts с важными моментами — получается очень удобно. Для чувствительных данных использую локальные модели.

Callouts в обработанном транскрипте: NEW — новое по сравнению с прошлой версией документа, CONFIRMS — подтверждение уже известного, с пометкой «Prior view»

Отдельно скажу: транскрипты после обработки LLM реально комфортно читать. Модель убирает шум и посторонние реплики и превращает текст в достаточно близкий к тому, что попадало в микрофон.

Точным он при этом не становится: галлюцинации никто не отменял. Тем не менее серьёзных ошибок я пока не замечал, и модели вполне справляются.

Транскрипт Teams как второй источник

У транскрипта Teams и моего сильные стороны зеркальные. Teams точно знает, кто говорит, но коверкает бренды и термины. У меня наоборот: термины правильные благодаря OCR и заметкам проекта, а спикеры безымянные.

Поэтому транскрипт Teams (.vtt, .docx, .txt или .srt) теперь можно загрузить во вкладку Notes, и Beautify сводит два источника: спикеров берёт из Teams, термины и время — из моего, а то, что моё распознавание пропустило, восстанавливает по Teams. Чат Ask про этот файл тоже знает.

Если транскрипта Teams нет, имена можно расставить вручную. Выделяешь фразу в транскрипте и выбираешь «Who said it»: в заметки проекта ложится строка - "цитата" <-- Имя (at ЧЧ:ММ:СС). Beautify отдаёт эту реплику названному человеку и по ней же узнаёт тот же голос в остальных местах.

Транскрипт + база знаний: подсказки по ходу разговора

Эту штуку очень удобно совмещать с базой знаний по теме разговора. Транскрипт пишется в файл по ходу, поэтому его можно обрабатывать в любой момент, неполным. Отдаёшь LLM то, что уже сказано, вместе с контекстом и спрашиваешь: какие вопросы мы ещё забыли задать, хотя для них был или есть повод?

Слева — сырой транскрипт с метками времени, спикерами и маркерами скриншотов; справа — вкладка Ask: сводка по ещё идущей записи со ссылками на таймстемпы

Простейший пример — собеседование, на стороне интервьюера. Загружаешь резюме кандидата, и система по ходу разговора подсказывает темы для обсуждения. При этом она учитывает оба источника сразу:

То же работает для сбора требований (база знаний — документация проекта), продажных звонков (история клиента) и тестирования (спецификация — какие сценарии ещё не проверены).

О согласии на запись

Отдельно про этику и право. Записывать звонки, а тем более собеседования, без ведома собеседника нельзя: во многих странах это прямо запрещено законом, а в компаниях обычно есть свои правила на этот счёт. Инструмент задуман для личного использования: собственные диктовки, публичные видео, звонки, участники которых знают о записи и согласны на неё. Если записываете других людей, предупредите их заранее. В отличие от Teams, инструмент никого не уведомляет о записи сам, так что ответственность на том, кто её запускает.

Галлюцинации Whisper

У Whisper есть известная болезнь: на тишине, шуме или музыке он не молчит, а выдумывает текст. Модель учили на субтитрах из интернета, и вылезает именно то, что там часто стояло в конце ролика.

В целом, такое чинится заменой модели, и теперь инструмент это умеет (см. следующий раздел). GigaAM и Parakeet устроены иначе: они не сочиняют текст свободно, как Whisper, а сопоставляют его со звуком кадр за кадром, так что «Продолжение следует…» им взять неоткуда. В моей проверке GigaAM на тишине не выдал вообще ничего.

Часть проблемы снимает архитектура. Silero VAD пускает в Whisper только участки с речью, а обрывки короче 250 мс отбрасывает. Каждая фраза распознаётся отдельно, без предыдущего текста, так что одна галлюцинация не тянет за собой следующие. Остальное чистится на постобработке: LLM легко узнаёт «Субтитры сделал…» посреди рабочего звонка. Для важных записей стоит включать --save-audio, чтобы спорное место можно было переслушать по таймстемпу.

Не только Whisper: GigaAM и Parakeet

Теперь инструмент умеет работать и с другими моделями распознавания. Флаг --engine или список Engine в веб-интерфейсе выбирает одну из трёх:

Движок Модель Языки Когда брать
whisper (по умолчанию) faster-whisper large-v3-turbo Любые Для всего; единственный принимает подсказки терминов
gigaam GigaAM v3 от Сбера Только русский Русские звонки и диктовка
parakeet NVIDIA Parakeet TDT 0.6B v3 25 европейских языков, включая русский и английский Речь, где русский смешан с английским

GigaAM и Parakeet работают через onnx-asr на ONNX Runtime, без PyTorch. При первом запуске модель скачивается: около 250 МБ для GigaAM и 700 МБ для Parakeet.

Тут я как раз замерил. 140 секунд русской речи, которые тот же детектор речи порезал на 7 фраз, MacBook с M3 Max, только процессор. Для этого в репозитории есть скрипт bench_asr.py, так что замер можно повторить на своих записях.

Движок Время распознавания Память
whisper 90 с 2,3 ГБ
gigaam 4,4 с 0,7 ГБ
parakeet 4,8 с 1,6 ГБ

GigaAM примерно в 20 раз быстрее Whisper, а на русском не хуже, местами лучше: написал «рулоны бумаги» там, где Whisper услышал «роллы на бумаге», и ставит «ё» где положено. Parakeet такой же быстрый, но на русском заметно слабее: теряет слова и путает окончания. Он имеет смысл для звонков, где русский перемешан с английским.

Есть и минусы:

Что пока не решено

Одну задачу, которая долго оставалась открытой, для Whisper удалось закрыть: как дать модели контекст уже на этапе распознавания аудио. Те самые бренды, имена, термины — чтобы Whisper, скажем, не распознавал Hybris как Hybrid. Теперь правильные написания из заметок проекта уходят в Whisper подсказками (в консоли — флаг --vocab-file), и такие ошибки исчезают уже в сыром транскрипте. У GigaAM и Parakeet подсказок нет, им остаются замены после распознавания (--vocab-correct) и постобработка по OCR скриншотов.

Главное, что пока не решено, — скорость обработки. Beautify сейчас отдельный и довольно медленный процесс: для часовой записи он может занять минут пять. А часто хочется видеть саммари встречи прямо на лету, причём такое, которое переписывается по мере того, как добавляется контекст: то, о чём говорили в начале, переосмысляется с учётом того, что выяснилось в середине. Сейчас всё это работает, но только после Beautify, то есть с заметной задержкой. Если у вас есть опыт с этим — буду рад идеям и pull request’ам.

Второй открытый вопрос — выбор кадров. Pick сейчас опирается на то, что и сколько говорили; слайд, на который никто не показал, проиграет, даже если он самый важный. Следующие на очереди — OCR каждой сцены и короткое описание от локальной визуальной модели (Qwen-VL или Moondream через Ollama): тогда слова со слайда можно будет сверять с речью, а окончательный выбор кадров отдать текстовой LLM вместе с транскриптом.

Где взять

Инструмент бесплатный и лежит на GitHub: github.com/raliev/audiotranscripts.

Самый простой путь — приложение для macOS. Это тот же веб-интерфейс в своём окне, а внутри Python со всеми зависимостями, оба хелпера и ffmpeg, так что ставить ничего не нужно. Готовые DMG для Apple Silicon и для Intel лежат в релизах на GitHub, нужна macOS 14+. Моделей внутри нет: каждая скачивается при первом использовании, с прогрессом на экране, а дальше всё работает офлайн. Собрать DMG самому можно одним скриптом, packaging/build_app.sh, в том числе сразу со всеми моделями. При первом запуске приложение спрашивает, через что делать Beautify и чат: Claude Code или API с ключом. Подписи Developer ID у меня нет, поэтому на другом маке первый запуск — через правый клик → Open.

Для консольной версии нужны macOS, Python 3.10+ и, по желанию, ffmpeg для MP3. Система попросит разрешения: Screen Recording для скриншотов, Accessibility для захвата выделенного текста и, если слушать приложение напрямую, System Audio Recording.

pip install -r requirements.txt

swiftc -O -o screenshot_helper screenshot_helper.swift \
    -framework Cocoa -framework Carbon -framework CoreGraphics

# проект myproject, автоостановка через 60 минут
python main.py myproject 60

# звонок на двоих, со звуком в MP3
python main.py teams-sync 60 --speakers 2 --save-audio

# русский звонок на GigaAM: намного быстрее и легче Whisper
python main.py teams-sync 60 --engine gigaam --speakers 2

# звонок в наушниках: звук Teams и микрофон отдельно, строки [Them] и [Me]
./build_audio_helper.sh
./audio_helper --list
python main.py teams-sync 60 --source app+mic:com.microsoft.teams2

# готовая запись вместо живого звука
python main.py lecture 60 --source file:lecture.m4a

# доклад с автоматическими кадрами (скриншот раз в секунду, остаются только новые сцены)
python main.py talk 60 --auto-frames 1

# подобрать пороги кадров на скачанном видео, без записи
python autoframes.py talk.mp4 --out /tmp/frames

# постобработка за день
python process_transcript.py myproject 2026-10-01

# или веб-интерфейс на http://127.0.0.1:8765
./webui.sh

Во время записи работают хоткеи Ctrl+Shift+S и Ctrl+Shift+W, остановка — Ctrl+C. После него система ещё несколько секунд дорабатывает очередь, так что не закрывайте терминал сразу.

Windows и Linux: нужны волонтёры

Сейчас инструмент работает на полную только на macOS. Привязаны к macOS лишь хелперы на Swift: глобальные хоткеи через Carbon, снимок экрана через CoreGraphics, выделенный текст через имитацию Cmd+C, звук приложений через Core Audio Process Taps. Всё остальное — Python, и faster-whisper, onnx-asr, Silero VAD, EasyOCR, SpeechBrain и sounddevice кроссплатформенные и должны работать везде.

По сути, порт сводится к одному: написать хелпер под свою ОС, который говорит по тому же протоколу. Он принимает папку для скриншотов аргументом и печатает в stdout строки SCREENSHOT:<путь> и SELECTION:<текст в base64>. Python-часть почти не меняется: нужно только выбирать бинарник хелпера по платформе.

Windows Linux
Работает сейчас Нет Нет
Глобальные хоткеи RegisterHotKey (Win32), или pynput / keyboard на Python X11: XGrabKey или pynput; Wayland: глобальные хоткеи приложениям запрещены — через портал GlobalShortcuts или хоткей в настройках окружения, который вызывает команду
Скриншот mss, или Windows.Graphics.Capture X11: mss; Wayland: портал Screenshot (xdg-desktop-portal), grim на wlroots
Выделенный текст Имитация Ctrl+C и чтение буфера обмена X11: PRIMARY selection через xclip / xsel, без копирования; Wayland: wl-paste --primary
Звук с динамиков WASAPI loopback — можно писать звук напрямую, без микрофона Monitor-источник PulseAudio / PipeWire, тоже напрямую
Сложность Средняя X11 — простая, Wayland — заметно сложнее

Звук звонка напрямую на маке теперь берётся через Process Taps, а на Windows и Linux для этого есть штатные механизмы из таблицы. Так что режим App + mic переносится туда тем же способом: нужен ещё один хелпер, который отдаёт сырой звук в stdout.

Если вы готовы сделать хелпер под Windows или Linux — давайте организуемся, вот репозиторий или пишите мне. Буду рад.

Пользуйтесь на здоровье.