nd-video-studio — видео из HTML с диктором, музыкой и сведением

· 2 мин чтения
ai-agents video-generation python self-hosted workflow
📂 Исходный код на GitHub

Видеостудия для агентов: HTML-композиция рендерится в MP4 через HyperFrames, а вокруг неё собираются закадровый голос, музыка и финальный микс. Внутри два скилла для Claude Code и Codex, 17 Python-скриптов и локальная студия музыки на своём ACE-Step. Лицензия MIT.

nd-video-studio — видео из HTML с диктором, музыкой и сведением

nd-video-studio — видео из HTML с диктором, музыкой и сведением

nd-video-studio — небольшая видеостудия для агентов. Она берёт HTML-композицию, прогоняет её через HyperFrames и получает MP4. Вокруг ролика собирается закадровый голос, музыка и финальный микс. Всё это делают Python-скрипты, которые запускает Claude Code или Codex по инструкции из скилла. Лицензия — MIT, язык — Python.

Идея в том, чтобы не учить агента пользоваться видеоредактором. Композиция — это обычная HTML-страница. Агент уже умеет её писать. Остаётся добавить к ней тайминги, звук и правила анимации, а потом отрендерить.

Что лежит в репозитории

Репозиторий создан в сентябре 2026 года, у него 16 звёзд. Основное содержимое — два скилла и папка scripts/ с семнадцатью Python-скриптами и двумя shell-скриптами.

путь что делает
skills/nd-video/ скилл «от идеи до MP4»: приёмы HyperFrames, голос, музыка, сведение
skills/ndt-content/ скилл «видео из брифа целиком на моделях NeuralDeep»
scripts/nd_probe.py проверяет, что все модели и адреса API пайплайна отвечают
scripts/nd_research.py поиск через Search API, результат пишется в sources.md
scripts/nd_script.py пишет сценарий по брифу, на выходе narration.json со сценами
scripts/nd_retime.py расставляет начала реплик по настоящей длине озвучки
scripts/nd_align.py сверяет озвучку с текстом через whisper-1, даёт тайминги слов
scripts/nd_images.py картинки через FLUX, удаление фона, увеличение
scripts/nd_review.py визуальная проверка кадров моделью с картинками
scripts/music_studio.py + tools/music-studio.html локальная студия музыки поверх своего ACE-Step
scripts/beat_map.py + tools/beat-sync.js карта битов трека и привязка анимации к ней
scripts/tts_gpt_audio.py голос через gpt-audio от OpenAI (OpenRouter)
scripts/tts_hub.py голос через TTS NeuralDeep
scripts/music_lyria.py музыка через Google Lyria 3 (OpenRouter)
scripts/music_acestep.py музыка через свой ACE-Step 1.5
scripts/fit_narration.py вписывает реплики в отведённое время
scripts/voice_fx.py эквалайзер, компрессор, реверберация и пресеты
scripts/mix.py сведение голоса и музыки с приглушением музыки
scripts/build_audio.sh три предыдущих шага одной командой
vendor/hyperframes/ частичная копия скиллов HyperFrames вместе с их лицензией

Оба скилла лежат ещё и в .claude/skills/ и .agents/skills/, поэтому Claude Code и Codex подхватывают их сами.

Порядок работы

Первое, что стоит понять про этот пайплайн: голос идёт раньше вёрстки. Скорость речи нельзя угадать по тексту. Автор пишет, что голос ryan на сервисе NeuralDeep звучит со скоростью примерно 1,7 слова в секунду, а черновик сценария рассчитан на 2,1. Поэтому:

  1. Бриф и раскадровка. Одна мысль на сцену. У сцены заголовок в виде вопроса, 2–4 реплики диктора и ключевая идея в конце. Автор отдельно отмечает: без субтитров и без карты переходов зритель не понимает, что смотрит.
  2. Сценарий. nd_research.py собирает выдержки из поиска в sources.md, nd_script.py пишет по ним narration.json. Факты в сценарии потом правят руками.
  3. Голос. tts_hub.py кладёт wav-файлы в audio/raw/line_NN.wav.
  4. Пересчёт таймингов. nd_retime.py переписывает начала реплик по реальной длине файлов. Дальше верстать HTML.
  5. Сверка. nd_align.py расшифровывает озвучку и сравнивает с текстом. TTS иногда глотает и путает слова: в демо «reuse» превратилось в «ray use». На слух это почти не слышно, скрипт это ловит и просит переозвучить конкретную реплику. Заодно он даёт words.json с таймингами слов — по нему в HTML подсвечивается каждое слово в момент произнесения.
  6. Картинки и композиция. nd_images.py генерирует изображения, агент собирает index.html из сцен, субтитров и подсветки слов.
  7. Проверка кадров. npx hyperframes check, потом snapshot --at t1,t2,... и взгляд на лист контактов. nd_review.py делает то же самое моделью с картинками: она ловит мелкий текст, наложения и пустые кадры, но путает метки времени, поэтому это второй взгляд, а не замена своим глазам.
  8. Звук. bash build_audio.sh . trailer.
  9. Рендер. npx hyperframes render --output renders/<name>.mp4.

Голос

Два источника. tts_hub.py берёт Qwen3-TTS на сервисе NeuralDeep: восемь голосов, стиль задаётся обычным текстом, скорость — отдельным параметром. Для русского языка используется ESpeech с RUAccent. tts_gpt_audio.py ходит в openai/gpt-audio через OpenRouter, и автор считает, что он звучит лучше.

У gpt-audio есть особенность: модель отвечает как ассистент («Understood, here is…») и может вслух прочитать служебные теги вроде <line>. Поэтому скрипт задаёт системный промпт «you are a TTS engine», отправляет голый текст, а потом расшифровывает результат, сравнивает с исходником и повторяет попытку.

Дальше реплики нужно вписать в отведённое время. fit_narration.py срезает тишину по краям, а слишком длинные реплики ускоряет через atempo — тембр при этом сохраняется. Больше ×1,12 слышно уже отчётливо, и скрипт помечает такую строку как TOO LONG: нужно сократить текст и переозвучить только её.

Обработка голоса лежит в voice_fx.py. Там шесть пресетов:

пресет что делает
natural ничего не делает
trailer бас +5 дБ на 110 Гц, верх +2,5 дБ на 3 кГц, компрессия 4:1, хвост реверберации 1,4 с на 18%
deep тон на 2 полутона ниже
titan тон на 4 полутона ниже
cathedral хвост реверберации 3,2 с на 45%
radio полоса 350–3400 Гц

Пресеты намеренно сделаны очень разными. Автор пробовал делать их похожими и обнаружил, что после нормализации громкости мелкие правки почти не слышны. Сравнить варианты можно на слух: voice_fx.py fit ab --compare 0.

Музыка и сведение

Музыку можно взять двумя способами. Lyria 3 через OpenRouter стоит 0,08 доллара за трек и 0,04 за 30-секундный фрагмент. Google не любит промпты, где упоминаются AI, видео или продукт: такой запрос возвращает PROHIBITED_CONTENT и деньги не списывает. В промпте описывают только музыку — жанр, темп, инструменты, настроение, «No vocals».

Второй путь — свой ACE-Step 1.5 под лицензией MIT. Подробности разбора в docs/acestep.md. Коротко: вариант XL — это 4 млрд параметров, около 9 ГБ в bf16. Нужно от 12 ГБ видеопамяти с выгрузкой на CPU и квантизацией, либо от 20 ГБ без них. В качестве опорной конфигурации автор приводит одну RTX A4500 20 ГБ: XL вместе с языковой моделью занимает 16,6 ГБ, трек на 60 секунд с размышлениями делается за 78 секунд, два варианта по 60 секунд без размышлений — за 11 секунд. Просить больше двух вариантов сразу нельзя: карта кончается.

Сервер слушает только 127.0.0.1, поэтому к нему идут через ssh-туннель. Управлять им удобно из локальной студии: python3 scripts/music_studio.py поднимает страницу на http://127.0.0.1:8765 с пресетами, историей и кнопкой, которая кладёт трек в проект.

Сведение — mix.py. Голос приводится к −16 LUFS, музыка садится на −27 LUFS и приглушается под голосом боковым компрессором. Трек обрезается по длине ролика, с затуханием 1,5 с в начале и 4 с в конце. Отдельно автор предупреждает: loudnorm внутри пересэмплирует звук в 192 кГц и съедает хвост, поэтому после него обязательно идут aresample и apad.

Синхронизация с битами

Отдельная идея, пришедшая из ролика vibecoder-anthem: монтажные склейки и движения камеры должны попадать в настоящие биты трека, а не в сетку темпа.

beat_map.py разбирает трек через librosa и собирает beats.json с битами, сильными долями и акцентами. Дальше beat-sync.js даёт агенту четыре функции: притянуть примерное время к ближайшему биту, найти следующий бит или следующую долю, собрать акценты в окне и перенести готовую анимацию на другую версию трека.

Две детали, на которые стоит посмотреть. Первая: определитель темпа ошибается на октаву. Трек для ролика про Jev автор просил на 110, а получил 73,8 — это 147,6, поделённое на два. Вторая: сгенерированная музыка темп держит плохо, а ACE-Step держит. Drum and bass на 174 получился 172,3 BPM с разбросом ±5 мс.

Требования и ключи

Нужен Node 22+, FFmpeg и Python 3.10+. Скрипты используют только стандартную библиотеку Python. Рендер идёт через npx hyperframes@0.8.81 — версия зафиксирована в каждом проекте.

Ключи три. ND_API_KEY — доступ к моделям NeuralDeep, работает отовсюду. OPENROUTER_API_KEY — для gpt-audio и Lyria; OpenRouter отдаёт 403 на российских адресах, так что нужен сервер за границей или свой прокси. ACESTEP_API_KEY — если сервер с музыкой закрыт ключом. Всё это лежит в .env, который добавлен в .gitignore.

Что стоит за этим

Репозиторий честно небольшой и свежий. Но в нём собраны грабли, на которые обычно наступают при автоматической сборке видео: как заставить TTS не читать служебные теги, как поймать проглоченное слово, почему сначала голос, а потом вёрстка, почему после loudnorm нужно чинить длину, и почему пресеты обработки голоса должны отличаться сильно, а не на пару децибел.

Есть и правило честности, прописанное прямо в скиллах: если в ролике разбирается чужая модель, на экране нужно отмечать, что подтверждено источником, а что является оценкой. И в посте о видео надо честно перечислить, что именно использовалось — если на одном шаге музыка пришла из Lyria, писать «сделано целиком на моделях NeuralDeep» нельзя.

Источник: https://github.com/vakovalskii/nd-video-studio