Video Studio: рендерим Reels кодом на Remotion и HyperFrames
Если ты думаешь, что видео — это After Effects, Premiere и ручная работа на несколько часов, то этот пост перевернёт картину. Мы построили video-studio — песочницу, в которой видео пишется как код, рендерится командой в терминале и деплоится на домен с HTTPS и basic auth. Два независимых движка, один сервер, ноль ручного монтажа.
Давай расскажу, что умеем.
Что такое video-studio и зачем два движка
Проект живёт в /home/deploy/projects/video-studio и состоит из двух принципиально разных подходов к программному видео.
Remotion — это React-видео. Буквально: пишешь компоненты на TypeScript, описываешь анимации через хуки, а Remotion рендерит их в mp4 через headless Chrome. Стек: Remotion 4.0.515 + React 19 + Tailwind v4 + TypeScript. Есть визуальная студия с таймлайном, hot-reload и кнопкой Render прямо в браузере.
HyperFrames — HTML-composition движок от HeyGen. Другая философия: описываешь слайды-фреймы в декларативном формате, движок собирает их в видео. Есть свой CLI с командами lint, check, render и встроенной проверкой контраста.
Почему оба? Потому что задачи разные. Remotion даёт полный контроль через React-компоненты — идеально для сложных анимаций и кастомных сцен. HyperFrames быстрее в прототипировании слайдовых форматов. Мы хотели понять оба инструмента изнутри, поэтому сделали benchmark: один и тот же Reels-ролик на каждом движке.
Remotion: React-компоненты как кадры
Начали с Remotion. Задача — собрать вертикальный Reels (1080×1920, 30fps) с TikTok-стайл субтитрами, анимированными оверлеями и несколькими сценами.
Первый шаг — транскрипция аудио. Вытащили речь из исходника, получили 91 слово за 36.288 секунды. На основе транскрипта сгенерировали captions.json с таймингами для каждого слова.
Структура проекта в bench/remotion-agent/:
src/
Root.tsx — регистрация композиций
ViralReels.tsx — главная композиция
CaptionOverlay.tsx — субтитры с подсветкой
scenes/ — компоненты сцен
Главная композиция ViralReels — это набор <Sequence> блоков, каждый из которых монтирует свою сцену в нужный временной диапазон:
// ViralReels.tsx (упрощённо)
export const ViralReels: React.FC = () => {
const { durationInFrames } = useVideoConfig();
return (
<AbsoluteFill style={{ background: '#0f0f0f' }}>
<Audio src={staticFile('voice.mp3')} />
<Sequence from={0} durationInFrames={90}>
<HookScene />
</Sequence>
<Sequence from={90} durationInFrames={180}>
<BeforeAfterScene />
</Sequence>
<Sequence from={270} durationInFrames={durationInFrames - 270}>
<CtaScene />
</Sequence>
<CaptionOverlay captions={captions} />
</AbsoluteFill>
);
};Субтитры — отдельный компонент поверх всего. Логика: берём текущий кадр через useCurrentFrame(), находим активное слово по таймингу, подсвечиваем его акцентным цветом. Первая версия имела баг — между страницами субтитров были пустые промежутки, где текст исчезал. Починили, растянув каждую страницу до начала следующей:
// CaptionOverlay.tsx — фикс пустых промежутков
const pages = captions.map((page, i) => ({
...page,
endTime: captions[i + 1]?.startTime ?? page.endTime,
}));После TypeScript-проверки (tsc --noEmit — 0 ошибок) и ESLint (0 warnings) запустили рендер:
npx remotion render ViralReels \
/home/deploy/projects/video-studio/deliver/bench-remotion-agent.mp4Результат: 1089 кадров, H264+AAC, 1080×1920, 30fps. Проверка аудио показала mean −20.1 dB, max −3.8 dB — не тишина. Три контрольных кадра (начало, середина, конец) — разные, значит картинка движется. Повторная транскрипция рендера совпала с исходником по порядку и таймингу.
HyperFrames: декларативные фреймы
Параллельно собрали тот же Reels на HyperFrames в bench/hf-agent/.
Рабочий процесс здесь другой. Сначала читаешь skill-документацию (skill://hyperframes), потом описываешь композицию в декларативном формате — фреймы, слои, тайминги. CLI сам проверяет структуру.
Первый прогон hyperframes lint дал 0 ошибок и 3 предупреждения о плотности контента — это было намеренно, оставили. Затем hyperframes check — 12 проверок контраста, 2 упали: бейджи на светлом фоне не проходили WCAG AA.
Фикс простой — добавили непрозрачный тёмный фон под бейджи:
// до
{ "background": "rgba(0,0,0,0.4)", "color": "#ffffff" }
// после
{ "background": "#1a1a1a", "color": "#ffffff" }После фикса hyperframes check прошёл с 0 ошибок по всем 12 контрастным проверкам. Рендер:
hyperframes render \
--output /home/deploy/projects/video-studio/deliver/bench-hyperframes-agent.mp4Аудио и движение подтверждены аналогично Remotion-версии.
Деплой: две студии на одном домене
Оба движка имеют веб-UI. Remotion Studio — визуальный редактор с таймлайном, деревом слоёв и кнопкой Render. HyperFrames — своя студия с превью фреймов.
Задача: выставить обе на video.pashavin.ru/remotion и video.pashavin.ru/hyperframes с авторизацией.
Проблема с path-хостингом
Первая попытка — разместить студии в подпапках одного домена — провалилась сразу. Оба приложения тянут ассеты от корня (/api, /static, etc.), и оба используют /api — прямая коллизия. Remotion ещё и занимает root-пути под имена композиций (/HelloWorld, /OnlyLogo). Переписывать их JS ради path-prefix — не вариант.
Решение: каждая студия получает свой поддомен. Но сначала разобрались с инфраструктурой.
Схема сети
DNS *.pashavin.ru указывает на 45.91.55.56 — это RU-бридж (SNI passthrough, обход DPI). Бридж прозрачно проксирует трафик на Hetzner-сервер 135.181.115.234, где живёт Caddy. Фаервол снаружи открывает только 80/443 — значит Caddy единственная дверь, basic_auth не обойти по прямому порту.
DNS и systemd
Создали DNS-запись video.pashavin.ru через Vercel API (там управляется зона). Пока TTL расходился — оформили студии как systemd-сервисы:
# /etc/systemd/system/remotion-studio.service
[Unit]
Description=Remotion Studio
After=network.target
[Service]
Type=simple
WorkingDirectory=/home/deploy/projects/video-studio/remotion
ExecStart=/usr/bin/npm run dev -- --port 3457
Restart=always
User=deploy
[Install]
WantedBy=multi-user.targetАналогично для HyperFrames на порту 3458. Оба сервиса подняты и живут без активной сессии.
Caddy: конфиг без правок главного файла
Главный Caddyfile содержит import sites/*.caddy — добавили отдельный файл, не трогая основной конфиг:
# /etc/caddy/sites/video-studio.caddy
video.pashavin.ru {
basicauth /* {
# [СКРЫТО] — хеш пароля
}
handle /remotion* {
reverse_proxy localhost:3457
}
handle /hyperframes* {
reverse_proxy localhost:3458
}
}Валидация: caddy validate --config /etc/caddy/Caddyfile — OK.
Инцидент с reload
Первый systemctl reload caddy упал: Caddy не смог создать лог-файл из-за прав на директорию. Systemd залип в состоянии reloading (Type=notify так и не получил READY) — все последующие reload отваливались по таймауту.
Сам Caddy при этом жил и обслуживал трафик. Применили конфиг без даунтайма через admin API:
curl -X POST http://localhost:2019/load \
-H 'Content-Type: text/caddyfile' \
--data-binary @/etc/caddy/CaddyfileПосле этого починили права на лог-директорию и восстановили нормальную работу systemctl reload.
Сертификаты и финальная проверка
Caddy автоматически выпустил TLS-сертификаты через Let's Encrypt. Проверка через публичный домен показала 404 от Vercel — оказалось, локальный резолвер закешировал старый wildcard-ответ. После истечения TTL (660с) всё заработало корректно.
Оба UI доступны по HTTPS с basic auth:
https://video.pashavin.ru/remotion→ Remotion Studiohttps://video.pashavin.ru/hyperframes→ HyperFrames Studio
Результат
Что в итоге работает:
- Два рендер-движка в одном репозитории — Remotion (React) и HyperFrames (декларативный)
- Два готовых Reels в
deliver/— один на каждом движке, оба с аудио, субтитрами и анимациями - Два веб-UI на продакшн-домене с HTTPS и авторизацией
- systemd-сервисы — студии живут после перезагрузки сервера
- Caddy без даунтайма — конфиг применяется через admin API
Время рендера Remotion-ролика (1089 кадров, 36 секунд) — в пределах минуты на сервере. HyperFrames сопоставимо.
Выводы и уроки
Главный урок — программное видео реально работает в продакшне. Не как игрушка, не как демо, а как инструмент, который можно автоматизировать, версионировать в git и деплоить как любой другой сервис. Это меняет подход к контент-производству: вместо «открыть Premiere, потыкать мышкой» — git commit, render, готово.
Второй урок — два движка лучше одного, если понимаешь зачем. Remotion даёт максимальный контроль через React: хочешь кастомную физику, сложные переходы, данные из API прямо в кадре — пожалуйста. HyperFrames быстрее для слайдовых форматов с готовыми шаблонами. Мы не выбирали «лучший» — мы поняли, когда использовать каждый.
Третий урок — path-хостинг dev-студий не работает без боли. Оба приложения проектировались для корня домена: /api, статика, роутинг — всё от /. Попытка засунуть их в подпапку требует либо переписывания JS, либо реверс-прокси с rewrite-магией. Проще дать каждому свой поддомен или путь через отдельный vhost. Мы выбрали второй вариант — и не пожалели.
Четвёртый урок — Caddy admin API спасает когда systemd reload ломается. Классическая ситуация: сервис завис в промежуточном состоянии, systemctl reload не работает, но сам Caddy живой. curl -X POST localhost:2019/load применяет конфиг мгновенно без перезапуска и без даунтайма. Держи этот трюк в голове.
Пятый — проверяй контраст автоматически. HyperFrames встроил WCAG-проверку в CLI, и это поймало два реальных бага с бейджами до рендера. В Remotion такого из коробки нет — но можно добавить через axe-core или ручную проверку палитры. Видео смотрят на разных экранах, и плохой контраст убивает восприятие.
Дальше планируем: автоматический рендер по webhook (пришёл скрипт → отрендерилось → загрузилось), шаблоны для разных форматов (Shorts, Stories, горизонталь), и возможно — генерацию скриптов через LLM прямо в пайплайне. Но это уже следующая глава.
Ссылки

AI-инженер, предприниматель, маркетолог. Основатель feberra.com и x10seo.ru. 13 лет в перфоманс-маркетинге, 3 года в системной интеграции AI в бизнес.