How I built a desktop app that turns HTML into broadcast-ready video — frame for frame, the same way every time.
Nobody talks about this enough. The teams producing the most content aren't the ones with the best ideas. They're the ones with the shortest distance between "I want to say this" and "it's live."
We had the ideas. Forty hooks in a doc. A content calendar that looked great on Monday.
Then every one of them hit the edit. And that's where they died.
I've watched this loop kill more content than bad copy ever did.
You write a script. You brief an editor. Three days later you get a cut. The headline's wrong. You send notes. Two days later, v2. Now you want it in square for the feed and vertical for Reels. That's two more renders and another wait.
By the time the video is right, the thing that made you want to make it is stale.
And here's the part that really got me: the actual change was usually seven words. One line of text. A number that moved. We were spending a week of calendar time to change seven words.
The insight is dumb once you see it. Every video we make is text, shapes, and motion. A browser already does text, shapes, and motion better than anything — it's the most battle-tested rendering engine on the planet, and it's free.
So the source of truth for our videos isn't a project file locked inside an editor. It's an HTML file.
Change the copy, change the video. Change one number, re-render. No round trip. No one to brief.
The only thing missing was a way to turn that HTML into an actual MP4 that Instagram would accept. So I built it.
Animation Studio is a Mac app. You point it at an HTML animation, it renders a pixel-accurate video. That's the whole pitch.
This is the one technical bit worth understanding, because it's the reason the output is trustworthy.
The obvious way to record a web animation is to play it and screen-capture. That approach is garbage. If your machine stutters, the video stutters. Render the same file twice, get two different videos. You can't build a content system on something that unreliable.
So the studio doesn't play anything. It scrubs.
It loads your animation in a headless browser, freezes every animation on the page, then walks through it one frame at a time — jump to 0.0417 seconds, screenshot, jump to 0.0833 seconds, screenshot — and hands the pile of frames to ffmpeg.
A 45-second reel at 24fps is 1,080 frames. Every one of them is placed deliberately. Nothing is captured in real time, so nothing can drift.
The upshot: the same file always produces the same video. On my machine, on a laptop, at 2am while something else is compiling. That determinism is the whole foundation — everything else in the app is only useful because you can trust the output.
Plain CSS animations work with zero effort. For JavaScript timelines, a file opts in by exposing one hook:
gsap.ticker.lagSmoothing(0); const tl = gsap.timeline({ paused: true }); // …build the timeline… window.__studioSeek = (seconds) => tl.seek(seconds); window.__studioDuration = () => tl.duration(); window.__studioReady = true;
That's the contract. Anything that can be asked "show me what you look like at 12.5 seconds" can be rendered.
And every file carries its own settings, so a saved animation reopens exactly as you left it:
<meta name="render-name" content="interstaff_outbound_one_call"> <meta name="render-duration" content="45"> <meta name="render-fps" content="24"> <meta name="render-width" content="1080"> <meta name="render-height" content="1920"> <meta name="render-audio" content="vo_one_call_MASTER.wav">
Here's where it stopped being a renderer and started being a studio.
The Text tab pulls every piece of copy out of the animation and lists it as plain fields. Scene 3, line 1. The eyebrow. The reply. You rewrite the words, hit ⌘S, and the file updates. You never see a tag.
That's the seven-words problem, solved. It's now about eleven seconds of work.
But sometimes the copy is fine and the layout is wrong. So there's a second mode: click Select, then click anything in the preview. Drag it. Resize it. Retype it.
The part I'm proudest of is in the corner of that inspector: writes to line 418. You're not editing a copy or a layer. You're editing the actual HTML file, in place. Move a headline with your mouse, and the source changes underneath you.
Every platform wants a different shape. Vertical for Reels and TikTok. Square for the feed. Landscape for YouTube.
Historically that's three separate exports and three separate waits. In the studio it's three toggles — Square, Landscape, Story HD — next to the Render button. Hit render once and the variants queue up alongside the original. The app measures the animation, scales it to fit each canvas, and letterboxes what's left over.
Renders run in parallel, in the background, while you keep working on the next one.
Two things I care about in that queue. Finished jobs reload straight back into the studio with their exact settings, so a re-render is one click. And when something breaks, it names the cause — timed out waiting for window.__studioReady — so you know where to look.
Not every deliverable is a finished video. Sometimes you need a lower-third to drop over real footage in Premiere, with a genuine alpha channel.
ProRes 4444 for Premiere and Resolve. WebM for the web. PNG sequence when someone downstream insists. Same animation, no rebuild.
Everything lands in one folder and shows up in the Gallery with its format, dimensions and size.
The honest summary of what this did to our workflow:
| Task | Before | Now |
|---|---|---|
| Copy change on a finished video | Brief, wait, review, repeat | Retype the field, ⌘S, render |
| Getting three aspect ratios | Three exports, three waits | Three toggles, one click |
| Re-rendering an old video | Find the project file, hope it opens | One click from the queue |
| A transparent overlay for Premiere | A separate ask entirely | A dropdown |
| Same file rendered twice | Two slightly different videos | Byte-for-byte identical |
| Who can change a video | Whoever has the editor open | Anyone who can type |
That last row is the one that changed how we work. Video stopped being a specialist request and became something anyone on the team could touch.
The app runs entirely on the machine — Chromium and ffmpeg ship inside it, so it renders offline with nothing installed and nothing phoning home. There's also a command line, which means our AI agents can render video with no human in the loop:
./studio render interstaff_outbound_one_call.html --fmt prores --overlay
Every tool in the video industry was built on an assumption: that video is hard to produce, so the work is worth serializing through a specialist with a timeline open.
That assumption is where the revision loop comes from. It's where "let's just use the one we have" comes from. It's why most companies publish a fraction of what they mean to.
When the source of truth for a video is a text file, that assumption breaks, and things that were impossible get boring:
The moat was never creative talent. It was access to the pipeline. Whoever could open the editor decided what shipped, and everyone else waited in line.
That line is what disappears. Not editors — editors are still far better than me at editing. What disappears is the queue in front of them for the routine stuff: the stat card, the before/after, the lower-third, the twelfth variation of a hook that's already working.
The teams that figure this out won't just make videos faster. They'll make different decisions, because they'll stop asking "is this worth an edit?" and start asking "what do we actually want to say?"
So — how many of your ideas died in a queue this month, and would you still call that a creative problem?