---
# System prepended metadata

title: concept-for-animation-in-ffmpeg

---

# 🎬 Deterministic HTML/CSS/JS Animation Rendering

### *Frame-by-Frame Puppeteer → FFmpeg Pipeline with Transparency*

This document describes a workflow for rendering **web-based animations** (HTML/CSS/JS, Canvas, WebGL) into **smooth, high-quality videos with alpha transparency**.
The goal is to replicate “Motion-style” reusable templates (title cards, lower thirds, bumpers) using web technologies.

## 🔥 Why Frame-by-Frame?

Running the animation at real-time inside Chrome and screen-recording leads to:

* stutter, dropped frames
* inconsistent timing
* low-quality capture
* no alpha channel

**Frame-by-frame rendering** creates:

* perfectly stable FPS (30, 60, 120, etc.)
* deterministic animation timing
* super-smooth easing curves
* slower-than-real-time rendering for complex effects
* transparent PNG → alpha video output

This is required for templated, repeatable graphics.

---

# 🧱 Overall Architecture

```
┌──────────────────┐
│ HTML/CSS/JS page │  ← Your animation template
└────────┬─────────┘
         │ Expose a controllable renderFrame(t)
         ▼
┌──────────────────┐     Puppeteer
│ Virtual Browser  │  ← headless Chrome
└───────┬──────────┘
        │ Step time t = frameIndex / fps
        │ Screenshot each frame as PNG (with alpha)
        ▼
┌──────────────────┐     FFmpeg
│ frame_0000.png   │ → encode to ProRes 4444 or WebM VP9 alpha
│ frame_0001.png   │
│ ...              │
└──────────────────┘
```

---

# 🧩 Step 1 — Build a Time-Controlled Animation Template

Create a web page that renders animations based on a **virtual time variable** instead of `performance.now()` or normal `requestAnimationFrame`.

### `renderFrame(t)`

Define a global function in JavaScript:

```js
window.renderFrame = function(t) {
  // t = time in seconds
  
  // Update CSS variables
  document.documentElement.style.setProperty("--t", t);

  // Or animate DOM directly
  const el = document.querySelector("#title");
  el.style.transform = `translateX(${Math.min(0, -200 + t * 400)}px)`;

  // If using Canvas/WebGL, re-render scene at time t
  // shader.uniforms.time = t;
  // renderer.render(scene, camera);
};
```

### Optional: Live mode

So it still animates normally in a browser:

```js
if (!window.IS_PUPPETEER) {
  const start = performance.now();
  function tick() {
    const t = (performance.now() - start) / 1000;
    window.renderFrame(t);
    requestAnimationFrame(tick);
  }
  tick();
}
```

---

# 🧩 Step 2 — Launch Puppeteer and Step Through Frames

Use a Node script to render frames deterministically:

### `render.js`

```js
import puppeteer from "puppeteer";
import fs from "fs";
import path from "path";

const FPS = 60;
const DURATION = 5; // seconds
const TOTAL_FRAMES = FPS * DURATION;

const WIDTH = 1920;
const HEIGHT = 1080;

async function main() {
  const browser = await puppeteer.launch({
    headless: "new",
    defaultViewport: { width: WIDTH, height: HEIGHT },
    args: ["--disable-gpu", "--enable-blink-features=CSSColorSchemeUARendering"]
  });

  const page = await browser.newPage();
  await page.goto("http://localhost:3000/template?name=Alice&subtitle=Fellow");

  await page.addScriptTag({ content: `window.IS_PUPPETEER = true;` });

  // Ensure transparent background
  await page.evaluate(() => {
    document.body.style.background = "transparent";
  });

  for (let i = 0; i < TOTAL_FRAMES; i++) {
    const t = i / FPS;

    // Advance virtual time
    await page.evaluate((t) => window.renderFrame(t), t);

    // Capture frame
    const buffer = await page.screenshot({
      omitBackground: true,               // <-- transparency enabled
      type: "png"
    });

    fs.writeFileSync(
      path.join("frames", `frame_${String(i).padStart(4, "0")}.png`),
      buffer
    );

    console.log(`Rendered frame ${i + 1}/${TOTAL_FRAMES}`);
  }

  await browser.close();
}

main();
```

---

# 🧩 Step 3 — Convert PNG Frames to a Video with Alpha

Use FFmpeg to encode transparency.

## Option A: ProRes 4444 (studio/NLE-friendly)

Great for DaVinci Resolve, FCP, HyperDeck, ATEM:

```bash
ffmpeg -framerate 60 -i frames/frame_%04d.png \
  -c:v prores_ks -profile:v 4 -pix_fmt yuva444p10le \
  titlecard.mov
```

## Option B: WebM VP9 with alpha

Great for web compositing:

```bash
ffmpeg -framerate 60 -i frames/frame_%04d.png \
  -c:v libvpx-vp9 -pix_fmt yuva420p \
  titlecard.webm
```

---

# 🧩 Step 4 — Parameterize Templates (Names, Roles, etc.)

Your template page can use query parameters:

```
/template?name=Alice%20Zhang&subtitle=Expos%20Studio%20Fellow
```

Or you can inject data directly:

```js
await page.evaluate(({ name, subtitle }) => {
  document.querySelector("#name").textContent = name;
  document.querySelector("#subtitle").textContent = subtitle;
}, studentData);
```

Then loop over your class roster:

```js
for (const student of students) {
  await renderStudentLowerThird(student);
}
```

This is the web-based equivalent of **Final Cut/Motion templates**.

---

# 🧩 Step 5 — Folder Structure (Recommended)

```
templates/
  lower-third/
    index.html
    style.css
    script.js  // contains renderFrame(t)

scripts/
  render.js    // Puppeteer frame renderer
  batch.js     // optional, loop over roster

frames/
  ...output PNGs...

output/
  lower-third_Alice.mov
  lower-third_Benito.mov
```

---

# 🧩 Step 6 — Animation Tips for Web → Video Rendering

### Use CSS Variables for Time

```css
#title {
  transform: translateX(calc( var(--t) * 200px ));
  opacity: calc( min(1, var(--t) * 2) );
}
```

### Use deterministic easing

Provide your easing function in JS:

```js
function easeOut(t) {
  return 1 - Math.pow(1 - t, 3);
}
```

### Avoid built-in `transition` or `animation` timing

They're real-time and nondeterministic.
Drive everything with **virtual time** instead.

### Canvas/WebGL also works

Just re-render your scene at each `t`.

---

# 🧩 Optional: Generate 30 Title Cards Automatically

If you have a CSV:

```csv
name,subtitle
Alice Zhang,Expos Fellow
Benito Cruz,Vibe Coding
Chloe Kim,Harvard '27
```

Use:

```bash
node batch.js students.csv --template lower-third
```

The script:

* loads each student
* opens the template with params
* renders frames for 5 seconds
* encodes to transparent video

---

# 🎉 This Pipeline Gives You:

### ✔ Perfectly smooth Motion-quality graphics

### ✔ Deterministic timing

### ✔ Transparency (alpha)

### ✔ Reusable templates with “slots”

### ✔ HTML/CSS/JS as your graphics engine

### ✔ FFmpeg as your compositor

### ✔ Batch export of dozens of variants

### ✔ Easy integration with Resolve/ATEM/HyperDeck or web apps

It’s the cleanest way to produce **next-generation academic/teaching graphics** without touching native GPU frameworks.

---

If you want, I can also produce:

* A working **Next.js page template** with a sample animated lower third
* A full **CLI tool** for your studio that reads Canvas/Airtable roster → exports videos
* A **Makefile** or `npm run render` pipeline
* A **Dockerized** version so LLUFs can use it anywhere

Just say the word.
