# Html To Mp4

> Build a pixel-perfect animated video (MP4) by writing HTML + CSS + JS animations and recording them with headless Chromium. Use when AI video models (Sora / Veo / Runway / Seedance) fail at tasks requiring precise text rendering (especially CJK), UI mockups (chat / iOS / web app screens), data visualizations, or timeline-driven scripted sequences. Cost per video: $0. Iteration cost: ~20 seconds p…

- **Type:** Skill
- **Install:** `agentstack add skill-universaljojo-html-to-mp4-html-to-mp4`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Universaljojo](https://agentstack.voostack.com/s/universaljojo)
- **Installs:** 0
- **Category:** [Web & Browser](https://agentstack.voostack.com/c/web-and-browser)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Universaljojo](https://github.com/Universaljojo)
- **Source:** https://github.com/Universaljojo/html-to-mp4

## Install

```sh
agentstack add skill-universaljojo-html-to-mp4-html-to-mp4
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# HTML → MP4 Animation Skill

Record HTML + CSS + JS animations to MP4 via headless Chromium. Use for any
scenario where AI video models can't deliver — precise text, pixel-accurate UI,
data viz, timeline-scripted sequences.

## When to use this (vs. AI video models)

| Need | Tool |
|------|------|
| Real actors, real locations, camera motion | AI video model (Sora / Veo / Runway) |
| Precise text, chat UI mockups, message-by-message reveal | **This skill** |
| Data viz animations (bar growth, number counting) | **This skill** |
| Logo / slogan end cards | **This skill** (or `overlay_slogan.py`) |
| 3D product renders | Dedicated 3D tools |

**Rule of thumb:** AI video models can't hit precise text or pixel-accurate UI.
If the spec says "this line must read exactly X" or "this button must sit at
position Y", use this skill.

---

## Workflow (5 steps)

### 1. Clarify requirements before coding

Ask the user:

- **Canvas size**:
  - 9:16 portrait → `1080×1920` (TikTok / Reels / Shorts / YouTube Shorts)
  - 16:9 landscape → `1920×1080` (YouTube / web / conference)
  - 1:1 square → `1080×1080` (Instagram feed / square ad)
- **Duration**: how many seconds? > 30s → consider segmenting.
- **Frame-by-frame content**: what happens at each time marker?
- **Visual reference**: does the user have a screenshot / mood board? (Strongly
  recommended.)

### 2. Copy the template

```bash
mkdir -p /path/to/project && cd /path/to/project
cp -r ~/.claude/skills/html-to-mp4/template/* .
npm install                       # first time only
npx playwright install chromium   # first time only
```

### 3. Write `index.html`

The template gives you three load-bearing pieces:

- **Canvas lock**: `body { width: 1080px; height: 1920px; }` (must match
  `record.mjs` VIEWPORT).
- **TIMELINE array**: JS adds `.show` at the right timestamps.
- **CSS transitions**: `.item` has `opacity: 0; transform: translateY(30px)`,
  `.show` reverses to final state.

**Common patterns:**
- Messages / cards appearing one by one → `TIMELINE` + CSS transition.
- Content overflowing the viewport → `translateY` on the parent container (see
  `examples/01-chat-mockup`).
- Number counter → `requestAnimationFrame` + `lerp`.
- SVG line drawing → animate `stroke-dashoffset`.
- Growing bar chart → animate `height` or `scaleY`.

### 4. Record

```bash
node record.mjs
```

Playwright launches headless Chromium → opens `index.html` → waits
`DURATION_MS` → writes webm → ffmpeg transcodes to MP4 → removes webm.

Output: `out/{OUT_NAME}` — H.264 / yuv420p / libx264 / CRF 20 / faststart.

### 5. Iterate

The user says "make the text bolder" / "change red to deep red" / "delay message
3 by half a second":

- Edit the relevant CSS / TIMELINE in `index.html`
- Re-run `node record.mjs` (~20s)
- Zero cost, zero wait (vs. AI video: ~$0.50 per render + minutes of wait +
  possible content moderation hits)

---

## `record.mjs` constants

```js
const VIEWPORT = { width: 1080, height: 1920 };   // canvas size
const DURATION_MS = 16000;                         // recording length (ms)
const OUT_DIR = './out';
const OUT_NAME = 'my_animation.mp4';
```

**Must edit per project:** `VIEWPORT`, `DURATION_MS`, `OUT_NAME`.

---

## HTML authoring rules (for the agent)

### Lock the canvas

```css
html, body {
  margin: 0; padding: 0;
  background: #000;   /* first-frame color — whatever the user wants as the "before" frame */
  overflow: hidden;   /* never let scrollbars leak into the recording */
}
body {
  width: 1080px; height: 1920px;   /* must match record.mjs VIEWPORT */
}
```

### TIMELINE pattern (preferred)

```html
...
...

.msg { opacity: 0; transform: translateY(30px); transition: opacity .35s, transform .35s; }
.msg.show { opacity: 1; transform: translateY(0); }

const TIMELINE = [
  { id: 'm1', at: 500 },
  { id: 'm2', at: 2000 },
  { id: 'm3', at: 4000 },
];
window.addEventListener('load', () => {
  setTimeout(() => {
    TIMELINE.forEach(t => {
      setTimeout(() => document.getElementById(t.id).classList.add('show'), t.at);
    });
  }, 300);   // 300ms initial delay so the recording catches a clean first frame
});

```

### Auto-scroll when content overflows

See `examples/01-chat-mockup/index.html` — instead of using a native scrollbar
(which would show in the recording), the parent container is translated upward
whenever a new message would overflow the viewport.

### Fonts

Prefer system fonts with fallbacks:

```css
font-family: -apple-system, "Segoe UI", "PingFang SC", "Hiragino Sans GB",
             "Noto Sans CJK SC", sans-serif;
```

If you need a specific web font, use `@font-face` with base64-embedded woff2 to
avoid the "first frame started before font loaded" bug.

### Wait for images

Already handled in `record.mjs`:

```js
await page.evaluate(async () => {
  await Promise.all(
    Array.from(document.images).map(img =>
      img.complete ? Promise.resolve()
                   : new Promise(r => { img.onload = img.onerror = r; })
    )
  );
});
```

Don't remove that block. Without it you'll get "animation finished but the image
slot is still empty" corruption.

---

## Slogan overlay (no re-record needed)

If an AI video model produced great footage but the on-screen text is blurry,
use `template/overlay_slogan.py`:

1. Pillow renders a transparent PNG (fine control over font / color / stroke /
   shadow / rotation).
2. ffmpeg composites it onto the video for a time range, with optional fade-in.

Standalone tool — can be used without the rest of this skill.

---

## Common pitfalls

| Symptom | Fix |
|---------|-----|
| `Cannot find package 'playwright'` | `npm install && npx playwright install chromium` |
| webm generated but MP4 fails | Check `which ffmpeg`; `brew install ffmpeg` if missing |
| First frame is black | Recording caught the middle of the 300ms initial delay — add a `waitForTimeout(100)` after `page.goto` |
| Text renders as boxes | Font stack missing — see the CJK-safe stack above |
| Images missing in output | Don't remove the `Promise.all(document.images)` block |
| "Want to change one word, have to re-record" | That's the point — 20s per iteration |

---

## Project layout

```
your-project/
├── package.json           # playwright dependency
├── index.html             # the animation (you / agent write this)
├── record.mjs             # recorder (usually untouched)
├── overlay_slogan.py      # optional: caption overlay
├── assets/                # images / audio / fonts
└── out/                   # output (gitignored)
```

---

## Included examples

- **examples/01-chat-mockup/** — Messenger-style chat UI, messages appearing one
  by one with auto-scroll. `1080×1920 / 14s`
- **examples/02-data-viz/** — Bar chart growing from 0, numbers counting up.
  `1920×1080 / 10s`
- **examples/03-slogan-outro/** — Gold text slogan fade-in on black. `1080×1920 / 5s`
- **examples/04-product-ui/** — App UI interaction mock: button → modal →
  loading → close. `1080×1920 / 12s`

---

## Sharing with teammates

Clone this repo into `~/.claude/skills/html-to-mp4/` on any machine with
Node 18+ and ffmpeg — no further setup. Or copy just the `template/` folder
anywhere and run `npm install && node record.mjs` standalone.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Universaljojo](https://github.com/Universaljojo)
- **Source:** [Universaljojo/html-to-mp4](https://github.com/Universaljojo/html-to-mp4)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-universaljojo-html-to-mp4-html-to-mp4
- Seller: https://agentstack.voostack.com/s/universaljojo
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
