Remotion video composition knowledge — 6 template specs, storyboard-to-composition mapping, frame calculations, animation catalog, transition types, platform rules, retention triggers, and anti-patterns for video production
Specialized knowledge base for mapping creative video concepts to Remotion compositions. Covers template selection, storyboard-to-composition translation, timing and pacing rules, animation and transition catalogs, platform-specific constraints, brand integration, retention psychology, and anti-patterns. This skill is consumed by the video-producer agent and integrates with the copywriting, video-production, and platform-mastery skills.
Use this skill when the user asks to:
Six composition templates are available, each with specific constraints and scene flow patterns.
| Property | Value | |----------|-------| | Aspect Ratio | 9:16 (1080x1920) | | FPS | 30 | | Duration Range | 15-60 seconds (450-1800 frames) | | Scene Flow | hook(3s) > points(5s each) > CTA(4s) | | Background | Dark gradient with accent color from brand | | Default Animations | fade-in + scale-in for hook, alternating slide-left/right for points, bounce for CTA | | Best For | Quick tips, lists, how-tos, hacks |
Props Schema:
{
template: "ReelTips",
fps: 30,
width: 1080,
height: 1920,
brand: BrandProps,
scenes: SceneProps[], // types: hook, point, cta
showProgressBar: boolean,
showWatermark: boolean
}
| Property | Value | |----------|-------| | Aspect Ratio | 9:16 (1080x1920) | | FPS | 30 | | Duration Range | 15-30 seconds (450-900 frames) | | Scene Flow | hook(3s) > comparisons(6s each) > reveal(4s) > CTA(3s) | | Background | Split dark/light for before/after contrast | | Default Animations | wipe transitions for comparisons, slide for reveal | | Best For | Transformations, comparisons, results, upgrades |
Props Schema:
{
template: "ReelBeforeAfter",
fps: 30,
width: 1080,
height: 1920,
brand: BrandProps,
scenes: SceneProps[], // types: hook, comparison, point, cta
showProgressBar: boolean,
showWatermark: boolean
}
| Property | Value | |----------|-------| | Aspect Ratio | 9:16 (1080x1920) | | FPS | 30 | | Duration Range | 15-30 seconds (450-900 frames) | | Scene Flow | hook(3s) > number+context(5s each) > insight(5s) > CTA(3s) | | Background | Solid dark with accent-colored number highlights | | Default Animations | counter-up for numbers, fade for context text | | Best For | Statistics, data points, metrics, growth numbers |
Props Schema:
{
template: "ReelNumbers",
fps: 30,
width: 1080,
height: 1920,
brand: BrandProps,
scenes: SceneProps[], // types: hook, number, point, cta
showProgressBar: boolean,
showWatermark: boolean
}
| Property | Value | |----------|-------| | Aspect Ratio | 16:9 (1920x1080) | | FPS | 30 | | Duration Range | 60-180 seconds (1800-5400 frames) | | Scene Flow | intro(5s) > steps(15s each) > summary grid(10s) > CTA(5s) | | Background | Clean light or branded gradient | | Default Animations | slide-up for steps, scale-in for summary | | Best For | Tutorials, how-to guides, processes, setup guides |
Props Schema:
{
template: "ExplainerSteps",
fps: 30,
width: 1920,
height: 1080,
brand: BrandProps,
scenes: SceneProps[], // types: hook, step, point, cta
showProgressBar: boolean,
showWatermark: boolean
}
| Property | Value | |----------|-------| | Aspect Ratio | 16:9 (1920x1080) | | FPS | 30 | | Duration Range | 30-120 seconds (900-3600 frames) | | Scene Flow | problem(5s) > solution intro(5s) > features(15s each) > social proof(10s) > CTA(5s) | | Background | Branded gradient or solid | | Default Animations | fade transitions, scale for features | | Best For | Product demos, feature showcases, SaaS walkthroughs |
Props Schema:
{
template: "ExplainerDemo",
fps: 30,
width: 1920,
height: 1080,
brand: BrandProps,
scenes: SceneProps[], // types: hook, point, demo, cta
showProgressBar: boolean,
showWatermark: boolean
}
| Property | Value | |----------|-------| | Aspect Ratio | 4:5 (1080x1350) | | FPS | 30 | | Duration Range | 15-60 seconds (450-1800 frames) | | Scene Flow | cover(4s) > content slides(4s each) > CTA(4s) | | Background | Inherits from carousel design spec or brand palette | | Default Animations | slide-left transitions between slides | | Best For | Animated versions of existing carousel content |
Props Schema:
{
template: "CarouselAnimated",
fps: 30,
width: 1080,
height: 1350,
brand: BrandProps,
scenes: SceneProps[], // types: hook, point, cta
showProgressBar: boolean,
showWatermark: boolean
}
The storyboard YAML produced by the video-producer agent must be translated into a Remotion composition JSON. Follow these rules exactly.
| Storyboard Scene Type | Remotion SceneProps.type | Typical Animation | Notes | |----------------------|-------------------------|-------------------|-------| | hook | "hook" | fade-in, scale-in | Always first scene. Must fire immediately. | | point | "point" | slide-left, slide-right | Content scenes. Alternate directions for pattern interrupt. | | comparison | "comparison" | wipe, slide-left | Before/after pairs. Use wipe for dramatic reveal. | | number | "number" | counter-up | Numbers animate from 0 to target value. | | step | "step" | slide-up, fade-in | Tutorial steps. Numbered prominently. | | demo | "demo" | scale-in, fade-in | Product feature highlights. | | cta | "cta" | bounce, scale-in | Always last scene. Brand handle visible. |
For each scene in storyboard.scenes:
scene.durationFrames = scene.duration * storyboard.fps
scene.startFrame = sum of all previous scenes' durationFrames
Total composition frames:
totalFrames = sum of all scene durationFrames
Example calculation (30fps):
Scene 1 (hook, 3s): startFrame=0, durationFrames=90
Scene 2 (point, 5s): startFrame=90, durationFrames=150
Scene 3 (point, 5s): startFrame=240, durationFrames=150
Scene 4 (point, 5s): startFrame=390, durationFrames=150
Scene 5 (cta, 4s): startFrame=540, durationFrames=120
Total: totalFrames=660 (22 seconds)
Map brand-voice.yaml colors to Remotion BrandProps:
# Input: brand-voice.yaml
visual_identity:
colors:
primary: "#6c5ce7"
secondary: "#00cec9"
background: "#0a0a0a"
text: "#ffffff"
accent: "#fd79a8"
handle: "@myhandle"
name: "My Brand"
# Output: BrandProps
brand:
name: "My Brand"
handle: "@myhandle"
colors:
primary: "#6c5ce7"
secondary: "#00cec9"
background: "#0a0a0a"
text: "#ffffff"
accent: "#fd79a8"
watermark_position: "bottom-right"
{
"template": "<from storyboard.template>",
"fps": 30,
"width": "<from template spec>",
"height": "<from template spec>",
"totalFrames": "<calculated sum>",
"brand": "<BrandProps from brand-voice.yaml>",
"scenes": [
{
"type": "<mapped scene type>",
"startFrame": "<calculated>",
"durationFrames": "<scene.duration * fps>",
"text": "<from storyboard scene>",
"subtext": "<from storyboard scene, optional>",
"icon": "<from storyboard scene, optional>",
"animation": "<from storyboard scene>",
"transition": "<from storyboard scene, default crossfade>"
}
],
"showProgressBar": true,
"showWatermark": true
}
| Scene Type | Minimum | Recommended | Maximum | Notes | |-----------|---------|-------------|---------|-------| | hook | 1.5s | 2-3s | 3s | Must fire immediately. Hook text readable in under 2s. | | point | 3s | 4-6s | 8s | One concept per scene. Text + subtext readable in duration. | | comparison | 4s | 6-8s | 10s | Before/after pair needs time to register contrast. | | number | 3s | 4-5s | 6s | Counter-up animation needs 2-3s, context text needs 2s. | | step | 8s | 12-15s | 20s | Tutorial steps need reading time + comprehension. | | demo | 8s | 12-15s | 20s | Product features need visual demonstration time. | | cta | 2s | 3-4s | 5s | Short, clear, actionable. Don't linger. |
| Platform | Total Duration Sweet Spot | Scene Pacing | Hook Window | |----------|--------------------------|-------------|-------------| | Instagram Reels | 15-30s | Fast (3-5s per scene) | < 1.5s | | YouTube Shorts | 30-60s | Medium (4-6s per scene) | < 2s | | TikTok | 15-60s | Fast (3-5s per scene) | < 1s | | YouTube (long) | 60-180s | Measured (8-15s per scene) | < 3s |
Ten animation types are available. Each maps to a Remotion animation implementation using useCurrentFrame() and interpolate().
| Animation | Description | Best For | Remotion Implementation | |-----------|-------------|----------|------------------------| | fade-in | Opacity from 0 to 1 over 15 frames | Hook scenes, gentle reveals | interpolate(frame, [0, 15], [0, 1]) on opacity | | fade-out | Opacity from 1 to 0 over 15 frames | Scene exits, before transitions | interpolate(frame, [end-15, end], [1, 0]) on opacity | | slide-left | Element enters from right edge, slides to center | Content points (odd positions) | interpolate(frame, [0, 20], [width, 0]) on translateX | | slide-right | Element enters from left edge, slides to center | Content points (even positions) | interpolate(frame, [0, 20], [-width, 0]) on translateX | | slide-up | Element enters from bottom, slides to center | Steps, reveals, lists | interpolate(frame, [0, 20], [height, 0]) on translateY | | scale-in | Element scales from 0.5 to 1.0 with slight bounce | Hooks, reveals, emphasis | interpolate(frame, [0, 20], [0.5, 1]) on scale with spring config | | bounce | Element enters with elastic bounce effect | CTAs, emphasis, fun reveals | spring({ frame, fps, config: { damping: 8, stiffness: 100 } }) | | typewriter | Text appears character by character | Quotes, statements, dramatic reveals | Render substring text.slice(0, charIndex) per frame | | counter-up | Number counts from 0 to target value | Statistics, metrics, data | interpolate(frame, [0, durationFrames], [0, targetNumber]) rounded | | wipe | Horizontal wipe reveals new content | Before/after reveals, dramatic transitions | ClipPath animation from inset(0 100% 0 0) to inset(0 0 0 0) |
fade-in, scale-in, or wipe. Never use slide-left or slide-right for the first scene — they feel like continuation, not an opening.slide-left and slide-right for pattern interrupt. Same direction on every scene is monotonous.counter-up for the number itself. Use fade-in for the context text below.bounce or scale-in for emphasis. The CTA must feel like an arrival, not a slide.wipe for dramatic before/after reveals. slide-left/slide-right for less dramatic comparisons.slide-up consistently for all steps. The upward motion creates a sense of building/progressing.| Animation | Default Duration (frames) | At 30fps | |-----------|--------------------------|----------| | fade-in | 15 | 0.5s | | fade-out | 15 | 0.5s | | slide-left | 20 | 0.67s | | slide-right | 20 | 0.67s | | slide-up | 20 | 0.67s | | scale-in | 20 | 0.67s | | bounce | 30 | 1.0s | | typewriter | varies (2 frames/char) | varies | | counter-up | matches scene duration | varies | | wipe | 25 | 0.83s |
Four transition types connect scenes. Transitions occur between the end of one scene and the start of the next.
| Transition | Frame Duration | At 30fps | Description | Best For | |-----------|---------------|----------|-------------|----------| | crossfade | 15 frames | 0.5s | Outgoing scene fades out while incoming fades in. Smooth and professional. | Default for most scene changes. Points, steps, generic transitions. | | wipe | 20 frames | 0.67s | Horizontal wipe reveals the new scene from left to right. Dramatic. | Before/after reveals, comparison pairs, dramatic moments. | | cut | 0 frames | 0s | Instant switch. No transition effect. Sharp and immediate. | Hook scene start (always use cut for the first scene), rhythm breaks. | | slide | 15 frames | 0.5s | Outgoing scene slides out while incoming slides in from opposite edge. | Carousel-animated (mimics swipe gesture), sequential content. |
cut: The video must start immediately. No fade-in from black — the hook text must be visible from frame 1.crossfade: When no transition is specified, use crossfade. It is the safest and most professional option.wipe sparingly: Maximum 2 wipe transitions per video. Overuse makes the video feel gimmicky.slide for CarouselAnimated: The slide transition mimics the Instagram carousel swipe gesture, making animated carousels feel native.| Parameter | Value | Notes | |-----------|-------|-------| | Aspect Ratio | 9:16 (1080x1920) | Mandatory for full-screen Reels | | Max Duration | 90 seconds | Algorithm favors 15-30s for reach | | Min Duration | 3 seconds | Below 3s feels like an error | | Sweet Spot | 15-30 seconds | Highest completion rate and reach | | Hook Window | < 1.5 seconds | Text must be readable immediately | | Safe Zones | 250px top, 400px bottom | Avoid UI overlay areas (username, music, buttons) | | Audio | Optional (motion-only supported) | Add caption overlay for sound-off viewers |
| Parameter | Value | Notes | |-----------|-------|-------| | Aspect Ratio | 9:16 (1080x1920) | Same as Reels | | Max Duration | 60 seconds | Hard limit enforced by YouTube | | Min Duration | 15 seconds | Below 15s may not get Shorts shelf | | Sweet Spot | 30-60 seconds | YouTube Shorts audience tolerates longer content | | Hook Window | < 2 seconds | Slightly more forgiving than IG | | Safe Zones | 200px top, 350px bottom | Similar to Reels but slightly different UI | | Audio | Recommended | YouTube Shorts discovery relies more on audio |
| Parameter | Value | Notes | |-----------|-------|-------| | Aspect Ratio | 9:16 (1080x1920) | Standard vertical | | Max Duration | 3 minutes (180s) | Extended from original 60s | | Min Duration | 5 seconds | Below 5s may not surface in For You | | Sweet Spot | 15-60 seconds | Depends on content type | | Hook Window | < 1 second | TikTok has the shortest attention span | | Safe Zones | 150px top, 500px bottom | Large bottom safe zone for comments/buttons | | Audio | Strongly recommended | TikTok is audio-first platform |
| Parameter | Value | Notes | |-----------|-------|-------| | Aspect Ratio | 16:9 (1920x1080) | Standard horizontal | | Max Duration | No limit | Platform supports any length | | Min Duration | 30 seconds | Below 30s feels rushed for horizontal | | Sweet Spot | 60-180 seconds | For explainers and demos | | Hook Window | < 3 seconds | More patient audience | | Safe Zones | Minimal | Full-screen video, no UI overlays during playback | | Audio | Expected | Horizontal YouTube content almost always has narration |
# brand-voice.yaml colors map to video elements:
primary: # Scene accent colors, progress bar fill, icon highlights
secondary: # Secondary highlights, subtext emphasis, background accents
background: # Video background color, gradient base
text: # All text (headlines, body, subtext)
accent: # CTA button color, emphasis elements, counter-up numbers
| Scene Type | Background | Text | Accent Usage | |-----------|-----------|------|-------------| | hook | background color or dark gradient | text color, large | accent on key word or underline | | point | background color | text color | primary on icon, secondary on subtext | | comparison | split: muted background / vibrant background | text color | accent on "after" state | | number | background color | text color for context | accent on the animated number | | step | background color | text color | primary on step number | | demo | background color or branded gradient | text color | accent on feature highlights | | cta | accent color as background or accent border | text color (inverted if needed) | accent dominant |
The brand watermark (@handle) is rendered by the BrandWatermark component:
| Position | Pixel Offset | Best For | |----------|-------------|----------| | bottom-right | 40px from bottom, 40px from right | Default. Most natural position. | | bottom-left | 40px from bottom, 40px from left | When bottom-right conflicts with platform UI | | top-right | 40px from top, 40px from right | When bottom area is crowded |
Watermark rules:
The animated progress bar shows video progress (0% to 100% over the full duration):
The single most important retention factor in short-form video is visual change frequency. The human brain habituates to static visuals in 3-5 seconds and the viewer's finger moves to scroll.
Rule: A visible change must occur every 3-5 seconds.
Types of visible changes (in order of impact):
| Trigger | Implementation | Why It Works | |---------|---------------|-------------| | Motion on every scene | All scenes have entrance animations | Static text = scroll away. Motion = visual interest. | | Counter-up for numbers | Numbers animate from 0 to target | Brain tracks counting, creates micro-anticipation. | | Alternating slide directions | Odd scenes slide-left, even slide-right | Pattern interrupt prevents visual habituation. | | Progress bar | Animated bar from 0% to 100% | Creates completion motivation (endowed progress effect). | | Hook in first 1.5s | Text visible from frame 1, no fade-from-black | First impression determines stay/go. No wasted time. | | Text in motion | Typewriter, slide, fade — never just "appear" | Motion triggers peripheral vision attention. | | Color contrast on key words | Accent color on numbers, names, key terms | Draws eye to the most important information. | | Scene type variety | Mix hooks, points, numbers, comparisons | Different layouts prevent monotony. |
Before finalizing any storyboard, verify these are NOT present:
Retention Anti-Pattern Checklist:
[ ] No scene exceeds 8 seconds without a visual change
[ ] No two consecutive scenes use the same animation direction
[ ] Hook scene text is visible from frame 1 (no fade-from-black)
[ ] CTA scene is under 5 seconds (viewer has already decided)
[ ] No "intro" scene before the hook (no logos, no "welcome to...")
[ ] Total duration is within platform sweet spot
[ ] Progress bar is enabled (completion motivation)
[ ] At least 1 counter-up animation if content contains numbers
[ ] No scene has more than 20 words of text
| Anti-Pattern | Why It Fails | Fix | |-------------|-------------|-----| | Scene > 8s without visual change | Viewer habituates, scrolls away | Split scene or add mid-scene animation | | Static text (no animation) | Looks like a screenshot, not a video | Add entrance animation to every text element | | No CTA at the end | Viewer watches but takes no action | Always end with a clear, single CTA | | Hook > 3 seconds | Loses viewers before the content starts | Compress hook to 2-3s max. If text is too long, simplify. | | Intro/logo before hook | Wastes the critical first 1.5s | Hook first, always. Brand watermark handles attribution. | | Slow pacing (>6s per point scene) | Feels like a slideshow, not a video | Speed up to 4-5s per point. Cut unnecessary subtext. | | Too many words per scene | Text unreadable in scene duration | Max 20 words per scene. 12 words is ideal for 5s scenes. | | Same animation on every scene | Monotonous, no pattern interrupts | Alternate directions, mix animation types (max 3 types). |
| Anti-Pattern | Why It Fails | Fix | |-------------|-------------|-----| | Duration outside template range | Remotion render may fail or produce bad output | Check template duration constraints before composing | | Wrong aspect ratio for platform | Video appears letterboxed or cropped | Match template aspect ratio to target platform | | Transition frames exceeding scene duration | Scene content gets cut by transition overlap | Ensure scene durationFrames > transition frame count | | Missing brand colors | Video looks generic, no brand recognition | Always load and inject BrandProps from brand-voice.yaml | | Counter-up without sufficient duration | Number animation too fast to read | Minimum 3s for counter-up scenes | | Watermark over main text | Blocks content, looks amateur | Position watermark in corner away from text area |
| Anti-Pattern | Why It Fails | Fix | |-------------|-------------|-----| | Using explainer template for reel content | Wrong pacing, wrong audience expectations | Match template to content type and platform | | 60s reel for a 3-point topic | Padded content feels thin, hurts retention | Shorter content = shorter video. 3 points = 25-30s reel. | | Animated carousel without original carousel | No source content to adapt | Use reel or explainer template instead. CarouselAnimated is for converting existing content. | | Ignoring platform safe zones | Text hidden behind platform UI elements | Respect safe zone margins per platform | | No progress bar on reels | Viewer has no sense of video length | Always enable progress bar for short-form content |
Convert existing HTML carousel content to animated video when:
Carousel slide_plan: Video storyboard:
slide 1 (cover) --> scene 1 (hook, 4s, fade-in)
slide 2 (content) --> scene 2 (point, 4s, slide-left)
slide 3 (content) --> scene 3 (point, 4s, slide-right)
... ...
slide N (cta) --> scene N (cta, 4s, bounce)
| Carousel Element | Video Adaptation | |-----------------|-----------------| | Slide headline | Scene text (keep as-is, already optimized) | | Slide body text | Scene subtext (may need trimming for reading speed) | | Slide bullets | Convert to sequential text reveals within scene | | Slide icon/emoji | Scene icon (keep as-is) | | Progress dots | Replace with animated progress bar | | Brand watermark | Map to BrandWatermark component |
Carousel with N slides:
Each slide becomes a 4-second scene
Total duration = N * 4 seconds
Example: 7-slide carousel = 28-second video
If total > 60s: reduce per-scene duration to 3s
If total < 15s: increase per-scene duration to 5s
Always use slide transition for CarouselAnimated. This mimics the Instagram carousel swipe gesture and makes the animated version feel like a natural extension of the carousel format.
When converting from an existing carousel, the CarouselAnimated template inherits:
design_spec.palettedesign_spec.brand.handledesign_spec.typographyDo not re-derive these from brand-voice.yaml — use the carousel's existing design decisions for visual consistency between the static and animated versions.
Six Remotion React components are available for all templates:
| Component | Purpose | Props | |-----------|---------|-------| | AnimatedText | Renders text with specified animation type and delay | text, animation, delay, style | | ProgressBar | Animated bar showing video progress (0-100%) | position ("top" or "bottom"), color, height | | BrandWatermark | Brand handle overlay in corner, fades in on first scene | handle, position, color, opacity | | TransitionWipe | Renders transition effect between scenes | type ("crossfade", "wipe", "cut", "slide"), durationFrames | | BackgroundGradient | Animated gradient background with subtle color shift | colors (array), direction, animationSpeed | | CounterUp | Counts from 0 to target number with easing | target, durationFrames, style, easing |
output_contract:
skill: remotion-video
format: composition_json
required_fields:
template: string # Must match a registered template name
fps: integer # Always 30
width: integer # From template spec
height: integer # From template spec
totalFrames: integer # Calculated sum of all scene durationFrames
brand: BrandProps # From brand-voice.yaml
scenes: SceneProps[] # Mapped from storyboard
showProgressBar: boolean
showWatermark: boolean
scene_required_fields:
type: string # hook | point | comparison | number | step | demo | cta
startFrame: integer # Calculated cumulative position
durationFrames: integer # scene.duration * fps
text: string # Scene headline text
animation: string # From animation catalog
scene_optional_fields:
subtext: string
icon: string
transition: string # Default: crossfade
validation:
total_frames_within_template_range: true
all_animations_in_catalog: true
all_transitions_in_catalog: true
frame_calculations_correct: true
brand_colors_present: true
no_scene_exceeds_max_duration: true
The Remotion CLI may be installed differently on each user's machine. Never assume a fixed path or command. Follow this discovery sequence exactly.
The Remotion project lives inside the plugin directory. Discover its path dynamically:
# Strategy 1: Relative to the plugin root
PROJECT_ROOT="$(find . -maxdepth 4 -name 'brand-voice.yaml' -exec dirname {} \; 2>/dev/null | head -1)"
REMOTION_PROJECT="${PROJECT_ROOT}/remotion"
# Strategy 2: If brand-voice.yaml not found, search for remotion/src/Root.tsx
REMOTION_PROJECT="$(find . -maxdepth 5 -path '*/remotion' -type d 2>/dev/null | head -1)"
# Strategy 3: Check common Claude Code plugin locations
PLUGIN_LOCATIONS=(
"${HOME}/.claude/plugins"
"${HOME}/Downloads"
"$(pwd)"
)
for loc in "${PLUGIN_LOCATIONS[@]}"; do
if [ -f "${loc}/remotion/src/Root.tsx" ] || [ -f "${loc}/remotion/index.ts" ]; then
REMOTION_PROJECT="${loc}/remotion"
break
fi
done
Validation: After locating, confirm the project is valid:
# Must have at least one of these entry points
[ -f "${REMOTION_PROJECT}/index.ts" ] || [ -f "${REMOTION_PROJECT}/src/Root.tsx" ]
has two Remotion entry point structures. Detect which one is active:
# Preferred: Full project with 6 compositions (src/ structure)
if [ -f "${REMOTION_PROJECT}/src/Root.tsx" ] && [ -f "${REMOTION_PROJECT}/src/compositions/index.ts" ]; then
ENTRY_POINT="${REMOTION_PROJECT}/src/index.ts"
ENTRY_TYPE="full"
# Available compositions: ReelTips, ReelBeforeAfter, ReelNumbers, ExplainerSteps, ExplainerDemo, CarouselAnimated
# Fallback: Simplified structure (root-level)
elif [ -f "${REMOTION_PROJECT}/index.ts" ]; then
ENTRY_POINT="${REMOTION_PROJECT}/index.ts"
ENTRY_TYPE="simple"
# Available compositions: ReelTips only (others must be added)
fi
Entry point decision table:
| Structure | Entry Point | Compositions | When to Use | |-----------|-------------|-------------|-------------| | src/ (full) | remotion/src/index.ts | All 6 templates | Default — use this when available | | Root-level (simple) | remotion/index.ts | ReelTips only | Fallback if src/ not present |
Remotion CLI can be installed in multiple ways. Check in order of preference:
# Method 1: Local npx (most reliable — uses project's node_modules)
cd "${REMOTION_PROJECT}" && npx remotion --version 2>/dev/null
REMOTION_CMD="npx remotion"
# Method 2: Project-level node_modules binary
if [ -x "${REMOTION_PROJECT}/node_modules/.bin/remotion" ]; then
REMOTION_CMD="${REMOTION_PROJECT}/node_modules/.bin/remotion"
fi
# Method 3: parent node_modules (if deps installed at level)
REMOTION_BIN="${PROJECT_ROOT}/node_modules/.bin/remotion"
if [ -x "${REMOTION_BIN}" ]; then
REMOTION_CMD="${REMOTION_BIN}"
fi
# Method 4: Global install
if command -v remotion &>/dev/null; then
REMOTION_CMD="remotion"
fi
# Method 5: Not found — install needed
if [ -z "${REMOTION_CMD}" ]; then
echo "Remotion not found. Installing..."
cd "${REMOTION_PROJECT}" && npm install
REMOTION_CMD="npx remotion"
fi
Discovery result table:
| Method | Command | Reliability | Notes | |--------|---------|-------------|-------| | npx remotion | High | Uses local node_modules, auto-resolves version | Preferred | | ./node_modules/.bin/remotion | High | Direct binary, no npx overhead | Fast alternative | | Global remotion | Medium | May version-mismatch with project deps | Check version compatibility | | Not found | — | — | Run npm install in remotion project dir |
Before rendering, confirm all dependencies are installed:
cd "${REMOTION_PROJECT}"
# Check if node_modules exists and has remotion
if [ ! -d "node_modules/remotion" ]; then
echo "Installing Remotion dependencies..."
npm install
fi
# Verify Chrome Headless Shell (Remotion downloads this automatically on first render)
# No manual action needed — Remotion handles this. But if it fails:
# npx remotion browser ensure
Use the discovered REMOTION_CMD and ENTRY_POINT to execute renders:
# Ensure output directory exists
mkdir -p "${PROJECT_ROOT}/.output/videos/$(date +%Y-%m-%d-%H%M%S)"
OUTPUT_DIR="${PROJECT_ROOT}/.output/videos/$(date +%Y-%m-%d-%H%M%S)"
# List available compositions (verify before render)
cd "${REMOTION_PROJECT}" && ${REMOTION_CMD} compositions "${ENTRY_POINT}"
# Render MP4 video
cd "${REMOTION_PROJECT}" && ${REMOTION_CMD} render "${ENTRY_POINT}" <CompositionId> "${OUTPUT_DIR}/video.mp4"
# Render a still frame (preview/thumbnail)
cd "${REMOTION_PROJECT}" && ${REMOTION_CMD} still "${ENTRY_POINT}" <CompositionId> "${OUTPUT_DIR}/preview.png" --frame=45
# Open Remotion Studio (interactive preview in browser)
cd "${REMOTION_PROJECT}" && ${REMOTION_CMD} studio "${ENTRY_POINT}"
Passing dynamic props (from composition.json):
# Pass scene data from the storyboard via --props flag
cd "${REMOTION_PROJECT}" && ${REMOTION_CMD} render "${ENTRY_POINT}" ReelTips "${OUTPUT_DIR}/video.mp4" \
--props="${OUTPUT_DIR}/composition.json"
| Platform | Working Dir Separator | Shell | NPX Available | Notes | |----------|----------------------|-------|---------------|-------| | macOS | / | zsh/bash | Yes (with Node) | Default dev platform. open file.mp4 to play. | | Linux | / | bash | Yes (with Node) | xdg-open file.mp4 to play. May need apt install chromium for headless. | | Windows (native) | \ | PowerShell/cmd | Yes (with Node) | Use start file.mp4 to play. Paths need backslash or quotes. | | Windows (WSL) | / | bash | Yes (with Node) | Render inside WSL, output accessible at /mnt/c/.... explorer.exe file.mp4 to play. |
macOS-specific:
# Render and open
cd "${REMOTION_PROJECT}" && ${REMOTION_CMD} render "${ENTRY_POINT}" ReelTips out/video.mp4 && open out/video.mp4
Linux-specific:
# Ensure headless Chrome dependencies
sudo apt-get install -y chromium-browser 2>/dev/null || true
cd "${REMOTION_PROJECT}" && ${REMOTION_CMD} render "${ENTRY_POINT}" ReelTips out/video.mp4 && xdg-open out/video.mp4
WSL-specific:
# Render in WSL, open in Windows
cd "${REMOTION_PROJECT}" && ${REMOTION_CMD} render "${ENTRY_POINT}" ReelTips out/video.mp4
explorer.exe "$(wslpath -w out/video.mp4)"
# Default concurrency (auto-detected from CPU cores)
${REMOTION_CMD} render "${ENTRY_POINT}" ReelTips out/video.mp4
# Explicit concurrency (use 50-75% of available cores)
${REMOTION_CMD} render "${ENTRY_POINT}" ReelTips out/video.mp4 --concurrency=8
# Lower quality for fast preview
${REMOTION_CMD} render "${ENTRY_POINT}" ReelTips out/preview.mp4 --jpeg-quality=60 --scale=0.5
# High quality for publication
${REMOTION_CMD} render "${ENTRY_POINT}" ReelTips out/video.mp4 --jpeg-quality=90 --crf=18
| Error | Cause | Fix | |-------|-------|-----| | remotion: command not found | CLI not installed | cd ${REMOTION_PROJECT} && npm install then use npx remotion | | Cannot find module 'remotion' | Dependencies not installed | cd ${REMOTION_PROJECT} && npm install | | Could not find composition | Wrong entry point or composition ID | Run ${REMOTION_CMD} compositions ${ENTRY_POINT} to list available | | Chrome Headless Shell not found | First render, downloading | Wait for auto-download, or run npx remotion browser ensure | | ENOMEM / out of memory | Video too long or high concurrency | Reduce --concurrency or add --scale=0.5 | | ENOSPC / disk space | Not enough disk for render frames | Free 500MB+ before rendering | | Timed out | Render exceeds 5min | Reduce duration, lower quality, or increase --timeout | | JSX element type 'X' does not have any construct | TypeScript/React version mismatch | Check tsconfig.json has "jsx": "react-jsx" | | Module not found: @remotion/cli/config | Old remotion.config.ts API | Update to import { Config } from "@remotion/cli/config" |
#!/bin/bash
# Remotion Discovery — run from project root
set -e
# 1. Find root
PROJECT_ROOT="$(find . -maxdepth 4 -name 'brand-voice.yaml' -exec dirname {} \; 2>/dev/null | head -1)"
if [ -z "$PROJECT_ROOT" ]; then
echo "ERROR: brand-voice.yaml not found. Run /grow setup first."
exit 1
fi
# 2. Find Remotion project
REMOTION_PROJECT="${PROJECT_ROOT}/remotion"
if [ ! -d "$REMOTION_PROJECT" ]; then
echo "ERROR: Remotion project not found at ${REMOTION_PROJECT}"
exit 1
fi
# 3. Determine entry point
if [ -f "${REMOTION_PROJECT}/src/compositions/index.ts" ]; then
ENTRY_POINT="src/index.ts"
echo "Entry: full (6 compositions)"
else
ENTRY_POINT="index.ts"
echo "Entry: simple (ReelTips only)"
fi
# 4. Ensure dependencies
if [ ! -d "${REMOTION_PROJECT}/node_modules/remotion" ]; then
echo "Installing dependencies..."
cd "$REMOTION_PROJECT" && npm install
fi
# 5. Verify
cd "$REMOTION_PROJECT" && npx remotion compositions "$ENTRY_POINT"
echo ""
echo "Remotion ready!"
echo " Project: ${REMOTION_PROJECT}"
echo " Entry: ${ENTRY_POINT}"
echo " Render: cd ${REMOTION_PROJECT} && npx remotion render ${ENTRY_POINT} <CompositionId> out/video.mp4"
echo " Studio: cd ${REMOTION_PROJECT} && npx remotion studio ${ENTRY_POINT}"