pimenov.ai

Как мы научили Mac mini читать весь мой сайт моим голосом

Как мы с Codex развернули OmniVoice на Mac mini, клонировали мой голос, проверили 549 аудиофайлов и заменили платный API Яндекса.

ИИКейсCodex

Зачем я вообще полез в локальную генерацию голоса

На моём сайте уже пару месяцев можно слушать статьи и записи блога. Раньше аудио создавал платный API Яндекса. Сервис работал, но каждый новый материал означал новый внешний вызов, новые расходы и зависимость от чужой инфраструктуры.

Недавно я купил Mac mini M4 Pro с 48 ГБ памяти. Я постепенно превращаю его в локальный производственный узел для ИИ-задач. Первой постоянной работой стала генерация аудио для pimenov.ai. Дальше очередь дойдёт до других процессов. Видео уже машет рукой из ближайшего будущего.

Задача звучала просто: поставить OmniVoice, настроить мой голос и заменить старую схему. В реальности пришлось собрать полноценный производственный контур с очередью, проверками качества, аварийным резервом и наблюдением. Вместе с Codex мы прошли весь маршрут от первого образца до массовой замены аудио на сайте.

Что получилось в цифрах

Итоговая кампания дала 549 аудиофайлов OmniVoice. Из них 548 соответствовали актуальным материалам сайта и были опубликованы. Один файл относился к исторической версии материала, поэтому система спокойно оставила его за бортом.

Объём готового звука составил 102 972 секунды, то есть 28,6 часа аудио. Сами MP3-файлы заняли 824 056 641 байт, примерно 824 МБ.

Чистое время работы модели составило 127 320 секунд, или 35,37 часа. Весь проход от первого до последнего результата растянулся на 76,75 часа. Разница объясняется проверками, паузами, исправлениями и двумя приключениями с электричеством. Локальная генерация оказалась марафоном с несколькими вынужденными остановками.

После публикации мы провели полную контрольную проверку: 548 из 548 актуальных материалов получили правильный новый адрес аудиофайла, ошибок было 0. Контрольный файл с сайта отвечал на запрос фрагмента кодом 206 и типом audio/mpeg, как и положено нормальному веб-аудио.

Notion image

Как Mac mini получил первую постоянную должность

На Mac mini мы развернули OmniVoice и создали голосовой профиль sergey-v1. Для сайта выбрали скорость 1.2: речь остаётся естественной, а длинные статьи слушаются бодрее.

Генерация идёт с параметром numSteps=16. На выходе получается монофонический MP3 с битрейтом 64 кбит/с. Громкость приводится примерно к -16 LUFS, пиковый уровень ограничивается около -1.5 dB. Эти настройки дают предсказуемый файл для браузера без скачков от материала к материалу.

Сам Mac не ходит в Directus и не получает ключи от Яндекса. Он видит только защищённую SFTP-очередь. Это важная граница: локальная машина умеет взять задание, сгенерировать звук и вернуть результат. Публикацией занимается VPS.

Процесс выглядит так:

  1. В Directus появляется новый материал или меняется существующий.
  2. Координатор на VPS примерно раз в пять минут сверяет состояние сайта.
  3. Для нужного материала создаётся задание с текстом и контрольными хешами.
  4. Mac mini забирает одно задание и запускает OmniVoice.
  5. Результат проходит распознавание и техническую проверку.
  6. VPS загружает неизменяемую версию MP3, обновляет Directus и пересобирает сайт.
  7. Финальная проверка открывает страницу и сам аудиофайл снаружи.

Mac mini обрабатывает по одному заданию за раз. Это медленнее красивой схемы с десятком параллельных процессов, зато предсказуемо по памяти и устойчиво при длинной очереди. Для домашнего производственного узла такая дисциплина полезнее впечатляющего числа потоков.

Зачем в цепочке появился GigaSTT

Синтезатор может создать технически исправный MP3 и при этом проглотить слово, повторить фрагмент или странно прочитать конструкцию. Размер файла и длительность такой дефект не поймают.

Поэтому после OmniVoice файл отправляется в GigaSTT. Распознанный текст сравнивается с исходным сценарием. Проверяется смысловое совпадение, подозрительные пропуски и заметные расхождения.

У одного распознавателя тоже бывают плохие дни. Во время большой кампании 22 нормальных файла получили тревогу GigaSTT с расхождением выше 30%. Мы добавили второй уровень: whisper.cpp с моделью large-v3-turbo. Whisper подтвердил качество этих файлов, и система приняла их без лишней перегенерации.

Получилась практичная двухступенчатая проверка:

  • GigaSTT быстро пропускает обычные результаты;
  • Whisper включается только для спорных случаев;
  • сомнительный результат остаётся на ручной проверке, если два независимых распознавателя не дают уверенности.

После следующих 30 новых живых генераций мы отдельно посчитаем, сколько пользы даёт GigaSTT, сколько тревог оказываются ложными и какую задержку добавляет второй уровень. Если конструкция окажется слишком тяжёлой, упростим её на основании данных.

Notion image

Где система пыталась проверить нашу нервную систему

Первым заметным сбоем стала гонка при записи файла состояния обработчика. Два обновления претендовали на один временный файл .worker-state.json.tmp, LaunchAgent падал и запускался снова. Мы исправили атомарную запись состояния и проверили, что процесс остаётся живым после перезапуска.

Потом истёк токен. Координатор начал повторять безопасные публикационные проходы и складывать доказательства каждого цикла на диск. Данные сайта он не менял, но каталоги росли быстро. Диск VPS дошёл до 94%, каталог служебных проходов занял около 14 ГБ, а 211 аварийных проходов добавили примерно 9,4 ГБ.

В какой-то момент главным голосом проекта оказался df -h.

Перед очисткой мы проверили 3422 итоговых отчёта: safeMode=true, загрузки в хранилище не было, записей в Directus тоже. Только после этой проверки повторные каталоги удалили, сохранив подтверждающие данные. Затем исправили причину повторных проходов при ошибке Token expired.

Рядом на том же VPS шли сборки другого проекта, и кэш сборки Docker снова подвёл диск к границе 85%. Ограниченная очистка только неиспользуемого кэша вернула 2,431 ГБ, контейнеры и образы остались на месте.

Ещё мы обнаружили, что пустой цикл координатора каждые пять минут сохранял около 8,7 МБ служебных данных. Для цикла без работы это щедрость уровня «оставим весь чемодан ради одного чека». Теперь цикл без работы хранит компактное резюме и не вызывает платный синтез, загрузку, Directus или пересборку.

А потом отключилось электричество. Mac mini вернулся, LaunchAgent поднялся, очередь продолжилась с сохранённого состояния. Сейчас источник питания ещё может моргнуть, поэтому Яндекс сохранён как аварийный резерв, а ИБП уже едет ко мне c Ozon.

Как мы защищали сайт во время массового прохода

Все 549 файлов сначала создавались в режиме предварительной генерации. Они не попадали в Directus, не меняли публичные адреса аудиофайлов и не запускали массовую публикацию. Сайт продолжал обслуживать старое аудио, пока новая коллекция росла отдельно.

После завершения мы сверили каждый артефакт с текущим материалом по идентификатору, хешу текста и версии маршрутизации. Получили 548 точных совпадений, один устаревший исторический вариант, ноль пропусков и ноль потерянных файлов.

Только после этого запустили массовую публикацию. Она обновила 548 материалов, пересобрала сайт и прочитала результат обратно. Старые файлы Яндекса пока сохранены как технический вариант для отката. Активные страницы уже указывают на OmniVoice.

Для новых материалов основной маршрут теперь такой: audioProvider=mac-mini, macMiniMode=active, скорость 1.2. Если контрольный сигнал Mac mini давно не обновлялся или задание вышло за срок, координатор временно использует Яндекс. Поздний результат Mac принимается лишь тогда, когда материал всё ещё актуален.

Критические события уходят в приватный административный Telegram-чат через существующего бота «Инбокс контента». Системные сообщения отделены от входящих материалов, поэтому авария не превращается в черновик публикации. Первое событие приходит сразу, одинаковые повторы объединяются, при восстановлении отправляется отдельное сообщение. Если Telegram недоступен, событие остаётся в очереди и ждёт повторной доставки.

Notion image

Что именно делал Codex

Codex был рабочим партнёром во всём проходе. Мы вместе проектировали очередь, писали код, проверяли хеши и состояния, разбирали журналы VPS и Mac mini, собирали тесты, готовили сценарий отката и проводили внешние проверки сайта.

Каждое исправление проходило через узкий контроль. Финальный набор дал 313 из 313 успешных тестов в полном наборе проверок. Локальная сборка Astro создала 65 страниц. Контрольное задание на публикацию дошло до состояния mac_published, страница вернула 200, аудио корректно ответило на запрос фрагмента файла.

Мне нравится в этой истории конкретный вывод: вместе с Codex можно довести до рабочего контура задачу, которая начинается фразой «давайте клонируем голос», а заканчивается распределённой аудиосистемой с контролем качества, аварийным резервом и наблюдением.

Codex здесь ценен не магией одной команды. Он держит контекст, помогает быстро проверять гипотезы и заставляет каждый красивый замысел встретиться с логами. Логи, как известно, обладают редким даром возвращать романтику в инженерные рамки.

Первый процесс переехал домой

Mac mini с 48 ГБ памяти теперь делает полезную работу для сайта каждый день. Первый платный внешний процесс перенесён в локальную генерацию. Яндекс остаётся страховкой на случай отключения питания или недоступности обработчика, но штатный голос сайта создаёт OmniVoice.

Это только начало заполнения Mac mini важными локальными процессами. Следующий большой рубеж — видео. Там вычислений больше, файлов больше и поводов смотреть на свободное место тоже будет больше. После этого проекта мы хотя бы знаем, кто первым попросит внимания: модель, электричество или df -h.

По теме

Если вы тоже хотите вынести дорогой или медленный ИИ-процесс на собственное железо, можно начать с одного измеримого контура и безопасного аварийного резерва.

Если захотите обсудить, как это применить у себя или в команде — пишите в Telegram @pimenov