I already turn blog posts into Instagram carousels. An OpenRouter call on the server reads a list post and spits out slides, and I wrote about it in how I convert blog lists into carousels. Carousels are easy because they're just images.
Video is the next step, and the one I keep putting off. The idea in my head has been simple for a while: Claude reads a post, writes every scene as JSON, each scene is a small code template, and something joins the frames into an MP4. No timeline editor, no After Effects.
Then HyperFrames showed up. HeyGen open-sourced it with the line "Write HTML. Render video. Built for agents", and it picked up more than 21,000 GitHub stars in a month. That's almost exactly the engine I wanted to build, so I tested it on a real post. I took my system design chai stall post, turned it into a 45-second vertical short with HyperFrames, then rendered the same short again with plain Playwright and ffmpeg to see what the framework actually does for me. Remotion gets a fair look too, including the licence part people skip.
What HyperFrames is, in one paragraph
HyperFrames turns an HTML page into a video. You write normal HTML and CSS, mark which elements appear when with data-start and data-duration, and animate them with a paused GSAP timeline. The renderer opens the page in headless Chrome, jumps to each frame, takes a picture, and ffmpeg stitches the pictures into an MP4, so the same input gives the same output every time. It's Apache-2.0, needs Node 22 or newer plus ffmpeg, and ships as one npm package called hyperframes.
The "built for agents" part is real. hyperframes init drops a CLAUDE.md and an AGENTS.md into the project, and there's a Claude Code plugin:
claude plugin marketplace add heygen-com/hyperframes
claude plugin install hyperframes@hyperframesAfter that, /hyperframes inside Claude Code knows the rules of the format.
What I built
One repo, four files that matter:
| File | Job |
|---|---|
scenes.json | The script. One object per scene, with a type and a duration |
build.mjs | Turns scenes into one HyperFrames composition (short/index.html) |
render-plain.mjs | The no-framework version: Playwright + ffmpeg |
prompts/write-scenes.md | The prompt Claude Code uses to write scenes.json from any post |
Here's the whole flow:
Claude only writes the JSON. The templates, the build script and the renderer do the rest.
The repo is shreyvijayvargiya/blog-to-short. The stack is Node 22, GSAP, HyperFrames 0.8.223, Playwright and ffmpeg. There's no database, and you don't need an API key to render.
Step 1: Claude writes the scenes
I didn't want Claude writing raw HTML for every video. That's where things drift: a different font every time, text running off the screen, a scene that lasts 1.2 seconds. So Claude only writes data, and the design lives in templates.
The short has three scene types: a hook (5 seconds), up to six point scenes (6 seconds each) and an outro (4 seconds). That adds up to 45 seconds.
{
"fps": 30,
"width": 1080,
"height": 1920,
"scenes": [
{ "type": "hook", "duration": 5, "kicker": "system design",
"title": "6 lines.", "accent": "One chai stall.",
"sub": "A reel went viral. 400 people came." },
{ "type": "point", "duration": 6, "n": 1,
"problem": "Slow reads", "fix": "Add a cache",
"story": "The menu goes on a board. Nobody opens the notebook." },
{ "type": "outro", "duration": 4,
"title": "Full post with diagrams", "accent": "ihatereading.in" }
]
}The prompt in prompts/write-scenes.md sets hard limits: three words max for problem and fix so they fit on one line, 11 words max for story, durations that add up to 45, and "use the post's own analogy, don't add facts". Those limits came from the checks in step 3. I didn't guess them.
Step 2: scenes become one HTML file
build.mjs is about 110 lines. For each scene it picks a template, gives every element an id, and adds GSAP tweens at the scene's start time. This is the template and motion for a point scene:
point: (s, i) => `
<p class="count" id="s${i}-c"><span class="y">${String(s.n).padStart(2, "0")}</span> / ${String(points).padStart(2, "0")}</p>
<h2 class="problem" id="s${i}-p">${esc(s.problem)}</h2>
<div class="arrow" id="s${i}-a"></div>
<h2 class="fix" id="s${i}-f"><span class="hl" id="s${i}-h"></span><span class="fx">${esc(s.fix)}</span></h2>
<p class="story" id="s${i}-s">${esc(s.story)}</p>`,point: (i, t) => `
tl.from("#s${i}-p", { x: -120, opacity: 0, duration: 0.5, ease: "power3.out" }, ${t + 0.25});
tl.from("#s${i}-a", { scaleY: 0, transformOrigin: "top center", duration: 0.4 }, ${t + 0.9});
tl.from("#s${i}-h", { scaleX: 0, transformOrigin: "left center", duration: 0.45 }, ${t + 1.3});
tl.from("#s${i}-s", { opacity: 0, y: 30, duration: 0.6 }, ${t + 1.7});`,Each scene ends up as a <section class="clip"> with data-start and data-duration, inside one root element with data-composition-id="main". The timeline gets registered on window.__timelines["main"], and that's the whole contract HyperFrames needs.
Two small choices saved me time later. Fonts and GSAP sit inside the project (short/assets/) instead of loading from a CDN, so a render never depends on the network. And the design is the same dark ihatereading look as my banners: near-black background, one yellow, Inter 900 headlines.
Step 3: check, look, then render
npm run build # scenes.json -> short/index.html
npm run check # lint + runtime + layout + motion + contrastMy first version failed check with eight errors. I had used GSAP to hide and show each scene (autoAlpha), so the plain renderer would know which scene was on screen. HyperFrames manages clip visibility itself, and its linter caught the conflict straight away: "Do not animate these properties on clip elements." I removed those lines and the errors were gone.
The second catch was better. The checker warned that one story line was on screen for 3.86 seconds, under the reading time it wants for 16 words (about half a second plus 0.3 seconds a word). It was right. I cut every story to 11 words or fewer and brought it in a bit earlier, which is where the 11-word rule in the prompt comes from.
It still warns that the film has no sound and that eight scenes in one file should really be sub-compositions. Both are fair, and both are fine for a first version.
Before rendering, npx hyperframes snapshot --at 3,20,38,44 saves PNG frames and a contact sheet, so you can look at the result. That's how I found the outro URL running off both edges of the screen. Then:
npm run render # -> renders/hyperframes.mp4On a 2-core cloud machine it took 109 seconds for 1,350 frames (45 seconds at 30fps). The file is 1080×1920, H.264, 2.0 MB.
The same short without HyperFrames
To see what I was really getting, I wrote the renderer myself. render-plain.mjs opens the same index.html in Playwright and loops over every frame:
for (let f = 0; f < frames; f++) {
const t = f / fps;
await page.evaluate((t) => {
// HyperFrames does this part for you. Without it, it's your job.
document.querySelectorAll(".clip").forEach((el) => {
const s = Number(el.dataset.start), d = Number(el.dataset.duration);
el.style.visibility = t >= s && t < s + d ? "visible" : "hidden";
});
window.__timelines.main.seek(t, false);
}, t);
const png = await page.screenshot({ type: "png" });
ffmpeg.stdin.write(png);
}It took 113 seconds and gave me a 1.2 MB file (different encoder settings, CRF 18). I pulled frames at 7.6 and 20 seconds from both videos and put them side by side. They match.
Same HTML, same frames. The difference is how much of the loop you write yourself.
For one fixed template like mine, 40 lines of Playwright gets you the same video at the same speed, so rendering alone doesn't justify the framework. What you pay for is everything around it:
- the linter that caught my visibility bug and the reading-time problem
- snapshots and contact sheets so an agent can look at its own work
- a preview studio, a block registry, audio and caption tools
- rendering on many workers, in Docker, on AWS Lambda or Google Cloud Run
- skills that teach Claude the format, so it doesn't invent its own
If Claude is the one writing the video, those checks are the difference between "it rendered" and "it's watchable".
Where Remotion fits
Remotion is the older, bigger name here (about 69k stars). It has the same core idea, videos as code, but you write React components instead of HTML. You start with npx create-video@latest, and it renders on your server, on Lambda, on Vercel, or in the browser.
If you already think in React, Remotion feels natural. Your components, props and hooks all carry over, and years of docs, examples and community answers sit behind it.
Read the licence first. Remotion isn't MIT or Apache. From its LICENSE file (version 4.0.539, checked 31 Oct 2026), you can use it free, including commercially, if you are:
- an individual
- a for-profit company with up to 3 employees
- a non-profit
- evaluating it and not using it commercially yet
Bigger for-profit companies need a paid company licence from remotion.pro. I couldn't read the current prices off the pricing page, so check it yourself. The file also says the licence changes slightly in Remotion 5.0.
As a solo builder, Remotion is free for me. For a client with 20 people it isn't, and I'd rather know that before building their video pipeline on it. HyperFrames is Apache-2.0 with no team-size rule, which is part of why it's growing so fast. It even ships a /remotion-to-hyperframes skill for moving projects across.
HyperFrames vs Remotion vs Playwright + ffmpeg
| HyperFrames | Remotion | Playwright + ffmpeg | |
|---|---|---|---|
| You write | HTML, CSS, GSAP (or Lottie, Three.js, CSS animation) | React components | Any HTML, plus the render loop |
| Licence | Apache-2.0 | Free up to 3 employees, company licence above | Apache-2.0 (Playwright) + ffmpeg (LGPL/GPL builds) |
| Setup | npx hyperframes init, Node 22+, ffmpeg, Chrome | npx create-video@latest, Node | npm i playwright, ffmpeg |
| Agent support | Claude Code plugin, CLAUDE.md, lint and checks | Community skills; you write the rules | None, you write the prompt and the checks |
| Checks before render | lint, layout, contrast, reading time, snapshots | Studio preview | Whatever you build |
| Cloud render | Docker, Lambda, Cloud Run, HeyGen cloud | Lambda, Vercel, server, browser | DIY |
| My 45s short | 109 s, 2.0 MB | Not tested | 113 s, 1.2 MB |
| Best for | Agents writing videos, HTML people | React teams, long-lived video products | One template, no extra dependencies |
I didn't port the short to Remotion, so there's no Remotion number in my test. Everything else in the table comes from the repos and docs linked here.
A rough way to pick. Team size matters for Remotion because of its licence.
Watch someone else do it
Science Based AI's video shows the Claude Code + HyperFrames loop on three kinds of Reels: animated bar charts, explainers timed to a script, and glassy title cards. It shows what the skills can do once you stop limiting yourself to fixed templates like I did.

If you want the full process, Colin Blain's Claude + HyperFrames motion design walkthrough goes through script, voiceover, storyboard, animation and sound, with a prompt for each step.
What I'd add next
This is a first version, and it shows. These are the things I want to add:
- Sound. A music bed and a soft click on each highlight. HyperFrames' checker already complains about the silence.
- Voiceover.
hyperframes doctorlists Kokoro as a local text-to-speech option. A voice reading the story lines would make it work with the sound on. - Sub-compositions. One HTML file per scene type, which is what the linter suggests and what makes editing a single scene easy.
- A second layout. Code snippets and diagrams for posts like the databases one.
- Chat editing, like "make scene 3 slower, change the yellow". This is the one I care about most. The scenes JSON makes it cheap because Claude edits data, not a timeline.
I've written before about explaining LLM output with video and built a prompt-to-video app. This is the first time rendering felt like a solved problem. The writing, the pacing and the design still take the work.
Try it
git clone https://github.com/shreyvijayvargiya/blog-to-short
cd blog-to-short && npm install
npx hyperframes browser ensure # or set HYPERFRAMES_BROWSER_PATH to any Chromium
npm run build && npm run check && npm run renderThen open Claude Code in the repo, paste prompts/write-scenes.md with the URL of one of your own posts, and render again. If it breaks, the check output usually tells you why.
The repo sits next to my other open-source projects, which I listed in four open-source repos: Tubelog, Cizle, OpenNote and CRM SaaS.
FAQ
Is HyperFrames free?
Yes. It's Apache-2.0, so you can use it commercially at any company size. HeyGen also sells cloud rendering, but local rendering is free.
Is Remotion free for commercial use?
For individuals, non-profits and for-profit companies with up to 3 employees, yes. Larger for-profit companies need a company licence.
Does HyperFrames need React?
No. It's plain HTML and CSS with a GSAP timeline. Lottie, Three.js, Anime.js and CSS animations work too.
Can I convert HTML to video without a framework?
Yes. Open the page in Playwright, seek your animation frame by frame, screenshot each frame and pipe them into ffmpeg. My version is about 40 lines and gave me the same video.
Does Claude Code have a HyperFrames skill?
Yes. Install the plugin with claude plugin marketplace add heygen-com/hyperframes, then claude plugin install hyperframes@hyperframes, and use /hyperframes.
