agentic-screencast
v2.0.0
Published
A video-building CLI for AI agents: declarative scenarios, narration, reproducible frames and MP4 output.
Downloads
987
Maintainers
Readme
Agentic Screencast — инструмент агента для сборки видео
English | Русский
Лицензия: GPL-3.0-or-later (см. LICENSE). Так выбрано потому, что
ffmpeg-static поставляется зависимостью и распространяется под GPL-3.0;
лицензия слабее создала бы расхождение между файлом лицензии и тем, что
пользователь получает при установке.
Агент изучает продукт, прототип, идею или исследование, пишет сценарий и собирает видеопрезентацию для человека. Agentic Screencast выполняет сборку: создаёт слайды, синтезирует речь или берёт записи, рассчитывает кадры и склеивает MP4. Подходит для объяснения продукта, обзора функции, объяснения идеи и презентации с озвучкой. За факты, текст и проверку результата отвечает агент; входную схему и отчёты сборки он получает в машинно читаемом виде.
Готовые страницы рендерятся в назначенные моменты, а живые действия Playwright записываются в WebM вместе с курсором и откликом клика. Неизменные сцены сборки берутся из кэша.
Поручить агенту
Достаточно назвать продукт или функцию: агент найдёт доступные источники,
определит сюжет и спросит только о недостающем доступе или существенном выборе.
Начать можно со скилла,
готового примера и точки входа агента.
Сначала соберите черновик движком stub:
он создаёт тишину и не обращается к платному сервису.
Работайте через скилл: в нём путь от просьбы до проверенного ролика, бриф, который агент собирает, и база знаний, которую он читает. Claude Code и Codex ставят скиллы сами, поэтому просто попросите агента, например: «Поставь скилл Agentic Screencast из skills/agentic-screencast этого репозитория и сними по нему минутный ролик о нашем экспорте».
Универсальное ядро живёт в src/. Сценарии, снимки, записи и команды конкретного
ролика принадлежат внешнему проекту-потребителю; этот репозиторий их не хранит.
Запись настоящих действий
Модуль agentic-screencast/capture снимает настоящие действия в браузере:
recordTake открывает новую страницу Playwright, capturePage пишет уже
открытую. Методы, опции и масштаб записи — agentic-screencast help capture.
import { recordTake } from "agentic-screencast/capture";
await recordTake({
output: "captures/run.webm",
prepare: async (page) => { await page.goto("https://your-app.example/run"); },
}, async (take) => {
await take.click(take.page.getByRole("tab", { name: "Graph" }));
await take.withFocusCard(take.page.getByRole("slider"),
{ title: "Перематываем историю", body: "Экран показывает другое состояние запуска.",
reveal: "type", motion: "glide" },
async () => { await take.range(take.page.getByRole("slider"), 0.75); });
});Готовый WebM укажите в file: видеосцены и соберите MP4 обычной командой.
Запускаемый пример работает без внешнего сайта.
Краткая справка: agentic-screencast help capture.
Рядом с WebM съёмка пишет <дубль>.marks.json: именованные моменты, прямоугольники
отмеченных элементов и cameraMoves — интервалы уже снятого приближения и возврата
камеры. Сценарий ссылается на момент или элемент через @имя; lint учитывает
движение камеры, когда ищет необъяснённую смену экрана внутри дубля.
Файл отметок называет и скрипт, записавший дубль. Камеру над таким дублем в
видеосцене исполняет браузер: сборка заново запускает этот скрипт с камерой сцены,
привязанной к отметкам дубля, и берёт пересъёмку <дубль>.<сцена>.cam.webm, поэтому
мелкий текст в наезде остаётся чётким; неизменная камера дубль не переснимает.
Условия и запасной путь — в help capture.
Переменные окружения
| Переменная | Что задаёт | Умолчание |
|---|---|---|
| AGENTIC_SCREENCAST_HOME | каталог данных: кэш звука и кадров, записи голоса, вывод по умолчанию | ./.agentic-screencast |
| FFMPEG | путь к ffmpeg, если не устраивает поставляемый зависимостью | из node_modules |
| CLOUD_KEY | ключ Yandex SpeechKit; читается из окружения или из .env | — |
| AGENTIC_SCREENCAST_LANG | язык, на котором инструмент говорит с человеком | из LANG, иначе en |
| LANG | системный язык; берётся, если AGENTIC_SCREENCAST_LANG не задан | — |
| AGENTIC_SCREENCAST_FILM_LANG | вариант сценария на другом языке; его ставит --lang ru у команды | язык шапки сценария |
| AGENTIC_SCREENCAST_FILM_FORMAT | формат сборки горизонтального сценария с кадрированием; его ставит --format vertical у команды | формат шапки сценария |
| AGENTIC_SCREENCAST_NO_LIVE_CAMERA | 1 — камера над живым дублем растягивает видео, а не переснимает дубль с наездом браузером | наезд исполняет браузер |
| AGENTIC_SCREENCAST_TAKE_CAMERA | ставит сборка, а не человек: план камеры, который перезапущенный скрипт дубля исполняет в браузере (какой дубль, куда писать, движения с привязкой к отметкам) | не задана: скрипт снимает как обычно |
| NODE_TEST_CONTEXT | ставит node --test; дубль, снятый под ним, не записывает свой скрипт, чтобы сборка не перезапускала файл теста | не задана |
| AGENTIC_SCREENCAST_BARE | 1 — сборка без слоя поверх материала (подсветки, подписей, карточек): так проверка сравнивает кадрирование самого материала | слой рисуется |
| AGENTIC_SCREENCAST_JOBS | сколько сцен рисуется одновременно; каждая — своим браузером от первого кадра до последнего, но при общей нагрузке растеризатор Chromium может округлить точку иначе (PSNR не ниже 56 дБ против сборки по одной), поэтому побайтовая пересборка — с 1; поле отчёта timing показывает, где ушло время | треть ядер, не больше четырёх |
| AGENTIC_SCREENCAST_DEBUG | 1 — отказ сборки печатается со стеком Node; без неё — одной строкой build failed: …, которая называет сцену и причину | одна строка |
| AGENTIC_SCREENCAST_EMOJI_SET | каталог набора картинок эмодзи вместо поставляемого assets/emoji (Noto Emoji); пустой каталог заставляет сборку отказать на первом эмодзи | assets/emoji пакета |
Имя переменной ключа (CLOUD_KEY) переопределяется данными: key_env у голоса. Съёмка
снимков своих переменных не имеет вовсе: имена называет настройка
потребителя строками вида ${ИМЯ}. Своих переменных
у внешнего движка голоса может быть сколько угодно — он их и объявляет;
инструмент о них не знает.
Условие воспроизводимости кадра
Побайтовое совпадение кадров гарантируется на закреплённой версии
браузера: путь к бинарю Chromium входит в ключ сборки. Обновление
playwright законно разведёт ключи всех сцен: условия воспроизводимости
изменились. Версии закреплены и манифестом, и файлом блокировки
(package-lock.json), поэтому установка воспроизводима; расхождение
возможно только при осознанном обновлении playwright.
Установка
Пользователю инструмента хватает глобальной установки из npm (npm install
--global agentic-screencast, затем npx playwright install chromium) —
так, как описано в README.md. Этот раздел — для работы из
исходников: разработки инструмента или запуска чекаута без установки.
Нужен Node.js 20+. Питона на рабочем пути нет: синтез делает движок голоса, а поставляемые движки работают на узле. Chromium устанавливается отдельной командой Playwright; в Linux ему могут понадобиться системные пакеты.
Исходники — TypeScript в src/, запускается собранное в dist/. Собирать
руками не нужно: сборка навешена на prepare, то есть выполняется при
npm install в каталоге пакета и при установке из git-адреса.
# 1. зависимости узла и браузер; сборка запускается сама
npm install # ставит зависимости и собирает dist/
npx playwright install chromium # ~180 МБ
# 2. сказать инструменту, где держать кэш и вывод
export AGENTIC_SCREENCAST_HOME="$PWD/.agentic-screencast"
# 3. команда agentic-screencast в PATH — для агентов и оболочки
npm linkБез npm link у чекаута нет команды agentic-screencast, и агент, идущий по
скиллу, её не найдёт; тот же CLI запускается как node dist/agentic-screencast.js.
Локальный бесплатный синтез (Silero) и шлюз разборчивости живут
отдельно — они держатся за torch, а это окружение примерно на 600 МБ.
Ставить их нужно только тому, кому они нужны: external/README.md.
Запуск
Три способа, все равноправные:
node /путь/к/agentic-screencast/dist/agentic-screencast.js build --source story.md
npx agentic-screencast build --source story.md
agentic-screencast build --source story.mdПраво на исполнение у собранной точки входа ставит сборка, поэтому символической ссылке не нужно ничего доделывать руками. Каталог данных считается от ТЕКУЩЕГО рабочего каталога, поэтому вызов из своего проекта ничего не пишет в каталог инструмента.
npx agentic-screencast build --source story.md --out pitch.mp4Одна команда: породит слайды из сценария, синтезирует реплики, отрендерит сцены и склеит видео. Повторный запуск берёт неизменившиеся сцены из кэша.
Путь --out считается от текущего каталога; без него файл ложится
в каталог данных ($AGENTIC_SCREENCAST_HOME/pitch.mp4). Ход сборки идёт в поток
ошибок построчно — сцена за сценой, — а в стандартный вывод уходит отчёт
одним JSON.
Тот же отчёт сохраняется рядом с MP4 как <ролик>.report.json. В
audit.expected находятся расчётная длина в кадрах и явно заданные имена
частей part:; в audit.measured — число декодированных кадров, длительности
готовых потоков видео и звука, наличие дорожек и главы из WebVTT.
audit.issues называет рассинхрон, недостающую дорожку или расхождение глав
вместе с измеренными значениями; те же находки попадают в warnings и поток
ошибок. Каждая запись warnings — и каждая находка lint — имеет вид
{rule, id, message, hint}: id называет проверку, rule — правило базы за
ней (FC-58 — правило 58 из docs/film-craft.md, VA-5 — шаг 5 раздела о
лицензиях в docs/visual-assets.md), hint — что поменять. Пустой список означает совпадение этих свойств готового файла с учётом
одного видеокадра и заполнения AAC, но не заменяет просмотр ролика и проверку
подписей. У ролика без частей нет файла глав; пересборка без них удаляет
старый файл рядом с тем же выходом.
Пока подбираете сцену, собирайте её одну:
npx agentic-screencast build --source story.md --only e1 --out проба.mp4Полный ролик из двух десятков сцен строится минутами, одна — секундами, и сегмент получается тот же самый, что войдёт в готовый файл.
Кадр без синтеза речи удобно смотреть командой frames:
npx agentic-screencast frames --source story.md --scene e1 --out e1.png
npx agentic-screencast frames --source story.md --except e1 --out sheet.png--scene порождает только выбранную сцену; --except делает лист остальных,
поэтому материал исключённой сцены может ещё отсутствовать. Вместе их не задают.
Моменты тактов здесь оценочные. Кадр видео содержит его субтитры и накладку,
если у сцены есть речь или overlay. Лист сообщает о большой ровной пустой
полосе на нарисованной сцене; намеренная разреженная карточка трейлера не считается ошибкой.
Как устроен вход
Вход один — файл сценария. Слайды и сцены сборки порождаются из него, рядом с ним, и правятся только через него. Так сделано потому, что описание одной сцены иначе живёт в двух местах, и правка реплики перестаёт доезжать до кадра.
Сцена — блок: заголовок с идентификатором и видом, несколько полей «ключ: значение», затем речь обычными абзацами без кавычек. Её может подготовить агент или человек, а движок получает текст для озвучки.
Речь сцены делится на такты: абзац — один такт. У такта своя запись,
своя длина и свой ключ кэша; длительность сцены — сумма тактов плюс хвост.
Перезапись одного такта не трогает соседние. Строка, начинающаяся с ~,
задаёт произносимый вариант своего такта: на экране и перед чтецом остаётся
обычный текст, а синтезу и адресации записи достаётся вариант.
# Заголовок ролика
voice: {"engine":"speechkit","name":"kuznetsov","speed":1.2}
## s03 · slides.compare
kicker: что это
title: Заголовок сцены
left: Что делает :: выдаёт по одному шагу | не пускает дальше
right: Чего не делает :: не решает задачу
at: 0.7 2.2
Первый такт: он записывается и озвучивается отдельно.
Второй такт той же сцены.
~ Второй такт той же сцены, записанный так, как его надо произнести.
## s04 · slides.chapter
title: Следующий шаг
body: Покажите результат
Теперь перейдём к результату.Вид сцены принадлежит ПОСТАВЩИКУ материала, а не инструменту.
В заголовке сцены до точки стоит поставщик, после — его вид:
## s03 · slides.compare. Поставщик с единственным видом пишется без
точки: ## s09 · page.
Инструмент приносит с собой трёх поставщиков:
| Вид | Для чего | Обязательные поля |
|---|---|---|
| slides.chapter | анимированное вступление или смысловая глава | title, body |
| slides.compare | сравнение «без / с» в две колонки | left, right |
| slides.chain | схема со стрелками и подписью возврата | nodes |
| slides.number | крупная величина с подписью | values либо value |
| slides.quote | дословная цитата чужого ответа: крупная кавычка, источник подписью, слова набираются | parts |
| slides.hero | открывающее заявление: заголовок встаёт по словам, можно картинку фоном или залить ею буквы (fill) | title |
| slides.steps | шаги, загорающиеся по одному вслед за речью | items |
| slides.features | сетка из двух–шести возможностей со значками | items |
| slides.timeline | вехи на линии, которая прочерчивается к каждой, впереди линии — светящаяся голова | items |
| slides.counter | числа, докручивающиеся счётчиком, доли — кольцом, под каждым — маленький график хода (spark) | values либо value |
| slides.beforeafter | два состояния интерфейса в одном кадре: слева «было», справа «стало», разделитель едет | image и after |
| slides.perspective | снимок на экране в перспективе (WebGL): облёт камерой, блик, отражение | image |
| slides.parallax | снимок расслаивается на панели на разной глубине, ближние плывут сильнее | image и panels |
| slides.chart | график из CSV «подпись,значение»: столбцы растут, линия прорисовывается, пик подсвечен; type: race — гонка столбцов по периодам CSV | data |
| slides.code | код, набирающийся с подсветкой; строкой сценария или файлом | code либо file |
| slides.photo | картинка с медленным наездом к точке и подписью | image |
| slides.shot | снимок экрана в рамке браузера или телефона, плывущей в 3D | image |
| slides.outro | финальная карточка: заголовок, строка, призыв, адрес | title |
| slides.card | карточка трейлера: одно–три слова во весь кадр, влетают со вспышкой и тряской | title |
| slides.titlecard | название фильма разреженными заглавными с бликом и засветкой | title |
| slides.marquee | бесконечная лента логотипов или подписей, одна или две строки навстречу | items |
| slides.stack | стопка карточек: на каждом пункте верхняя улетает, колода подъезжает | items |
| slides.orbit | иконки на орбитах вокруг заголовка | items |
| slides.chat | реплики чата по очереди, перед ответом по строке «Думаю…» и полосам скелетона бежит блик | items |
| slides.carousel | кольцо карточек в настоящем 3D, поворачивается к пункту | items |
| slides.globe | глобус из точек на WebGL: дуги из первого города к остальным, пинг и подпись; map: flat раскладывает их на плоской карте | items |
| slides.layers | разобранный вид: панели снимка поднимаются на свою глубину, камера наклоняется, к концу слои собираются | image и panels |
| slides.bento | бенто-сетка: первая ячейка крупная, последние широкие, чтобы сетка закрылась, остальные малые; каждая входит наклоном из глубины | items |
| slides.wall | стена снимков, наклонённая в 3D: колонки едут навстречу друг другу под заголовком | images |
| slides.cloud | подписи или значки на вращающейся сфере, ближние крупнее и ярче | items |
| slides.shell | работающий терминал: команды набираются после приглашения, крутится спиннер, появляется вывод | items |
| page | готовая страница: снимок интерфейса или своя вёрстка | page |
| report | страница, которую agentic-report собирает из исходного Markdown заново для каждой сцены, сохраняя идентификаторы блоков и исходник; верхняя панель, ревью и переключатели схемы и темы выключены, если метаданные отчёта сами не называют эти ключи; нужен agentic-report >=0.20.0 в проекте | report |
| video | готовый видеофайл вместо нарисованной страницы | file |
У сцены page необязательное pageVertical называет отдельный HTML-файл,
свёрстанный для кадра 9:16. При вертикальной сборке, в том числе с
build --format vertical, берётся он; в остальных форматах — page.
Для английского варианта задайте page.en и pageVertical.en.
Без pageVertical горизонтальная страница в вертикальной сборке сохраняет
обычное движущееся кадрирование. Выбранная вертикальная страница видна также
в frames и на странице записи голоса; отсутствие выбранного файла — ошибка,
а не повод взять горизонтальный.
В третьей колонке названы только обязательные поля; остальные допустимые
перечисляет agentic-screencast schema. У цепочки это back — подпись стрелки
возврата, у величины — label и tags, у сцены-экрана — zoom,
spotFrom и focus. Что делает каждое поле слайда — входы, движение кадра,
живой фон, рамка устройства, — описывает agentic-screencast help slides;
вид плёнки, фирменные цвета и тема части ролика — agentic-screencast help themes.
Как эти токены темы соответствуют токенам agentic-report, чтобы ролик и страница отчёта
выглядели как одно, — в карте токенов темы.
Пункты items пишутся как у колонок: Заголовок :: пояснение | …, эмодзи
в начале пункта становится его значком. Если at не назвал моменты, пункты
выходят каждый на своём такте, когда тактов хватает, а иначе — ровным шагом
по всей речи сцены. Картинки и код встраиваются в порождённую страницу,
поэтому замена файла под тем же именем пересобирает сцену.
Плотный slides.steps с шестью–восьмью пунктами раскладывается в две
колонки горизонтального кадра и в одну вертикальную последовательность
на телефоне. Четыре коротких узла slides.chain стоят в одном ряду
горизонтально и друг под другом вертикально. Длинные подписи проверяйте
на готовом кадре.
Видеовставка приводится к кадру ролика: масштабируется с сохранением
пропорций, дополняется полями до нужного размера и переводится в темп
ролика. Речь у неё такая же, как у любой сцены, и длину задаёт она;
сцена без речи законна у видео, slides.chapter и page. У обычного
видео длину задаёт файл; немой главе и сохранённой странице задайте
duration в секундах, чтобы текст успел появиться и остаться читаемым.
Короче сцены — достаивается последним кадром, длиннее — обрезается.
Аннотации могут продлить сцену, чтобы текст успели прочитать.
Движение поверх настоящего видео
Общее поле overlay работает у video, page и других видов сцен: камера,
подсветка, карточки, титры, пометки и лупа. Поле speed видеосцены
замедляет отрезок клипа или останавливает время внутри того же плана,
freezeAt удерживает один настоящий кадр клипа. Их поля, умолчания и
поведение описывают agentic-screencast help overlay и help text;
рабочий пример показывает вводную, клип и
стоп-кадр в одном сценарии, а самодостаточный пример —
сохранённую страницу с наездом по CSS-селектору и надписью «иллюстрация».
Ручной указатель overlay.pointer с click:true рисует волну, но не нажимает
кнопку в исходной записи: настоящие действия снимайте через Playwright capture. На
сохранённой странице и на слайде накладка может сама ответить за страницу:
overlay.actions переключают тумблер, вкладку или меню и переставляют список, а его
элементы переезжают на новые места плавно; drag у точки указателя переносит
элемент, overlay.thinking кладёт «ИИ думает» на место будущего ответа, а
overlay.torch ведёт за указателем круг света (agentic-screencast help overlay).
У сцены с freezeAt сборка меряет разброс яркости в каждой области area
на замороженном кадре и отказывает, если область почти пустая: в отказе
названы сцена и измеренный контраст.
Поле stills: @saved :: проверить результат у видеосцены заказывает контрольный
кадр на именованной отметке дубля. Можно также назвать такт (b2+0.3), долю
сцены (80%) или шаг (every 1s). Полная сборка пишет кадры в каталог
<ролик>.stills/ и сообщает для каждого scene, moment, time в секундах
готового ролика, заметку note и путь file; сборка с --only кадров не снимает.
Значения полей пишутся коротким размеченным списком. Разделителей два, и значат они в каждом поле своё — вот все:
| Поле | Как писать | Пример |
|---|---|---|
| left, right | Заголовок :: пункт \| пункт — заголовок колонки и её содержимое; одно значение без \| становится абзацем. Пометка (bad), (good), (plain) в заголовке задаёт окраску | Что приходит (bad) :: «готово» \| не работает |
| nodes | узлы цепочки через \|; пометка (acc) или (bad) красит узел | сам поставил \| сам сделал \| сам объявил (bad) |
| values | пары величина :: подпись через \| | 300 :: узлов \| 24 :: действия |
| parts | пары подпись :: текст через \|; текст цитаты идёт как есть | ответ :: шаг принят |
| tags | простой список через \| | что это \| зачем \| как начать |
| back | одна строка: подпись стрелки возврата | никто не держит рамку |
| note | одна строка под содержимым: у сравнения, цепочки и цитаты; у числа, счётчика и графика — источник цифр; остальные виды поле отвергают | отрисовка ответа, не съёмка |
at — моменты появления элементов якорями:
| Якорь | Что значит |
|---|---|
| b2 | начало второго такта речи |
| b2.end | конец его речи: начало третьего такта, а у последнего такта — конец речи сцены, без хвоста и перехода |
| b2+0.4, b2-0.2 | через 0,4 с после начала такта, за 0,2 с до него |
| 40% | доля длительности сцены |
| 1.2s | секунды от начала сцены; голое число — тоже секунды |
Без at элементы появляются каждый на своём такте: первый на первом,
второй на втором. Элементов у сцены больше, чем полей: шапка (надзаголовок
с заголовком) — тоже элемент, и она всегда первая. У compare их три
(шапка и две колонки), у chain — шапка, каждый узел и подпись возврата,
у number — шапка, каждая величина и строка меток, у quote — шапка
и каждая часть. Якорь, указывающий дальше последнего такта, разбор
отвергает и называет число тактов сцены. Это и есть то, ради чего якоря заведены: слайд
собирается вслед за речью и тянется вместе с записью любой длины.
Момент, записанный секундой, так не умеет — он назначен до записи
и о ней ничего не знает: реплика вышла длиннее, и элемент полсцены ждёт;
короче — сцена кончилась раньше, чем он проступил.
Те же якоря принимают focus у сцены-экрана (селектор @ якорь),
spotFrom, spotlight и at любого элемента накладки overlay
({"at":"b2+0.5",…}; там же — доля сцены 40%). Незнакомое поле — ошибка разбора,
а не молчание: опечатка в имени иначе тихо выбросит содержимое слайда.
Отсутствие обязательного поля — тоже ошибка разбора, и она называет вид
сцены и имя поля.
У сцены page поля target и mustRead разбору не нужны, но без них признак
кадра проверять нечего — см. раздел «Снимок интерфейса как подложка».
Базовую длительность определяет речь — сумма длин её тактов плюс хвост, округлённая вверх до целого кадра. Аннотации могут продлить сцену до окончания последней карточки или клика.
Язык и правила чтения — данные ролика
lang: ru
pronounce: "ru-latin"lang — язык ролика: на нём подписана страница записи, помечены
порождённые страницы и говорят отказы этой сборки. Не назван — берётся
из AGENTIC_SCREENCAST_LANG, затем из LANG, иначе английский. Инструмент сам
по себе языка ролика не знает и не выбирает.
pronounce — правила чтения: текст для озвучки не равен тексту
на экране. Один движок читает латиницу сам, другой молча её пропускает;
аббревиатуру надо произнести по буквам, а адрес сайта не произносить
вовсе. Это свойства языка и предметной области, поэтому они приходят
данными — именем поставляемого набора, путём к своему файлу или прямо
объектом:
| Набор | Для чего |
|---|---|
| ru-latin | русская речь, движок латиницу не читает: словарь сокращений плюс побуквенная замена остатка |
| ru-abbr | русская речь, движок латиницу читает сам: правятся только аббревиатуры, адреса выбрасываются |
Свой набор — объект из четырёх необязательных частей: say (замены
целых слов), translit (побуквенная замена), drop (что выбросить),
cleanup (уборка после замен), плюс order (порядок шагов)
и caseSensitive. Термины своей области добавляют сюда, а не в
инструмент.
Порядок шагов — не мелочь: словарь, применённый раньше выбрасывания адресов, успевает переписать кусок внутри домена, и адрес перестаёт быть адресом — вместо того чтобы исчезнуть, он произносится наполовину переписанным.
Смена правил меняет строку синтеза, а с ней ключ кэша: сменились правила — звук будет синтезирован заново, у сетевого движка за деньги.
Кадр, качество и оформление — данные ролика
# Заголовок ролика
frame: {"width":1080,"height":1920,"fps":30,"scale":1}
encode: {"crf":18,"preset":"veryfast","pix":"yuv420p","audio":"192k"}
theme: calm-paper| Шапка | Что задаёт | Умолчание |
|---|---|---|
| frame | ширина и высота кадра, кадры в секунду, scale | 1920×1080, 25, 1 |
| encode | качество видео и битрейт звука | crf 18, veryfast, yuv420p, 192k |
| theme | имя поставляемой темы или переменные оформления: уходят в корень страницы и в накладку | neutral |
| scheme | light или dark: схема всех названных тем ролика; её цвета ролей — из файла палитр, общего с agentic-report | своя у каждой темы |
| format | пресет формата: landscape, vertical (1080×1920, 30 кадров, с безопасной зоной площадок), square | — |
| look | вид плёнки: грейд, виньетка, зерно, каше — именем или JSON | — |
Список тем с их шрифтами, переменные договора темы и правила частичной правки
(theme: {"preset":"noir","--acc":"#7aa2ff"}) печатает agentic-screencast
help themes. Незнакомое имя темы и объект без preset, в котором не хватает
переменной, — ошибки разбора, которые называют доступное и недостающее. У каждой темы, кроме
blockbuster, есть светлая и тёмная схема; сцена называет свою через
theme: {"preset":"noir","scheme":"dark"}, а незнакомая схема или схема, которой у темы нет, —
ошибка разбора с перечнем схем темы.
Вертикаль и квадрат одной строкой. Шапка format: vertical задаёт кадр
1080×1920, 30 кадров в секунду и безопасную зону площадок, format: square —
1080×1080 и те же 30 кадров; frame по-прежнему перекрывает размер и темп, а
зона пересчитывается под кадр.
Горизонтальный сценарий собирается вертикальным ключом --format vertical
без переписывания. Как раскладывается кадр и что сборка меряет —
agentic-screencast help vertical; как сделать такой ролик читаемым на
телефоне — руководство по вертикальным роликам.
Остальное описано в справке инструмента, одним местом на каждую тему:
заготовки сценария по жанру (new), кадры без сборки (frames), контрольные
кадры stills, режиссёрские правила lint, длинные поля в несколько строк и
перевод из того же сценария (.ru-поля, блок речи [ru], --lang ru) —
agentic-screencast help vertical; пометки от руки, блики, всплески и лупа —
help text и help overlay; переходы и поле fade — help transitions;
ролик для страницы — help web.
Вертикальный ролик, тридцать кадров в секунду, светлая тема — всё это шапка сценария, а не правка инструмента. Поставляемые слайды рисуются в сетке шириной 1280 и растягиваются под ЛЮБОЙ кадр: кегли и отступы остаются теми же числами, меняется только увеличение.
scale — множитель плотности пикселей, как у экрана с высоким
разрешением: страница рисуется в том же размере, но снимается с большим
числом точек. Размер готового файла он НЕ меняет — его задают width
и height; при scale: 2 кадр 1920×1080 остаётся 1920×1080, просто
рисуется вчетверо дольше. Менять его нужно редко. Тема — пары «переменная —
значение»; инструмент кладёт их в :root без собственной интерпретации.
Смена кадра или качества обесценивает кэш кадров — так и задумано: собранное прежним размером к новому не подходит.
Порождаемое кладётся рядом с источником — каталог slides/ и файл
.generated-pitch.json. Править их руками бессмысленно: следующий запуск
перезапишет. Держите их вне системы контроля версий, иначе вторая копия
сценария вернётся тем же путём, каким её убирали.
Флаги --deck и --pitch принимают готовые данные в обход сценария —
они для чужих наборов, не для своего ролика.
Договор о поставщике материала
Чем нарисована сцена — решает поставщик, а не инструмент. Ядро не знает ни одного вида сцены: оно спрашивает у того, кого назвал заголовок, какие у него виды и какие у них поля, и просит породить страницу. Поэтому ролик можно собрать из чего угодно — из своей вёрстки, диаграммы, чужого фреймворка, готового видео, — не трогая инструмент.
Поставщик — обычная программа; реализацию пишут на любом языке, не читая исходников. Объявляется она в шапке ролика:
providers: {"my": "python3 /путь/provider.py"}Что обязан уметь поставщик
Две подкоманды. Ответ — JSON в стандартный вывод и ничего кроме него; отказ — ненулевой код возврата и причина словами в поток ошибок.
| Подкоманда | Аргументы | Ответ |
|---|---|---|
| kinds | — | {вид: {about, fields, required, effects?, check?, fileField?, video?, offline?, shown?, staging?}} |
| page | --scene-json, --out | {file} — путь к порождённой странице |
fields— какие поля допустимы у сцены этого вида,required— группы «хотя бы одно из». По ним ядро отвергает опечатку в имени поля: без этого содержимое сцены тихо выпало бы.effects— умолчания расписания для вида: наезд, пятно, переход. Выбор остаётся за поставщиком, ядро исполняет; сцена вправе перебить своими полями.check— пороги приёмки кадра: по ним проверка судит именно этот материал. Слайд разрежен и его текст читают, экран плотен и от него нужно узнавание — числа у них разные и в инструменте не зашиты.shownиstaging— какие поля зритель читает в кадре, а какие управляют показом. Поshownправило перевода (lint --lang ru) называет поле, оставшееся без перевода; вид, не разделивший поля, этим правилом не проверяется.fileField— материал берётся готовым файлом, названным этим полем, и порождать нечего. Так устроеныpageиvideo.- Страница обязана быть чистой функцией времени: моменты появления
элементов пишутся в
data-atякорями, а разрешает их слой композиции. Слой читаетdata-at,data-typeиdata-kineticтолько у страницы с меткой<html data-sc-page>; у чужой страницы эти имена значат своё. - Страница объявляет число своих элементов расписания атрибутом
data-slidecast-elementsна теле. По нему проверка порядка отличает «страница отрисовалась не целиком» от «так и задумано»; страница, которая молчит, проверяется только на непустоту. - Страница объявляет, что у неё считается содержанием, атрибутом
data-slidecast-content— списком селекторов через запятую. Признак кадра требует, чтобы хотя бы одно из объявленного в кадре было; страница, которая молчит, этим признаком не судится вовсе. Проверка не знает ничьей разметки — иначе кадр чужого материала объявлялся бы пустым, сколь угодно осмысленный.
Соответствие проверяется программой, а не на слово:
npx agentic-screencast provider-check 'python3 /путь/provider.py'Проверки
Сборка проверяет закодированные дорожки и главы, но не судит изображение.
Слишком много текста, вылезшая за край строка и нечитаемая подпись —
предмет check, и запускать его нужно самому.
npx agentic-screencast check --source story.md # признак кадра: текст, кегли, графика
npx agentic-screencast order --source story.md # порядок появления элементов
npx agentic-screencast verify # четыре проверки ядра рендера
npx agentic-screencast script --source story.md # читаемый сценарий в поток вывода
npx agentic-screencast schema # описание полей сцены, JSON Schema
npx agentic-screencast scenes --source story.md # сцены источника как JSON
npx agentic-screencast craft spotlight # правила режиссуры для решения, с номерами FC
npm test # быстрые проверки без браузера
npm run e2e # полные браузерные и видео проверки; в CI запускаются ночьюhandover story.md --film <ролик>.mp4 — ворота перед сдачей ролика: запускает lint, требует непустой MP4 и отчёт полной сборки (собран после последней правки сценария, без проблем аудита и предупреждений), проверяет контрольные кадры и checklist.md и печатает {verdict, checks}, при провале — код 1. Отчёт build --only проверку не проходит.
schema печатает машинно читаемое описание сцены, scenes — сцены
разобранного сценария в той же форме. Вместе они дают чужому агенту
проверку до сборки: получить схему, получить сцены, прогнать одно через
другое любым проверяльщиком JSON Schema (черновик 2020-12). Схема
порождается из тех же таблиц, по которым разбор отвергает незнакомое поле
и требует обязательное, — второго списка не заводится.
check меряет прямо на кадре, а пороги и признак содержания берёт
у поставщика и у самой страницы:
что считать годным кадром, знает тот, кто его рисует. У поставляемых
слайдов это не больше 220 знаков видимого текста и кегль основного текста
не меньше 28 пикселей; сверх порогов проверка требует, чтобы в кадре было
объявленное страницей содержание, не осталось неподставленных значений,
ни одна строка не ушла за край, страница закрывала собой кадр,
а текст читался: отношение контраста к его фактической подложке —
не меньше 4,5 у основного текста и трёх у текста размером с заголовок (от 48 точек
на кадре 1080), как требует WCAG AA. Свой поставщик объявляет свои числа полем check,
и чужой материал судится по ним, а не по слайдовым. Готовый видеоклип
признаком кадра не судится: check помечает такую сцену отдельным признаком
video и объясняет, что её качество меряется по готовому файлу.
verify проверяет ядро: воспроизводимость (два прогона в разных процессах
дают одинаковые кадры), живость (кадры меняются), отсутствие реального
времени в кадре и сикабельность (кадр из середины сцены совпадает
со сквозным прогоном).
lint предупреждает о верхнем титре поверх интерфейса, субтитрах вместе
с индикатором прогресса сверху, слишком быстром наборе текста, отрезке дубля,
пересекающем не названную в stills отметку, и резкой смене экрана вне
отметок и интервалов cameraMoves. Проверка размещает речевые якоря по оценке;
после записи голоса сверяйте готовый ролик.
Договор о движке голоса
Порождение звука вынесено за границу инструмента. Движок — обычная программа, которую инструмент запускает; о её устройстве он ничего не знает и нигде не ветвится по её имени. Поэтому реализацию можно написать на любом языке, не читая исходников инструмента.
Движок называется в данных голоса. Если имя совпало с поставляемым —
работает поставляемый; иначе имя считается командой (путь или имя
в PATH), и её можно указать вместе с тем, чем запускать.
npx agentic-screencast voices --engine speechkit
npx agentic-screencast build --voice-json '{"engine":"speechkit","name":"kuznetsov","speed":1.2}'
npx agentic-screencast build --voice-json '{"engine":"/путь/python /путь/voice.py","name":"baya","rules":"silero"}'
npx agentic-screencast voice-checkЧто обязан уметь движок
Четыре подкоманды. Ответ — JSON в стандартный вывод и ничего кроме него; отказ — ненулевой код возврата и причина словами в поток ошибок.
| Подкоманда | Аргументы | Ответ |
|---|---|---|
| voices | — | список имён голосов, [] если движок их не перечисляет |
| synth | --voice-json, --text, --out | {file, duration, engine, voice} |
| probe | --voice-json, --text | {fingerprint} |
| paths | — | ресурсы, которые движок разрешает сам |
Требования, которые нельзя нарушить:
- Звук — WAV 48 кГц моно. Из его длины инструмент выводит длительность сцены; сжатый формат или иная частота дадут неверное число, и картинка разъедется со звуком.
- Ключ доступа движок берёт из окружения. В данные голоса его класть нельзя: объект голоса целиком уходит в ключ кэша и в отчёт сборки.
- Отпечаток (
probe) — строка, которая меняется тогда и только тогда, когда изменился бы звук при тех же тексте и данных голоса. Синтез отвечает пустой строкой: для него текст и данные определяют звук полностью. Непустой отвечает движок, у которого звук берётся снаружи, — например записанный человеком голос: при перезаписи он даёт другой звук при тех же тексте и данных, и без отпечатка сборка отдала бы прежнюю запись из кэша, выглядя успешной.
Отпечаток подмешивается в ключ кэша только когда он непуст. Поэтому появление подкоманды не обесценило ни одной уже синтезированной реплики.
Соответствие договору проверяется программой, а не на слово:
agentic-screencast voice-check <команда> гоняет восемь требований против чужого
движка и называет каждое нарушение поимённо.
Поставляемые движки
Какие движки поставляются, что каждому нужно и как выбрать голос, печатает
agentic-screencast help voice; список голосов движка — agentic-screencast
voices. Системный голос (say) идёт в ролик только с явного разрешения
владельца — раздел «Voice» скилла.
Ролик, озвученный своим голосом
Движок recorded реализует тот же договор, что и синтез, поэтому
собирается ролик теми же командами и с тем же кэшем:
npx agentic-screencast build --source story.md --voice-json '{"engine":"recorded","dir":"/путь/к/записям"}'Запись адресуется текстом такта: имя файла — md5 от текста плюс
.wav. Отсюда следствие, о котором нужно знать заранее: два такта
с дословно одинаковым текстом получат одну и ту же запись. Для сценария
ролика это верно — одинаковый текст и звучать должен одинаково.
Нужного файла нет — сборка отказывает и называет, какой реплики не хватает, куда её класть и под каким именем. Гадать правило адресации не приходится. Файл на месте, но не читается или повреждён — тоже отказ с именем файла и причиной одной строкой, а не падение посреди рендера.
Каталог задаётся полем dir в данных голоса; без него берётся
$AGENTIC_SCREENCAST_HOME/recordings. Формат записи приводится к требуемому
договором (WAV 48 кГц моно) самим движком, так что класть можно то, что
отдал диктофон.
Правила чтения (rules) с этим движком несовместимы, и он отказывается
работать, когда они заданы: правила переписывают текст для синтеза,
а человек читает вслух саму реплику — адрес записи считался бы от того,
чего он не произносил.
Перезапись доезжает до готового файла. Отпечаток этого движка — хеш содержимого записи, поэтому замена файла меняет ключ кэша, и сборка берёт новый звук с новой длительностью сцены. Отпечаток, посчитанный от имени файла или от текста, выглядел бы работающим и этого не давал: сборка отдала бы прежнюю запись, выглядя успешной.
Чем озвучена каждая сцена, видно в отчёте ключей — поле voiced:
npx agentic-screencast build --source story.md --keys-onlyЗаписать озвучку голосом
Класть файлы в каталог руками не обязательно: инструмент поднимает страницу, на которой видно сцену и её реплику.
npx agentic-screencast record --source story.mdКоманда печатает адрес вида http://127.0.0.1:<порт>/ — откройте его
в браузере. По каждой сцене показаны картинка, которую увидит зритель,
и её речь тактами: у каждого своя строка для чтения вслух, свой ориентир
длины и свои кнопки. Записанное можно переслушать, перезаписать или удалить,
и перезапись одного такта не трогает соседние. Выбрать вход можно тут же —
список микрофонов рядом с кнопкой разрешения; названия устройств браузер
показывает только после разрешения, поэтому его и просят заранее.
Страница открывается, даже если дубль видео, файл отметок или материал другой сцены ещё не создан. Доступные сцены можно записывать сразу; отдельный список называет пропущенные сцены, недостающие файлы и нужное действие. Обычная сборка остаётся строгой и требует все материалы.
Пока идёт запись, переключиться на другую сцену нельзя — метки сцен и кнопки перехода заперты: записываемая реплика замкнута на сцену, выбранную при нажатии, и переход посреди чтения означал бы звук не в той сцене, поверх уже записанного там.
Собрать и посмотреть. Кнопка над списком тактов собирает показанную
сцену и показывает её тут же, в проигрывателе: тем же ядром, той же
озвучкой и тем же расписанием, что и готовый ролик, — но одну сцену,
поэтому это секунды, а не минуты. Платного синтеза не бывает: звук
берётся из записей. Та же сборка — build --only (раздел «Запуск»).
Закончив, остановите интерфейс — Ctrl+C: сервер закрывается, временные картинки убираются, а записи остаются в хранилище и переживают и остановку, и закрытие вкладки. Дальше ролик собирается обычной командой сборки.
Почему сервер, а не файл на диске. Микрофон браузер даёт только
в защищённом контексте: http://127.0.0.1 в него входит, file:// —
нет, и там navigator.mediaDevices попросту отсутствует. Слушает сервер
только петлю — наружу интерфейс не смотрит.
Ориентир длины — это оценка, а не длительность. Настоящую длину даёт только звук: после записи ориентир сменяется измеренной длительностью, и ровно её сборка берёт за длину сцены.
Каталог записей — тот же, из которого читает движок recorded, и адрес
файла считается тем же кодом. Браузер отдаёт сжатый поток, но в хранилище
кладётся договорный WAV 48 кГц моно: заглянувший в каталог человек найдёт
там звук, а не контейнер браузера.
Локальный бесплатный синтез Silero поставляется внешней реализацией
(external/silero/): он держится за torch, а это окружение примерно
на 600 МБ, и платить их должен только тот, кому он нужен.
Правила чтения текста для синтеза объявлены в src/speech.ts: Silero
молча пропускает латиницу, поэтому для него она переписывается
кириллицей, а SpeechKit читает её сам. На экране в обоих случаях
остаётся исходное написание.
Выбираются они по имени, названному данными голоса. У поставляемого движка это его имя; у посторонней реализации имя — путь к программе, и правил по нему не найти, поэтому данные голоса могут назвать их прямо:
{"engine": "/путь/python /путь/voice.py", "name": "baya", "rules": "silero"}Смена движка, голоса или темпа обесценивает кэш и звука, и кадров — так и задумано: длительность сцены выводится из длины реплики.
Темп cps у чернового stub — только оценка. Для точного черновика
озвучьте одну сцену окончательным голосом через build --only <сцена>
--keys-only --voice-json '<голос>', разделите число произносимых знаков
на длительность её тактов spoken в отчёте и задайте полученное cps.
Записанный звук уже лежит в кэше и повторно оплачиваться не будет.
Ключ доступа к SpeechKit
Ключ читается из окружения (CLOUD_KEY), а если его там нет — из .env
в корне проекта.
Где его взять:
- В консоли Yandex Cloud создайте сервисный аккаунт в нужном каталоге.
- Выдайте ему роль
ai.speechkit-tts.user(или выше). - Создайте для этого аккаунта API-ключ — это и есть
CLOUD_KEY. - Идентификатор каталога не нужен: при авторизации API-ключом сервис использует каталог сервисного аккаунта (проверено живым вызовом).
Голос kuznetsov в таблице голосов документации не значится, но API его
принимает. Полный список сверен живыми вызовами и лежит в поставляемом
движке.
Снимок интерфейса как подложка
npx agentic-screencast snapshot <файл-настройки.json> [каталог-подложек]Страница работающего приложения сохраняется отладочным протоколом Chromium в MHTML и дальше используется как обычная подложка сцены. Так сделано потому, что интерфейс рисуется на клиенте: сохранённая обычным способом страница при открытии из файла выполнит скрипт заново, запросы уйдут в никуда и в кадре будет пустой экран.
Договор подложки. Инструменту нужен один файл на сцену —
самодостаточная страница, скрипты которой при открытии не исполняются
(этому отвечает MHTML). Всё остальное — дело потребителя: снимайте
подкомандой ниже, своим сценарием Playwright или чем угодно ещё. Файл,
отвечающий договору, годится сцене вида page без участия инструмента. Следствие, о котором нужно знать: слой композиции инструмент
внедряет до документа (addInitScript), а не тегом <script>.
Подкоманда ничего не знает о вашем приложении. Как войти, какие куки поставить, чего дождаться, что нажать и подвинуть — приходит настройкой:
{
"base": "http://localhost:8080",
"width": 1920,
"height": 1080,
"signIn": {
"url": "/api/auth/sign-in/email",
"json": { "email": "${APP_USER}", "password": "${APP_PASS}" },
"cookies": ["session_token"]
},
"cookies": [{ "name": "beta-accepted", "value": "true" }],
"screens": [
{ "id": "board", "path": "/board", "await": "[data-testid=card]",
"prep": [{ "zoom": 1.45, "pause": 600 }], "settle": 1500 }
]
}| Поле | Что задаёт |
|---|---|
| signIn | запрос входа целиком: url (свой или относительный к base), method, headers, json. Куки ответа переносятся в браузер; cookies называет нужные поимённо, без него берутся все |
| cookies | куки, которые приложение ждёт помимо сессии: согласия, режимы, язык |
| screens[].id | имя файла подложки; должно быть единственным — настройка с двумя одинаковыми отвергается, иначе второй снимок затёр бы первый |
| screens[].await | селектор данных: ожидание по времени может поймать индикатор загрузки, а проверка «кадр не пустой» окажется зелёной на спиннере |
| prep | что сделать в живом приложении до снимка: click (сколько раз — times), zoom, drag, inject (css, js), wait (пауза в миллисекундах) |
| prep[].pause | сколько ждать после приёма: после click — 120 мс по умолчанию, после zoom — 400; у inject своя пауза, inject.pause, по умолчанию 300 |
| screens[].settle | пауза перед самим снимком, миллисекунды: время дорисовать то, чего не выразить селектором ожидания, — вроде раскладки графа или анимации, доигрывающей после появления данных |
| drag | привести элемент to в середину кадра, таща поверхность surface; avoid перечисляет то, за что хвататься нельзя, — элементы, которые тащатся сами |
Публичные страницы входа не требуют — тогда signIn просто не объявлен,
и снимок берётся как у гостя, то есть ровно так, как страницу увидит
посторонний.
Строка вида ${ИМЯ} в любом месте настройки подставляется из окружения
или из .env проекта. Так учётные данные не лежат в файле настройки
и не попадают в снимок: в MHTML пишется разметка, куки туда не записываются.
Увеличение делается в живом интерфейсе (prep), а не растягиванием
снимка: служебные подписи интерфейса — 10–12 пикселей, и в кадре
1920×1080 их иначе не прочесть.
Сцена готовой страницы помечается видом page и объявляет mustRead —
селектор того, что обещает показать реплика. Кадр признаётся годным,
когда выполнено всё перечисленное:
mustReadнайден, помещается в кадр целиком и читаем — кегль не меньше 16 экранных пикселей с учётом всех увеличений;- цель не вылезает за кадр (доля кадра, занятая целью, не больше 1,15);
- сама страница закрывает собой не меньше 0,85 кадра — иначе она уехала углом внутрь кадра, и зритель видит подсвеченный угол вместо страницы;
- цель занимает не меньше 0,002 кадра — вырожденную в точку цель показывать нечем;
- если увеличение больше единицы, цель без увеличения занимает меньше половины кадра: иначе целью объявили бы весь макет и приближать нечего. При масштабе единица это правило не применяется — честный кадр «страница целиком» им запрещаться не должен.
Безусловного требования «цель занимает половину кадра» нет: оно противоречиво для широких тонких целей. Строка списка — это четыре процента кадра, и требование занять половину заставило бы увеличить её так, что обещанное срезалось бы краем.
Чего инструмент не делает
Не решает за автора, какой сюжет, смысл действия, факт или голос показывать: живые действия записываются, но проверить, что зритель их понял, должен агент.
