Расшифровка созвонов, которая не уходит наружу
Задача
День из созвонов, а к вечеру от них не остаётся ничего, кроме памяти и пары строк в блокноте. Через неделю не вспомнить, о чём договорились и почему решили именно так.
Сервисов, которые пишут встречу и присылают саммари, сейчас десятки. Ни один не подошёл по одной причине: это рабочие встречи. Там чужие голоса, внутренние названия систем, цифры, которые не должны уезжать на чужие серверы. Согласия собеседников на отправку их речи стороннему сервису у меня нет и не будет.
Значит, всё должно считаться дома. Задача: запись, расшифровка и саммари, не покидающие мой контур, с готовой заметкой в Obsidian на выходе.
Как устроено
Три части. На маке – приложение, которое пишет встречу и панель управления
на 127.0.0.1. На домашнем сервере – распознавание и суммаризация. Между ними –
очередь.
Запись идёт двумя дорожками сразу: микрофон – это я, звук приложения звонка – это собеседники. Побочный эффект оказался важнее основного: разделение реплик на «я» и «остальные» получается бесплатно, без всякой диаризации, просто потому что это два разных файла.
Звук собеседников снимается тапом на процесс звонка. Это принципиальный момент. Предыдущая схема была классической – виртуальное аудиоустройство и multi-output, чтобы звук шёл одновременно в наушники и на запись. Ломалась она ровно там, где больнее всего: при bluetooth-наушниках звук у собеседников пропадал совсем. Тап на процесс ничего не трогает в настройках пользователя: не создаёт устройств, не переключает выходы, не вмешивается в маршрутизацию.
Календарь читается через системный API, поэтому в панели видно ближайшую встречу с участниками и типом. Начать запись можно тремя способами: из карточки встречи, руками для внепланового разговора и просто из строки меню, не отвечая ни на один вопрос, – название и участников можно проставить уже во время записи или потом.
Дальше всё едет само: сведение, отправка на сервер, распознавание локальным whisper, саммари локальной моделью, готовая заметка обратно в vault. Тип встречи определяет папку: один на один, performance review или обычная рабочая встреча.
Из офиса домашний сервер недоступен – и это нормально: записи копятся в очереди и уезжают, когда появится домашняя сеть. Полчаса встречи – это около 350 МБ сырых дорожек и 12 МБ сжатого файла, который и уходит на обработку. Сырое удаляется, как только заметка забрана, сжатое живёт ещё две недели на случай, если захочется прогнать заново.
Что оказалось сложнее распознавания
Само распознавание было самой простой частью. Интересное началось вокруг него.
Нормализация ломает распознавание. Дорожки надо усиливать: люди говорят тихо, микрофоны разные. Первая версия честно нормализовала по среднему уровню – и стало хуже. Причина в том, что на встрече больше половины времени тишина, средний уровень из-за неё занижен, и «нормализация» добавляла десять децибел дорожке, у которой пики и так стояли у нуля. Срезанные вершины whisper разбирает плохо, а иногда на них просто зацикливается.
Модель знает, когда ей плохо – надо спросить. Whisper сообщает по каждому куску две служебные метрики: насколько сжался текст и насколько он в нём уверен. Кусок, где сжатие выше 2,4 – верный признак, что одна фраза повторена десятки раз, – или уверенность ниже минус единицы отправляется на переспрос отдельно. Границы окон при повторе другие, и обычно со второго раза выходит нормально. Если нет – кусок выбрасывается: живой речи в нём всё равно не осталось.
Подсказка помогает и мешает одновременно. Список своих терминов – названия систем, продуктов, отделов – резко улучшает результат: без него whisper уверенно превращает «Cloud.ru» в «Клаудру». Но на тихих и рваных местах та же подсказка тянет модель в сторону от того, что реально сказано. На одной встрече первые две минуты, где между короткими репликами цифровая тишина, whisper с подсказкой выдал «Продолжение следует…» и потерял всю речь, а без подсказки разобрал её целиком, пусть и без запятых. Теперь места, где детектор речи слышит голос, а слов не появилось, переспрашиваются вторым проходом без подсказки. На той же встрече это вернуло 121 секунду разговора и 275 слов из 898 – включая полностью потерянный кусок про то, почему поиск в новом сервисе на порядок медленнее старого.
Отдельно отсекается типовой мусор, который whisper выдаёт на тишине: «Субтитры сделал…», «Продолжение следует», одинокое «и т.д.».
Кто говорил
Число голосов определяется по самой записи, а не по списку приглашённых. Список из календаря – подсказка и верхняя граница, не более: на встречу приходят не все, приводят незаявленных, а на спринт-ревью из тридцати приглашённых говорят четверо.
Имена система не угадывает. Реплики подписаны номерами, а имена проставляются руками в панели, где видно, сколько минут говорил каждый голос. Это сознательное решение и, пожалуй, главное в проекте: угаданное имя выглядит ровно так же, как распознанное, и ошибку потом не замечает никто. Лучше номер, к которому вы точно знаете, что его надо уточнить.
Один раз подписанный голос запоминается и на следующих встречах узнаётся сам. Узнанные автоматически имена помечены отдельно – чтобы отличать их от проставленных руками.
Известное ограничение честно записано в документации: если система узнала неверно и имя поправили, прежний отпечаток остаётся с примесью чужого голоса. Лечится только удалением записи из базы голосов.
Выводы
Локально – это давно не компромисс по качеству. Пару лет назад выбор между своим и облачным распознаванием был выбором между «плохо, но у себя» и «хорошо, но у них». Сейчас на домашней машине получается результат, который меня полностью устраивает.
Не угадывайте то, что пользователь примет за факт. Система, которая уверенно подставила неверное имя, вреднее системы, которая оставила номер. Первую перестают перепроверять – и ошибка живёт в заметках вечно.
Здравые «улучшения» сигнала часто вредят. Нормализация громкости – каноническая правильная операция, которая в этой задаче сделала хуже, потому что статистика входных данных не та, для которой она придумана.
Спрашивайте у модели, как она себя чувствует. Метрики уверенности и сжатия превращают «иногда мусор на выходе» в «знаем, какие именно куски плохи, и переспрашиваем только их». Это дешевле и точнее, чем гонять всё дважды.