У нас было несколько обучающих видео по личному кабинету. После большого редизайна большая часть стала неактуальной: кнопки переехали, разделы переименовали, один экран вообще уехал в другое меню.
Чтобы обновить одно видео, надо открыть OBS, поднять чистый профиль браузера — иначе в кадр лезут закладки и всплывашки. Потом записать дубль, в котором ты ни разу не промахнулся мышкой. Потом порезать в редакторе, наложить подписи. На семь видео уходит день. Через два месяца — опять день.
Предсказуемо, видео перестали обновлять.
Тогда я написал штуку, которая собирает ролик из текстового файла. Скрипт открывает сайт в headless‑хроме, водит нарисованным курсором, кликает, печатает, зумит куда надо, показывает подписи, читает их вслух синтезированным голосом, подмешивает звуки мыши и клавиш поверх музыки и выдаёт mp4.
Сценарий выглядит так:
{ "meta": { "url": "https://ru.wikipedia.org", "voice": { "name": "ru-RU-SvetlanaNeural" } }, "steps": [ { "type": "title", "text": "Как найти статью" }, { "type": "caption", "text": "Впишите запрос", "read": 2500 }, { "type": "click", "target": "input[name=\"search\"]" }, { "type": "type", "target": "input[name=\"search\"]", "text": "Кот" }, { "type": "key", "key": "Enter" }, { "type": "zoom", "target": "h1", "scale": 1.4 } ]}
npm run record scenarios/my.json — и на выходе видео. Изменился интерфейс — запустил ещё раз. Дубля нет, монтажа нет.
Гифка выше сделана самим инструментом из сценария, который лежит в репозитории.
Что оказалось сложнее, чем я думал
Playwright умеет писать видео сам, но плохо
У него есть recordVideo, и первую версию я собрал на нём. Выглядело дёшево. Он упирается примерно в 25 fps и на длинных записях начинает терять кадры. Плавное движение курсора превращается в дёрганье.
Пришлось брать кадры напрямую через CDP — Page.startScreencast. Он отдаёт каждый кадр композитора с точным таймстампом, до 60 fps. Кадры складываются jpeg‑ами во временную папку, потом собирается ffconcat‑список, где у каждого кадра своя длительность, а ffmpeg фильтром fps выравнивает это в ровный поток.
Отдельная засада: даже в headless нужна GPU‑растеризация. Без флагов --enable-gpu --enable-gpu-rasterization растр на 1080p проседает до ~40 кадров в секунду там, где анимация полноэкранная — на зуме и титрах. Где GPU нет, флаги просто игнорируются, так что вреда от них тоже нет.
Курсор и зум — это DOM, а не композитинг
Курсор, камера зума, подписи, титульные карточки нарисованы обычными элементами и инжектятся скриптом прямо в страницу. Поэтому нет отдельного слоя, который надо накладывать на видео, и нет зависимости от оконного менеджера: что в странице, то и в кадре.
Зачем это в CI
Собственно, ради этого всё и затевалось. Если запись — одна команда, её можно повесить на тег релиза, и видео физически не может отстать от интерфейса. Не «мы стараемся обновлять», а не может.
Для этого нужен вход без человека, то есть токен в localStorage из секрета репозитория. Папку voice/ имеет смысл закэшировать, чтобы не синтезировать одно и то же на каждом прогоне. И заложить время: в раннере GPU нет, растр софтверный, запись идёт заметно медленнее, чем на ноутбуке.
Чего оно не умеет
Синтез речи ходит в ту же неофициальную ручку, что и «Прочитать вслух» в браузере Edge. Бесплатно, без ключей и регистрации, но неофициально. Есть ретраи, есть кэш, но риск честный.
Селекторы ломаются ровно так же, как в e2e‑тестах. Переименовали класс — сценарий надо чинить. Это цена того, что демка живёт в репозитории рядом с кодом, и меня она устраивает.
Монтажа нет. Что записалось, то и в ролике: склеить два дубля или вырезать неудачный кусок нельзя.
И drag‑n-drop, канвас и хитрые ховер‑меню — не поддерживаются. Инструмент для линейных туториалов «нажми сюда, впиши это, посмотри результат».
Три рантайм‑зависимости (playwright, ffmpeg‑static, node‑edge‑tts), около тысячи строк, MIT. Работает на macOS, Windows и Linux.
ссылка на оригинал статьи https://habr.com/ru/articles/1066964/