AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Mk:html Video

skill-ngocsangyem-meowkit-html-video · by ngocsangyem

Render an HTML/CSS/JS template to a local MP4 video by capturing frames with headless Chromium (via Playwright) and encoding them with ffmpeg. Use when "render my animation as a video", "export HTML to MP4", "make a video from my web template", or "capture CSS animation as MP4". NOT for recording live browser interactions (use `agent-browser record start`); NOT for screen recording; NOT for AI vi…

No reviews yet
0 installs
42 views
0.0% view→install

Install

$ agentstack add skill-ngocsangyem-meowkit-html-video

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-ngocsangyem-meowkit-html-video)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Mk:html Video? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

HTML → MP4 Render Pipeline

Converts a local HTML/CSS/JS file into an MP4 video by:

  1. Writing a Playwright capture script
  2. Capturing PNG frames (headless Chromium) at a specified frame rate
  3. Encoding the frames to MP4 with ffmpeg

Prerequisites

# Check dependencies
ffmpeg -version 2>&1 | head -1            # Must be installed
npx playwright --version 2>/dev/null      # Or: node -e "require('@playwright/test')"

# Install Playwright Chromium if missing
npx playwright install chromium

If ffmpeg is not installed:

  • macOS: brew install ffmpeg
  • Linux: apt install ffmpeg / dnf install ffmpeg

HTML Template Contract

The template must expose an optional JS hook so the capture script can step through frames:

// In your HTML  block — optional but required for JS-driven animations
window.onFrame = function(frameIndex, totalFrames) {
  // Advance animation to this frame's position
  // e.g. set CSS custom property, GSAP seek, requestAnimationFrame position
  const t = frameIndex / totalFrames;
  document.documentElement.style.setProperty('--progress', t);
};

If window.onFrame is absent, the capture script uses setTimeout delays between frames (suitable for CSS animations playing at natural speed).

Usage

# Render a 5-second animation at 30 fps, 1920×1080
npx mk:html-video render template.html output.mp4

# Custom parameters
npx mk:html-video render template.html output.mp4 \
  --fps 60 \
  --duration 10 \
  --width 1280 \
  --height 720

# With a background colour (for templates with transparent backgrounds)
npx mk:html-video render template.html output.mp4 --background "#000000"

> mk:html-video is a skill, not an npm package. Run the workflow steps below directly.

Step-by-Step Workflow

Step 1 — Write the capture script

Write this to a temporary file (e.g. /tmp/mk-html-video-capture.mjs):

// Playwright frame-capture script for mk:html-video
// Usage: node capture.mjs       [bgColor]

import { chromium } from '@playwright/test';
import { fileURLToPath } from 'url';
import path from 'path';
import fs from 'fs';

const [,, htmlFile, frameDir, fps, duration, width, height, bgColor] = process.argv;

const totalFrames = Math.ceil(Number(fps) * Number(duration));
const absHtml = path.resolve(htmlFile);

fs.mkdirSync(frameDir, { recursive: true });

const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();

await page.setViewportSize({
  width: Number(width) || 1920,
  height: Number(height) || 1080,
});

if (bgColor) {
  await page.addStyleTag({ content: `body { background: ${bgColor} !important; }` });
}

await page.goto(`file://${absHtml}`, { waitUntil: 'networkidle' });

// Allow CSS animations to settle
await page.waitForTimeout(200);

const hasFrameHook = await page.evaluate(() => typeof window.onFrame === 'function');

for (let i = 0; i  window.onFrame(fi, total),
      { fi: i, total: totalFrames }
    );
  } else {
    // No hook: let natural CSS time elapse between captures
    await page.waitForTimeout(1000 / Number(fps));
  }

  const pad = String(i).padStart(6, '0');
  await page.screenshot({
    path: path.join(frameDir, `frame_${pad}.png`),
    type: 'png',
  });

  if (i % 30 === 0) process.stderr.write(`  frame ${i}/${totalFrames}\n`);
}

await browser.close();
process.stderr.write(`Captured ${totalFrames} frames to ${frameDir}\n`);

Step 2 — Capture frames

HTML_FILE="template.html"
OUTPUT="output.mp4"
FPS=30
DURATION=5
WIDTH=1920
HEIGHT=1080
FRAME_DIR="/tmp/mk-html-video-frames-$$"

node /tmp/mk-html-video-capture.mjs \
  "$HTML_FILE" "$FRAME_DIR" "$FPS" "$DURATION" "$WIDTH" "$HEIGHT"

Step 3 — Encode to MP4

ffmpeg -y \
  -r "$FPS" \
  -i "$FRAME_DIR/frame_%06d.png" \
  -c:v libx264 \
  -pix_fmt yuv420p \
  -preset medium \
  -crf 18 \
  "$OUTPUT"

# Verify output
ffprobe -v quiet -print_format json -show_format -show_streams "$OUTPUT" \
  | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['format']['duration'],'s')"

Step 4 — Clean up

rm -rf "$FRAME_DIR"
echo "Done: $OUTPUT"

Complete One-Shot Script

#!/usr/bin/env bash
set -euo pipefail

HTML_FILE="${1:-template.html}"
OUTPUT="${2:-output.mp4}"
FPS="${3:-30}"
DURATION="${4:-5}"
WIDTH="${5:-1920}"
HEIGHT="${6:-1080}"
FRAME_DIR="/tmp/mk-html-video-$$"

# Write capture script
cat > /tmp/mk-html-video-capture.mjs  typeof window.onFrame === 'function');
for (let i = 0; i  window.onFrame(fi,t), {fi:i,t:totalFrames});
  else await page.waitForTimeout(1000/Number(fps));
  await page.screenshot({ path: path.join(frameDir,`frame_${String(i).padStart(6,'0')}.png`) });
}
await browser.close();
JSEOF

node /tmp/mk-html-video-capture.mjs \
  "$HTML_FILE" "$FRAME_DIR" "$FPS" "$DURATION" "$WIDTH" "$HEIGHT"

ffmpeg -y -r "$FPS" \
  -i "$FRAME_DIR/frame_%06d.png" \
  -c:v libx264 -pix_fmt yuv420p -preset medium -crf 18 \
  "$OUTPUT"

rm -rf "$FRAME_DIR" /tmp/mk-html-video-capture.mjs
echo "Output: $OUTPUT"

Canvas / WebGL Templates

For ` or WebGL animations, the same approach works. Ensure the template exposes window.onFrame(i, total) to seek the animation state. If the animation uses requestAnimationFrame` internally, pause it and advance manually:

// Example: GSAP-based template
import gsap from 'gsap';
const tl = gsap.timeline({ paused: true });

window.onFrame = (i, total) => {
  tl.progress(i / total);
};

Gotchas

  • @playwright/test must be importable as ESM: The capture script uses ES modules

(import). Run with Node.js 18+ and ensure @playwright/test is installed in the working directory (npm install --no-save @playwright/test or install globally). If @playwright/test is missing, install with npx playwright install.

  • CSS animations play at real time without window.onFrame: Without the hook, the

script inserts real waitForTimeout delays between frames. A 30-fps, 10-second video takes ~10 actual seconds to capture. For fast renders, implement window.onFrame.

  • file:// URL restrictions: Some HTML templates load external resources via relative

paths. If images or fonts are missing, serve the template locally instead: npx serve . --port 8080 and use http://localhost:8080/template.html.

  • ffmpeg yuv420p requirement: Some players (QuickTime, Windows Media Player) require

yuv420p pixel format. Always include -pix_fmt yuv420p even if quality looks identical without it.

  • Frame directory cleanup on failure: If the script fails mid-capture, the temp frame

directory is left on disk. Check /tmp/mk-html-video-* and remove manually.

  • Odd-dimension mp4 error: libx264 requires width and height divisible by 2. If viewport

dimensions are odd, ffmpeg errors with width not divisible by 2. Use even numbers only.

  • Headless Chrome security flags for file://: Chromium may block some resources loaded

from file:// origins (CORS). Pass --allow-file-access-from-files via Playwright launchOptions.args in the capture script if cross-origin file resources are needed.

Source & license

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

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

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.