Install
openclaw skills install shellbot-video-generatorAI video production workflow using Remotion. Use when creating videos, short films, commercials, or motion graphics. Triggers on requests to make promotional...
openclaw skills install shellbot-video-generatorCreate professional motion graphics videos programmatically with React and Remotion.
Every video MUST follow the AIDA arc in order:
cd output && bash ../scripts/remotion.sh init <project-name>
npm installcd output/<project-name> && npm run dev
Wait for "Server ready" on port 3000.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 from local template (no network needed)
cd output && bash ../scripts/remotion.sh init my-video
cd my-video && npm install
# Add motion libraries
npm install lucide-react
# Start dev server
npm run dev
# Expose publicly
bash skills/cloudflare-tunnel/scripts/tunnel.sh start 3000
MANDATORY: Every video needs visual assets — logos, screenshots, product images, brand colors. Collect them BEFORE designing scenes. There are three sources:
Scrapes a website and extracts brand data + downloads reusable images in one step:
# Scrape and auto-download assets to the project's public/images/brand/
bash scripts/firecrawl.sh "https://example.com" output/my-video/public/images/brand
This returns JSON with: brandName, tagline, headline, description, features, logoUrl, faviconUrl, primaryColors (hex codes), ctaText, socialLinks, imageUrls (hero images, product shots, illustrations).
It also auto-downloads to the output directory: screenshot.png, og-image.png, favicon.png, logo.png, and all extracted page images (image-1.png, image-2.png, ...).
API Key: Set FIRECRAWL_API_KEY in the environment or .env.
The user may provide logos, images, or screenshots directly (as file paths, URLs, or paste). Always ask if they have specific assets they want included. Save them to public/images/:
mkdir -p public/images
curl -sL "https://user-provided-url.com/logo.png" -o public/images/logo.png
If you need specific images spotted in the scrape markdown (e.g. plan cards, channel icons), download them directly:
curl -sL "https://example.com/path/to/image.png" -o public/images/image.png
Reference downloaded assets with staticFile():
import { Img, staticFile } from "remotion";
<Img src={staticFile("images/brand/logo.png")} />
<Img src={staticFile("images/brand/screenshot.png")} />
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>
);
};
slideLeft, slideRight, crossDissolve, fadeBlur presetsnpm install lucide-react) — never emoji// 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/ # Firecrawl-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