Install
openclaw skills install code2animationProduce complete code-based animated videos by scripting, generating narration, creating visual assets, and rendering final MP4s using the code2animation fra...
openclaw skills install code2animationA comprehensive video editing and rendering skill that enables AI agents to create code-driven animations with text-to-speech narration and smooth transitions.
This skill allows agents to:
See public/projects/agentSaasPromoVideo.json for a complete working example demonstrating all transition types and stayInClip behavior.
This skill executes the following shell commands:
npx tsx scripts/generate-audio.ts <projectId>
node scripts/render.js <projectId> [--portrait]
ffmpeg -framerate 30 -i frames/frame-%05d.jpg -c:v libx264 ...ffmpeg -i video.mp4 -i audio1.mp3 -i audio2.mp3 -filter_complex ...which command to find Chrome/Chromium on Linux/macOSPUPPETEER_EXECUTABLE_PATH environment variableThe skill may expose an HTTP endpoint for TTS generation:
POST /api/tts
Content-Type: application/json
{
"text": "Text to speak",
"voice": "en-US-GuyNeural",
"rate": "+0%",
"pitch": "+0Hz"
}
This endpoint is optional and only used when pre-generated audio files are not available.
public/projects/<projectId>/public/projects/<projectId>/audio/, public/video/execSync for: browser detection, audio generation triggerOptional configuration:
PUPPETEER_EXECUTABLE_PATH: Custom browser path for PuppeteerFASTMCP_LOG_LEVEL: Logging level (default: ERROR)public/
projects/
<projectId>/
<projectId>.json # Project configuration
footage/ # HTML/CSS media components
audio/ # Generated TTS audio files
0.mp3, 1.mp3, ...
0.json, 1.json, ... # Word timing metadata
video/
render-<projectId>-landscape.mp4
render-<projectId>-portrait.mp4
# 1. Generate audio for a project
pnpm generate-audio agentSaasPromoVideo
# 2. Preview in browser
pnpm dev
# 3. Render final video
pnpm render agentSaasPromoVideo
# 4. Render portrait version
pnpm render agentSaasPromoVideo --portrait
When creating HTML animations for video rendering, use the CSS variable timeline model.
--t every frame.DOM = f(t).play/start/reset) and no hidden runtime state.transitionIn property instead.:root { --t: 0; }
--t.--p: clamp(0, calc((var(--t) - var(--start)) / var(--duration)), 1);
t).transitionanimation / @keyframeswindow.registerFrameAnimation(...)requestAnimationFrame loops for timeline progressionDate.now() / performance.now() for visual statetransitionIn in project configopacity: var(--p) instead of opacity: calc(var(--p) * (1 - var(--fade))) to keep elements visible at their final state..element {
--start: 0.5;
--duration: 1;
--p: clamp(0, calc((var(--t) - var(--start)) / var(--duration)), 1);
opacity: var(--p);
transform: translateY(calc((1 - var(--p)) * 20px));
}
Use math on progress directly:
--p: clamp(0, calc((var(--t) - var(--start)) / var(--duration)), 1);
--ease-out: calc(1 - (1 - var(--p)) * (1 - var(--p)));
opacity: var(--ease-out);
<script>
const labels = ['A', 'B', 'C'];
const el = document.getElementById('label');
window.onTimelineUpdate = (t) => {
const idx = Math.floor(Math.max(0, t) / 1.2) % labels.length;
el.textContent = labels[idx];
};
</script>
t vs globalTime)onTimelineUpdate(t, globalTime) supports two time domains:
t: clip-local time (resets to 0 when clip changes). This is the default for most HTML animations.globalTime: continuous timeline across clips. Use only when an element must stay continuous through cross-clip transitions.t is media-local. If a media appears mid-clip, t may already be large when it first becomes visible.globalTime and derive:
local = globalTime - mediaStartGlobalTimewindow.onTimelineUpdate = (t, globalTime) => {
const g = Number.isFinite(globalTime) ? globalTime : t;
// use `t` for normal clip-local animation, `g` only when continuity is required
};
t yields exactly one deterministic frame.t = totalDuration) must remain on the last clip (no wrap to first clip).This skill executes shell commands and spawns child processes for video rendering. All operations are limited to:
No arbitrary code execution or user input is passed to shell commands. All file paths and commands are predefined and validated.