Install
openclaw skills install remotion-video-generatorClawHub Security found sensitive or high-impact capabilities. Review the scan results before using.
AI video production workflow using Remotion. Use when creating videos, short films, commercials, or motion graphics. Triggers on requests to make promotional videos, product demos, social media videos, animated explainers, or any programmatic video content. Produces polished motion graphics, not slideshows.
openclaw skills install remotion-video-generator"Create professional motion graphics videos programmatically with React and Remotion."
scrapling library instead of Firecrawl API# Run the scrapling script to get brand colors, logo, tagline
bash skills/remotion-video-generator/scripts/scrapling.sh "https://brand-website.com"
This extracts: brandName, tagline, logoUrl, faviconUrl, primaryColors, ogImageUrl, screenshotUrl
mkdir -p public/images/brand
curl -sL "https://brand.com/logo.svg" -o public/images/brand/logo.svg
curl -sL "https://brand.com/og-image.png" -o public/images/brand/og-image.png
curl -sL "https://image.thum.io/get/width/1200/crop/800/https://brand.com" -o screenshot.png
mkdir -p my-video/src my-video/public/images/brand my-video/public/audio
{
"name": "my-video",
"scripts": {
"dev": "npx remotion studio",
"build": "npx remotion bundle"
},
"dependencies": {
"@remotion/cli": "^4.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"remotion": "^4.0.0",
"lucide-react": "^0.300.0"
}
}
cd my-video && npm install
Create src/MyVideo.tsx with:
Create src/index.tsx - MUST use .tsx extension:
import { registerRoot, Composition } from "remotion";
import { AbsoluteFill, Sequence, useCurrentFrame, useVideoConfig, interpolate, spring } from "remotion";
const MyVideo = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// Animations - ALWAYS pass fps to spring()
const scale = spring({ frame, fps, from: 0.8, to: 1 });
return (
<AbsoluteFill style={{ backgroundColor: "#000" }}>
<Sequence from={0} durationInFrames={90}>
<h1>Hello World</h1>
</Sequence>
</AbsoluteFill>
);
};
registerRoot(() => {
return (
<Composition
id="MyVideo"
component={MyVideo}
durationInFrames={240}
fps={30}
width={1920}
height={1080}
/>
);
});
⚠️ CRITICAL Remotion v4 Rules:
.tsx extension (NOT .ts) for files with JSXregisterRoot + Composition APIfps to spring(): spring({ frame, fps, from: 0.8, to: 1 })useVideoConfig() to get fps: const { fps } = useVideoConfig()npx remotion render MyVideo out/video.mp4cd my-video && npm run dev
Server runs on http://localhost:3000
npx remotion render index out/final-video.mp4
# Install Remotion globally
npm install -g remotion
# Install dependencies for video projects
npm install lucide-react
# Install Scrapling (already in workspace skills)
pip install scrapling
Use this skill when:
Do NOT use for:
output/<project-name>/npm installnpx remotion (not bun):
"scripts": {
"dev": "npx remotion studio",
"build": "npx remotion bundle"
}
cd output/<project-name> && npm run dev
bash skills/cloudflare-tunnel/scripts/tunnel.sh start 3000
https://xxx.trycloudflare.com)The user will preview in their browser, request changes, and you edit the source files. Remotion hot-reloads automatically.
cd output/<project-name>
npx remotion render CompositionName out/video.mp4
# Scaffold project
cd output && npx --yes create-video@latest my-video --template blank
cd my-video && npm install
# Add motion libraries
npm install lucide-react
# Fix scripts in package.json (replace any "bun" references with "npx remotion")
# Start dev server
npm run dev
# Expose publicly
bash skills/cloudflare-tunnel/scripts/tunnel.sh start 3000
MANDATORY: When a video mentions or features any product/company, use Scrapling to scrape the product's website for brand data, colors, screenshots, and copy BEFORE designing the video. This ensures visual accuracy and brand consistency.
# Run the brand data extraction script
bash skills/remotion-video-generator/scripts/scrapling.sh "https://example.com"
This returns structured brand data: brandName, tagline, headline, description, features, logoUrl, faviconUrl, primaryColors, ctaText, socialLinks, plus screenshot URL and OG image URL.
If the script isn't available, use Python directly:
import json
from scrapling.fetchers import StealthyFetcher
from urllib.parse import urljoin
import re
url = 'https://brand.com'
page = StealthyFetcher.fetch(url, headless=True)
html = page.text
def resolve(u):
return urljoin(url, u) if u and not u.startswith('http') else u
colors = list(set(re.findall(r'#(?:[0-9a-fA-F]{3}){1,2}', html)))[:5]
data = {
'brandName': page.css('[property="og:site_name"]::text').get() or page.title(),
'tagline': page.css('[property="og:description"]::text').get(),
'headline': page.css('h1::text').get(),
'description': page.css('[property="og:description"]::text').get(),
'logoUrl': resolve(page.css('[rel="icon"]::attr(href)').get()),
'faviconUrl': resolve(page.css('[rel="icon"]::attr(href)').get()),
'primaryColors': colors,
'ctaText': page.css('a[href*="signup"]::text').get(),
'ogImageUrl': resolve(page.css('[property="og:image"]::attr(content)').get()),
'screenshotUrl': f"https://image.thum.io/get/width/1200/crop/800/{url}"
}
print(json.dumps(data, indent=2))
mkdir -p public/images/brand
curl -s "https://example.com/favicon.ico" -o public/images/brand/favicon.ico
curl -s "${OG_IMAGE_URL}" -o public/images/brand/og-image.png
curl -sL "${SCREENSHOT_URL}" -o public/images/brand/screenshot.png
Note: Some S3 buckets block direct access. Use thum.io screenshot service as fallback.
Use scene-based architecture with proper transitions:
const SCENE_DURATIONS: Record<string, number> = {
intro: 3000, // 3s hook
problem: 4000, // 4s dramatic
solution: 3500, // 3.5s reveal
features: 5000, // 5s showcase
cta: 3000, // 3s close
};
import { AbsoluteFill, Sequence, useCurrentFrame, useVideoConfig, interpolate, spring, Img, staticFile, Audio } from "remotion";
export const MyVideo = () => {
const frame = useCurrentFrame();
const { fps, durationInFrames } = useVideoConfig();
return (
<AbsoluteFill>
{/* Background music */}
<Audio src={staticFile("audio/bg-music.mp3")} volume={0.35} />
{/* Persistent background layer - OUTSIDE sequences */}
<AnimatedBackground frame={frame} />
{/* Scene sequences */}
<Sequence from={0} durationInFrames={90}>
<IntroScene />
</Sequence>
<Sequence from={90} durationInFrames={120}>
<FeatureScene />
</Sequence>
</AbsoluteFill>
);
};
npm install lucide-react) — never emoji| Technique | Description |
|---|---|
| Morph/Scale | Element scales up to fill screen, becomes next scene's background |
| Wipe | Colored shape sweeps across, revealing next scene |
| Zoom-through | Camera pushes into element, emerges into new scene |
| Clip-path reveal | Circle/polygon grows from point to reveal |
| Persistent anchor | One element stays while surroundings change |
| Directional flow | Scene 1 exits right, Scene 2 enters from right |
| Split/unfold | Screen divides, panels slide apart |
| Perspective flip | Scene rotates on Y-axis in 3D |
// Timing values (in seconds)
const timing = {
micro: 0.1-0.2, // Small shifts, subtle feedback
snappy: 0.2-0.4, // Element entrances, position changes
standard: 0.5-0.8, // Scene transitions, major reveals
dramatic: 1.0-1.5, // Hero moments, cinematic reveals
};
// Spring configs
const springs = {
snappy: { stiffness: 400, damping: 30 },
bouncy: { stiffness: 300, damping: 15 },
smooth: { stiffness: 120, damping: 25 },
};
const opacity = interpolate(frame, [0, 30], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp"
});
const scale = spring({
frame,
fps,
from: 0.8,
to: 1,
durationInFrames: 30,
config: { damping: 12 }
});
<Sequence from={0} durationInFrames={100}>
<Scene1 />
</Sequence>
<Sequence from={80} durationInFrames={100}>
<Scene2 />
</Sequence>
Place persistent elements OUTSIDE Sequence blocks:
const PersistentShape = ({ currentScene }: { currentScene: number }) => {
const positions = {
0: { x: 100, y: 100, scale: 1, opacity: 0.3 },
1: { x: 800, y: 200, scale: 2, opacity: 0.5 },
2: { x: 400, y: 600, scale: 0.5, opacity: 1 },
};
return (
<motion.div
animate={positions[currentScene]}
transition={{ duration: 0.8, ease: "easeInOut" }}
className="absolute w-32 h-32 rounded-full bg-gradient-to-r from-coral to-orange"
/>
);
};
Before delivering, verify:
npm run dev on port 3000bash skills/cloudflare-tunnel/scripts/tunnel.sh start 3000my-video/
├── src/
│ ├── Root.tsx # Composition definitions
│ ├── index.ts # Entry point
│ ├── index.css # Global styles
│ ├── MyVideo.tsx # Main video component
│ └── scenes/ # Scene components (optional)
├── public/
│ ├── images/
│ │ └── brand/ # Scrapling-scraped assets
│ └── audio/ # Background music
├── remotion.config.ts
└── package.json
See references/components.md for reusable:
# Start tunnel (exposes port 3000 publicly)
bash skills/cloudflare-tunnel/scripts/tunnel.sh start 3000
# Check status
bash skills/cloudflare-tunnel/scripts/tunnel.sh status 3000
# List all tunnels
bash skills/cloudflare-tunnel/scripts/tunnel.sh list
# Stop tunnel
bash skills/cloudflare-tunnel/scripts/tunnel.sh stop 3000
| Issue | Solution |
|---|---|
Expected ">" but found "schema" | Use .tsx extension for files with JSX, not .ts |
useCurrentFrame() can only be called inside a component | Use registerRoot + Composition API (see Step 7) |
"fps" must be a number, but you passed undefined to spring() | Pass fps to spring: spring({ frame, fps, from: 0.8, to: 1 }) |
Could not find composition with ID index | Use composition name: npx remotion render MyVideo out.mp4 |
Module build failed | Ensure react and react-dom are in dependencies |
| Remotion not found | Run npm install in project directory |
| Hot reload not working | Ensure running npm run dev, not npx remotion directly |
| Brand colors not extracting | Some sites use CSS variables - check page source manually |
.tsx for files with JSX (components with < tags >).ts for pure TypeScript files.tsx if it uses JSXnpm run devregisterRoot + Composition patternfps parameterHere's the actual project created during testing:
Location: skills/remotion-video-generator/openclaw-promo/
Brand Data Extracted:
Project Structure:
openclaw-promo/
├── src/
│ ├── index.tsx # Entry point
│ └── OpenClawPromo.tsx # Video component
├── public/
│ └── images/
│ └── brand/
│ ├── logo.svg
│ ├── og-image.png
│ └── screenshot.png
├── package.json
└── tsconfig.json
Commands:
cd skills/remotion-video-generator/openclaw-promo
npm run dev # Start studio at localhost:3000
npm run build # Bundle for production
Last updated: 2026-02-25