HyperFrames is a TypeScript framework that takes HTML, CSS, and GSAP animations and produces seekable MP4 files. It runs locally via CLI, integrates with AI agents through MCP and skills.sh, and ships with a hosted playground. The core promise is deterministic video output from code, which means agents can write HTML and get frame-perfect video without manual timeline editing.
The project has 42K stars and is trending #11 on GitHub for TypeScript. HeyGen built it to make video generation programmatically addressable. The architecture is Puppeteer for DOM rendering, GSAP for animation timing, and FFmpeg for encoding. The interesting part is how it guarantees determinism when each layer is async by default.
Why HTML-to-Video Matters for Agents
Most video generation tools target human designers. You drag keyframes, adjust curves, export. Agents need something different: a function that takes structured input and returns a file. HyperFrames treats video as a build artifact. You write HTML with animation code, run a command, get an MP4.
This shifts video from creative workflow to infrastructure. An agent can generate a data visualization, encode it as HTML with GSAP transitions, and call HyperFrames to render. No GUI, no manual export, no non-deterministic output. The same HTML always produces the same video.
The MCP server integration means agents can invoke HyperFrames as a tool. The skills.sh distribution packages it as a skill set that coding agents can install and call. This is video rendering as a first-class agent capability, not a side effect of screen recording.
Architecture: Puppeteer, GSAP, and FFmpeg
HyperFrames chains three components:
- Puppeteer launches a headless Chromium instance and loads your HTML.
- GSAP (GreenSock Animation Platform) runs animations inside the browser. GSAP is deterministic because it uses explicit timelines, not CSS transitions or requestAnimationFrame drift.
- FFmpeg encodes the captured frames into MP4 with H.264 or other codecs.
The pipeline looks like this:
// Simplified flow (not actual HyperFrames source)
async function render(htmlPath: string, outputPath: string) {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto(`file://${htmlPath}`);
// Wait for GSAP timeline to be ready
await page.evaluate(() => window.gsap.timeline().totalDuration());
// Capture frames at fixed intervals
const frames = [];
for (let t = 0; t < duration; t += frameInterval) {
await page.evaluate((time) => window.gsap.globalTimeline.seek(time), t);
const screenshot = await page.screenshot({ encoding: 'binary' });
frames.push(screenshot);
}
await browser.close();
// Pipe frames to FFmpeg
const ffmpeg = spawn('ffmpeg', ['-framerate', '30', '-i', '-', outputPath]);
frames.forEach(frame => ffmpeg.stdin.write(frame));
ffmpeg.stdin.end();
}
The key is gsap.globalTimeline.seek(time). GSAP lets you jump to any point in the animation timeline without playing it in real time. Puppeteer captures a screenshot at each seek position. FFmpeg stitches the screenshots into video.
Determinism and Frame Timing
Determinism breaks if any of these layers drift:
- Puppeteer rendering: DOM layout must be stable. Fonts, images, and CSS must load before capture starts. HyperFrames waits for a ready signal.
-
GSAP animations: GSAP timelines are deterministic by design. Seeking to
t=1.5salways produces the same visual state. CSS transitions orrequestAnimationFrameloops would not. - FFmpeg encoding: Same input frames and codec settings produce the same output. HyperFrames defaults to H.264 with fixed bitrate to avoid encoder variance.
The failure mode is async resource loading. If a web font or image loads after the first frame capture, the video will show a flash. HyperFrames mitigates this with preload checks and a configurable wait time.
Another failure mode is GSAP timeline complexity. If your animation uses random values or Date.now(), it is not deterministic. HyperFrames does not enforce purity. It assumes your HTML is reproducible.
MCP Server and Agent Integration
HyperFrames ships an MCP server that exposes video rendering as a tool. The server accepts HTML strings or file paths, renders them, and returns MP4 URLs or base64 blobs.
The MCP server holds no persistent state between calls. Each render is a fresh Puppeteer instance. This avoids state leakage but means you cannot reuse browser sessions for performance. The trade-off is correctness over speed.
The skills.sh integration packages HyperFrames as a skill set. An agent can install it with npx skills add heygen-com/hyperframes and then call /hyperframes commands. The core skill set includes:
-
/hyperframes createto generate a new project -
/hyperframes renderto produce MP4 from HTML -
/hyperframes previewto open the playground
The skills are non-interactive. An agent can invoke them in a script without human input. This is the key difference from CLI tools that prompt for options.
Codec Choices and Output Shape
HyperFrames defaults to H.264 in MP4 container. You can override with FFmpeg flags. Common options:
| Codec | Use Case | File Size | Compatibility |
|---|---|---|---|
| H.264 | Default, broad compatibility | Medium | High |
| H.265/HEVC | Smaller files, modern devices | Small | Medium |
| VP9 | Web-native, YouTube-friendly | Small | Medium |
| ProRes | Editing workflows, lossless | Large | Low |
The FFmpeg pipeline accepts custom codec strings. If you need alpha channel, you can use VP9 with transparency or ProRes 4444. HyperFrames does not abstract codec selection. You pass raw FFmpeg arguments.
The output shape is a single MP4 file. No intermediate frames are saved unless you specify a temp directory. This keeps disk usage low but makes debugging harder. If a frame looks wrong, you cannot inspect the raw screenshot.
Error Boundaries and Observability
HyperFrames surfaces three error classes:
- Puppeteer launch failures: Missing Chromium, sandbox issues, out of memory.
- Rendering timeouts: HTML does not signal ready, fonts do not load, GSAP timeline is undefined.
- FFmpeg encoding errors: Invalid codec, disk full, corrupted frame data.
The CLI logs each error with a stack trace. The MCP server returns error codes. There is no built-in retry logic. If Puppeteer crashes, the render fails. You must handle retries in the orchestration layer.
Observability is minimal. HyperFrames does not emit metrics or traces. You can wrap the CLI in a script that logs start/end times and file sizes. The MCP server does not expose health checks or render queue depth.
For production use, you would add:
- A queue to serialize render jobs and limit concurrency.
- Prometheus metrics for render time, frame count, and error rate.
- Distributed tracing to correlate agent tool calls with render jobs.
HyperFrames is a library, not a service. It does not ship with these features.
Deployment Shape
HyperFrames runs wherever Node.js 22+ and FFmpeg are available. Common deployment patterns:
- Local CLI: Developer machine, CI/CD pipeline, cron job.
- Docker container: Isolated environment with Chromium and FFmpeg pre-installed.
- Serverless function: AWS Lambda, Google Cloud Run. Watch cold start time and memory limits. Puppeteer + FFmpeg can exceed 512MB.
- Kubernetes job: Batch rendering with pod autoscaling. Use ephemeral storage for temp frames.
The Docker image is not official. You build your own with:
FROM node:22-slim
RUN apt-get update && apt-get install -y \
chromium \
ffmpeg \
fonts-liberation
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npx", "hyperframes", "render", "input.html", "output.mp4"]
Serverless is tricky. Puppeteer needs /tmp write access for Chromium cache. FFmpeg writes temp frames. You must configure both to use /tmp and clean up after each invocation.
Security Boundaries
HyperFrames executes arbitrary HTML in a headless browser. If an agent generates malicious HTML, it can:
- Exfiltrate data via fetch() or WebSocket.
- Consume CPU with infinite loops or heavy animations.
- Write files via Puppeteer's download API (if enabled).
The MCP server does not sandbox HTML. It trusts the agent. For untrusted input, you must:
- Run Puppeteer in a separate container with no network access.
- Set a timeout on page.goto() and page.evaluate().
- Disable JavaScript if you only need static HTML (breaks GSAP).
FFmpeg is also a risk. It parses image and video files. A malicious image can exploit codec vulnerabilities. Keep FFmpeg updated and run it in a restricted user context.
When to Use HyperFrames
Use HyperFrames when:
- You need programmatic video generation from structured data.
- Your agent workflow already produces HTML or can learn to.
- You want deterministic output for testing or reproducibility.
- You can tolerate the Puppeteer + FFmpeg dependency chain.
Avoid HyperFrames when:
- You need real-time video streaming (this is offline rendering).
- Your animations are not GSAP-based (CSS transitions drift).
- You cannot install Chromium and FFmpeg in your environment.
- You need sub-second render times (Puppeteer startup is slow).
Technical Verdict
HyperFrames makes video a build artifact. The Puppeteer + GSAP + FFmpeg pipeline is well-understood and deterministic. The MCP server and skills.sh integration position it as an agent tool, not a human tool.
The architecture is simple: no custom rendering engine, no GPU acceleration, no distributed job queue. This keeps the codebase small but limits performance. Rendering a 60-second video at 30fps means 1,800 Puppeteer screenshots. Expect minutes, not seconds.
The lack of built-in observability and error recovery means you will build your own orchestration layer. HyperFrames is a library. Treat it like FFmpeg: a powerful primitive that needs wrapping.
If your agent workflow generates data visualizations, social media clips, or explainer videos, HyperFrames is a strong fit. If you need real-time rendering or sub-second latency, look elsewhere.
Top comments (0)