# HTML to Animated WebP / GIF / MP4 (`conscientious_gelato/html-to-animated-webp-gif-mp4`) Actor

Apify Actor that renders a single HTML document or animation and exports it as an animated WebP, GIF, or MP4.

- **URL**: https://apify.com/conscientious_gelato/html-to-animated-webp-gif-mp4.md
- **Developed by:** [Muhammad Abdullah](https://apify.com/conscientious_gelato) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 88.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## HTML to Animated WebP / GIF / MP4

Apify Actor that renders a **single HTML document or animation** and exports it as an **animated WebP, GIF, or MP4**. Provide the HTML via a public URL, raw HTML text, or an uploaded `.html` file — no local setup required.

### Features

- Multiple HTML input methods: URL, raw HTML, uploaded file
- Output formats: animated WebP, GIF, MP4 (H.264)
- Deterministic frame capture using Playwright's fake clock (animations driven by `setTimeout`/`setInterval` advance frame-by-frame)
- Configurable duration, fps, dimensions, quality, loop, real-time capture
- Output saved to the run's default Key-Value Store (`OUTPUT`) plus a `metadata` record and a dataset item
- Runs in a self-contained Docker image (Playwright + Chromium + FFmpeg preinstalled)

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `html` | string | — | **Textarea field.** Provide HTML as a public http(s) URL or raw HTML content (paste the document). Auto-detects URL vs content. |
| `htmlFile` | string | — | **File upload field.** Upload an `.html` file (upload button) or provide a URL/key to one. Use this OR `html`, not both. |
| `outputFormat` | enum | `webp` | `webp`, `gif`, or `mp4`. |
| `duration` | integer | `5` | Animation length in seconds (1–120). |
| `fps` | string | `"25"` | Frame rate: 10, 12, 15, 20, 24, 25, 30, 60. |
| `width` | integer | `1280` | Viewport/output width in px (100–3840). |
| `height` | integer | `720` | Viewport/output height in px (100–2160). |
| `quality` | integer | `80` | Encoding quality 1–100. |
| `loop` | boolean | `true` | Loop animation (WebP/GIF). |
| `useRealTime` | boolean | `false` | Use real wall-clock time instead of a fake clock. Enable for `requestAnimationFrame`-based animations. |

**Provide HTML in either the `html` field (URL or raw content) or the `htmlFile` field (upload/URL/key) — use one, not both.** The Actor auto-detects URL vs content in `html`.

#### Input example (raw HTML)

```json
{
  "html": "<!DOCTYPE html><html><body style='margin:0;background:#0f172a;color:#fff;font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;'><div id='box' style='width:80px;height:80px;background:#2563eb;border-radius:12px;'></div><script>const b=document.getElementById('box');let t=0;setInterval(()=>{t+=0.05;b.style.transform='rotate('+(t*57.3)+'deg)';},40);</script></body></html>",
  "outputFormat": "webp",
  "duration": 4,
  "fps": "25",
  "width": 640,
  "height": 480,
  "quality": 85
}
```

#### Input example (URL)

```json
{
  "html": "https://example.com/animation.html",
  "outputFormat": "mp4",
  "duration": 6,
  "fps": "30",
  "quality": 80
}
```

### Output

Stored in the run's **default Key-Value Store**:

- `OUTPUT` — the encoded file (binary), with the correct `Content-Type` (`image/webp`, `image/gif`, or `video/mp4`).
- `metadata` — JSON with `format`, `duration`, `fps`, `width`, `height`, `quality`, `loop`, `useRealTime`, `frameCount`, `sizeBytes`, `createdAt`.

A single **dataset item** is also pushed with the same summary fields and `outputKey: "OUTPUT"`.

Download the file from the Apify Console run page or via the KV store API.

### Supported HTML animations

The Actor installs a fake browser clock at time 0 and advances it by one frame interval per captured frame. Animations work best when driven by:

- `setTimeout` / `setInterval`
- CSS animations/transitions that the browser evaluates as time advances

Enable `useRealTime` for `requestAnimationFrame`-based animations; they need the real wall clock to advance.

Common libraries/engines that rely on `requestAnimationFrame` and usually need `useRealTime` enabled:

- GSAP (GreenSock)
- anime.js
- Three.js / Babylon.js / PixiJS (WebGL/canvas render loops)
- Lottie (lottie-web)
- Framer Motion / React Spring (motion libraries)
- p5.js, Processing.js
- Velocity.js, Popmotion
- Custom canvas/WebGL render loops using `requestAnimationFrame`

### Limitations

- **Single page only** — no crawling or multi-URL processing.
- **Output size** — Apify Key-Value Store records have a size limit (≈9 MB on most plans). If your animation exceeds it, lower `quality`, `duration`, `fps`, or dimensions. The Actor warns when the output exceeds the guard threshold.
- **Frame cap** — maximum 3600 frames per run (`duration × fps`).
- **HTML size** — input HTML is capped at 5 MB.
- **Fonts/network** — for `html`/`htmlFile`, external resources (fonts, images) must be reachable; the Actor waits for `networkidle` with a timeout.
- **Memory** — 1080p or long animations may need 4 GB. Default is 512 MB.

### Usage

#### Apify Console (UI)

1. Open the Actor page and click **Try for free**.
2. Fill in the input form (provide one HTML source).
3. Click **Start**. When finished, open the run's **Key-Value Store** and download `OUTPUT`.

#### API

```bash
curl -X POST "https://api.apify.com/v2/acts/<USERNAME>~html-to-webp/runs?token=<TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"html":"https://example.com/anim.html","outputFormat":"webp","duration":5}'
```

Then fetch the run's default KV store record `OUTPUT`.

#### Apify CLI

```bash
apify call <USERNAME>/html-to-webp -i '{"html":"<html>...</html>","outputFormat":"gif"}'
```

### Local development

Prerequisites: Node.js 20+, FFmpeg, and Playwright Chromium.

```bash
npm install
npx playwright install chromium
## Provide ffmpeg on PATH.
node src/main.js   # reads input from APIFY_INPUT env or stdin per Apify SDK
```

Run with Apify CLI for realistic local testing:

```bash
npm install -g apify-cli
apify login
apify run
```

### Docker

Build and run locally:

```bash
docker build -t html-to-webp .
docker run --rm -e APIFY_INPUT='{"html":"<html>...</html>","outputFormat":"webp"}' html-to-webp
```

The image is based on `apify/actor-node-playwright-chrome:22-1.64.0-slim` with `ffmpeg` added. It runs as the non-root `myuser`.

### Troubleshooting

- **"ffmpeg is not available"** — the image wasn't built with the Dockerfile in this repo; rebuild.
- **Blank/empty output** — ensure your animation uses time-based timers, or enable `useRealTime` for `requestAnimationFrame`-based animations.
- **Run timed out / OOM** — reduce `duration`, `fps`, or dimensions; raise memory to 4096 MB.
- **KV upload failed (size)** — lower `quality` or shorten the animation.
- **Networkidle timeout** — external resources may be slow; consider inlining assets in `html`.

### Cost & performance notes

- Frame capture is CPU-bound; cost scales with `duration × fps × resolution`.
- MP4 (H.264) is usually the smallest; WebP is a good balance; GIF is largest.
- Use the lowest fps that still looks smooth (15–25 is usually enough for UI animations).

### Security

- Only `http(s)` URLs are accepted for `html` (when a URL is provided) and `htmlFile`.
- HTML is rendered in a sandboxed Chromium instance with `--no-sandbox` disabled features scoped to rendering.
- No user input is interpolated into shell commands (FFmpeg is invoked with an argument array).

### Changelog

#### 1.0.0

- Initial release: HTML (URL or content) and HTML file inputs
- Output formats: animated WebP, GIF, MP4 (H.264)
- Deterministic frame capture via Playwright fake clock
- Configurable duration, fps, dimensions, quality, loop
- Docker image with Playwright + Chromium + FFmpeg

### License

Apache-2.0

# Actor input Schema

## `html` (type: `string`):

Provide the HTML here in either form:

- A public http(s) URL to an HTML page
- Raw HTML content (paste the full document)
  The Actor auto-detects whether it's a URL or raw HTML.

## `htmlFile` (type: `string`):

Upload an .html file (use the upload button) or provide a URL/key to one. Use this instead of the field above.

## `outputFormat` (type: `string`):

Target output format.

## `duration` (type: `integer`):

Length of the animation in seconds.

## `fps` (type: `string`):

Capture frame rate. Higher = smoother but larger output.

## `width` (type: `integer`):

Viewport/output width in pixels.

## `height` (type: `integer`):

Viewport/output height in pixels.

## `quality` (type: `integer`):

Encoding quality (1-100). Higher = better quality, larger file.

## `loop` (type: `boolean`):

Whether the animation should loop (WebP/GIF).

## `useRealTime` (type: `boolean`):

Capture frames using real elapsed time instead of a fake clock. Enable for requestAnimationFrame-based animations (e.g., Three.js, Canvas); disable (default) for deterministic setTimeout/setInterval animations.

## Actor input object example

```json
{
  "html": "<!DOCTYPE html><html><body style=\"margin:0;background:#0f172a;color:#fff;font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;\"><div id=\"box\" style=\"width:80px;height:80px;background:#2563eb;border-radius:12px;\"></div><script>const b=document.getElementById('box');let t=0;setInterval(()=>{t+=0.05;b.style.transform='rotate('+(t*57.3)+'deg)';},40);</script></body></html>",
  "outputFormat": "webp",
  "duration": 5,
  "fps": "25",
  "width": 1280,
  "height": 720,
  "quality": 80,
  "loop": true,
  "useRealTime": false
}
```

# Actor output Schema

## `outputFile` (type: `string`):

The rendered animation (WebP, GIF, or MP4) stored in the default key-value store.

## `metadata` (type: `string`):

JSON metadata about the render: format, duration, fps, dimensions, quality, loop, frame count, size, and creation time.

## `dataset` (type: `string`):

Summary item pushed to the default dataset with format, dimensions, frame count, and size.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "html": "<!DOCTYPE html><html><body style=\"margin:0;background:#0f172a;color:#fff;font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;\"><div id=\"box\" style=\"width:80px;height:80px;background:#2563eb;border-radius:12px;\"></div><script>const b=document.getElementById('box');let t=0;setInterval(()=>{t+=0.05;b.style.transform='rotate('+(t*57.3)+'deg)';},40);</script></body></html>"
};

// Run the Actor and wait for it to finish
const run = await client.actor("conscientious_gelato/html-to-animated-webp-gif-mp4").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "html": "<!DOCTYPE html><html><body style=\"margin:0;background:#0f172a;color:#fff;font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;\"><div id=\"box\" style=\"width:80px;height:80px;background:#2563eb;border-radius:12px;\"></div><script>const b=document.getElementById('box');let t=0;setInterval(()=>{t+=0.05;b.style.transform='rotate('+(t*57.3)+'deg)';},40);</script></body></html>" }

# Run the Actor and wait for it to finish
run = client.actor("conscientious_gelato/html-to-animated-webp-gif-mp4").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "html": "<!DOCTYPE html><html><body style=\\"margin:0;background:#0f172a;color:#fff;font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;\\"><div id=\\"box\\" style=\\"width:80px;height:80px;background:#2563eb;border-radius:12px;\\"></div><script>const b=document.getElementById('\''box'\'');let t=0;setInterval(()=>{t+=0.05;b.style.transform='\''rotate('\''+(t*57.3)+'\''deg)'\'';},40);</script></body></html>"
}' |
apify call conscientious_gelato/html-to-animated-webp-gif-mp4 --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,conscientious_gelato/html-to-animated-webp-gif-mp4"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/hBCd2bnmlWwSUDzV9/builds/a5XtoxdkWaTgMvQ9B/openapi.json
