reference.md
# Remotion API Reference
## Core Hooks
### useCurrentFrame()
```tsx
const frame = useCurrentFrame();
```
Returns current frame (0-indexed). Inside `<Sequence>`, returns relative frame.
### useVideoConfig()
```tsx
const { width, height, fps, durationInFrames, id, defaultProps } = useVideoConfig();
```
## interpolate()
```tsx
interpolate(
input: number,
inputRange: number[],
outputRange: number[],
options?: {
extrapolateLeft?: 'extend' | 'clamp' | 'identity' | 'wrap',
extrapolateRight?: 'extend' | 'clamp' | 'identity' | 'wrap',
easing?: (t: number) => number
}
): number
```
**Examples:**
```tsx
// Basic interpolation
interpolate(15, [0, 30], [0, 100]); // 50
// With clamping
interpolate(50, [0, 30], [0, 1], { extrapolateRight: 'clamp' }); // 1
// Multiple keyframes
interpolate(frame, [0, 20, 40, 60], [0, 1, 1, 0]);
// With easing
interpolate(frame, [0, 30], [0, 100], { easing: Easing.bezier(0.42, 0, 0.58, 1) });
```
## spring()
```tsx
spring({
frame: number,
fps: number,
config?: {
damping?: number, // Default: 10
mass?: number, // Default: 1
stiffness?: number, // Default: 100
overshootClamping?: boolean
},
from?: number, // Default: 0
to?: number, // Default: 1
durationInFrames?: number,
durationRestThreshold?: number,
delay?: number,
reverse?: boolean
}): number
```
**Config presets:**
- High bounce: `{ damping: 5, stiffness: 200 }`
- No bounce: `{ damping: 20, stiffness: 100, overshootClamping: true }`
- Slow: `{ damping: 20, mass: 2 }`
## measureSpring()
Get the duration of a spring animation:
```tsx
import { measureSpring } from 'remotion';
const duration = measureSpring({ fps: 30, config: { damping: 10 } });
// Returns number of frames until spring settles
```
## interpolateColors()
```tsx
interpolateColors(
input: number,
inputRange: number[],
outputRange: string[], // Hex, rgb(), rgba(), hsl()
options?: { extrapolateLeft?, extrapolateRight? }
): string
```
## Easing
```tsx
import { Easing } from 'remotion';
// Basic
Easing.linear
Easing.ease
Easing.quad
Easing.cubic
// In/Out/InOut variants
Easing.in(Easing.quad)
Easing.out(Easing.cubic)
Easing.inOut(Easing.ease)
// Cubic bezier
Easing.bezier(x1, y1, x2, y2)
// Other
Easing.circle
Easing.back(s?) // Overshoot
Easing.elastic(bounciness?)
Easing.bounce
Easing.sin
Easing.exp
Easing.poly(n) // Power of n
```
## Components
### Composition
```tsx
<Composition
id="MyVideo"
component={MyComponent}
// OR lazyComponent={() => import('./MyComponent')}
durationInFrames={150}
fps={30}
width={1920}
height={1080}
defaultProps={{ title: 'Hello' }}
calculateMetadata={async ({ props }) => ({
durationInFrames: props.items.length * 30,
props: { ...props, computed: true }
})}
/>
```
### Sequence
```tsx
<Sequence
from={30} // Start frame
durationInFrames={60} // Optional duration
name="Intro" // Label in Studio timeline
layout="none" // "none" | "absolute-fill"
>
<Child />
</Sequence>
```
### Series
```tsx
<Series>
<Series.Sequence durationInFrames={30} offset={-5}>
<A /> {/* Frames 0-29 */}
</Series.Sequence>
<Series.Sequence durationInFrames={60}>
<B /> {/* Frames 25-84 (offset caused overlap) */}
</Series.Sequence>
</Series>
```
### Loop
```tsx
<Loop
durationInFrames={30}
times={3} // Or Infinity
layout="none"
>
<Animation />
</Loop>
```
### AbsoluteFill
```tsx
<AbsoluteFill style={{ backgroundColor: '#000' }}>
{/* Position: absolute, full width/height */}
</AbsoluteFill>
```
### Media Components
**Img** (waits for load):
```tsx
<Img src={staticFile('photo.jpg')} style={{ width: '100%' }} />
```
**Video/Html5Video:**
```tsx
<Video
src={staticFile('clip.mp4')}
volume={0.5} // 0-1, or callback: (f) => f / 100
playbackRate={1.5}
muted={false}
loop={false}
startFrom={30} // Skip first 30 frames of source
endAt={120} // Stop at frame 120 of source
acceptableTimeShiftInSeconds={0.2}
/>
```
**OffthreadVideo** (better performance):
```tsx
<OffthreadVideo
src={staticFile('clip.mp4')}
volume={0.5}
transparent={false} // For videos with alpha
toneMapped={true} // HDR tone mapping
/>
```
**Audio:**
```tsx
<Audio
src={staticFile('music.mp3')}
volume={0.8}
startFrom={0}
endAt={300}
playbackRate={1}
/>
```
**AnimatedImage** (GIF/APNG):
```tsx
<AnimatedImage src={staticFile('animation.gif')} />
```
## Async Handling
```tsx
import { delayRender, continueRender, cancelRender } from 'remotion';
// Block render
const handle = delayRender('Loading data...');
// Unblock when ready
continueRender(handle);
// Cancel on error
cancelRender(new Error('Failed to load'));
```
**With timeout:**
```tsx
const handle = delayRender('Loading...', { timeoutInMilliseconds: 30000 });
```
## Static Files & Prefetching
```tsx
import { staticFile, prefetch, getStaticFiles } from 'remotion';
// Reference file in public/
const url = staticFile('video.mp4');
// Prefetch for faster playback
const { free, waitUntilDone } = prefetch(url);
await waitUntilDone();
// Later: free() to release memory
// List all static files
const files = getStaticFiles(); // ['video.mp4', 'image.png', ...]
```
## Input Props
```tsx
import { getInputProps } from 'remotion';
const props = getInputProps(); // Data passed via --props CLI flag
```
## Environment Detection
```tsx
import { getRemotionEnvironment } from 'remotion';
const env = getRemotionEnvironment();
// { isStudio: boolean, isRendering: boolean, isPlayer: boolean }
```
## random()
Deterministic random for consistent renders:
```tsx
import { random } from 'remotion';
const value = random('my-seed'); // 0-1, same every render
const value2 = random('seed', 0, 100); // 0-100
const value3 = random(null); // Different each render
```
## @remotion/renderer API
```tsx
import { bundle } from '@remotion/bundler';
import {
renderMedia,
renderStill,
selectComposition,
getCompositions,
renderFrames,
stitchFramesToVideo
} from '@remotion/renderer';
// Bundle project
const bundleLocation = await bundle({
entryPoint: './src/index.ts',
webpackOverride: (config) => config,
});
// Get composition
const composition = await selectComposition({
serveUrl: bundleLocation,
id: 'MyComp',
inputProps: {},
});
// Render video
await renderMedia({
composition,
serveUrl: bundleLocation,
codec: 'h264', // h264, h265, vp8, vp9, gif, prores
outputLocation: 'out.mp4',
inputProps: {},
onProgress: ({ progress }) => console.log(`${progress * 100}%`),
imageFormat: 'jpeg', // jpeg or png
jpegQuality: 80,
scale: 1,
frameRange: [0, 59], // Optional: specific frames
muted: false,
audioBitrate: '128k',
videoBitrate: '5M',
crf: 18, // Quality (lower = better, bigger)
concurrency: 4,
});
// Render still image
await renderStill({
composition,
serveUrl: bundleLocation,
output: 'thumbnail.png',
frame: 30,
imageFormat: 'png',
});
```
## @remotion/lambda API
```tsx
import {
deployFunction,
deploySite,
renderMediaOnLambda,
renderStillOnLambda,
getRenderProgress,
downloadMedia,
} from '@remotion/lambda';
// Deploy function
const { functionName } = await deployFunction({
region: 'us-east-1',
timeoutInSeconds: 120,
memorySizeInMb: 2048,
});
// Deploy site
const { serveUrl } = await deploySite({
entryPoint: './src/index.ts',
region: 'us-east-1',
siteName: 'my-video',
});
// Render
const { renderId, bucketName } = await renderMediaOnLambda({
region: 'us-east-1',
functionName,
serveUrl,
composition: 'MyComp',
codec: 'h264',
inputProps: {},
framesPerLambda: 20,
});
// Check progress
const progress = await getRenderProgress({
renderId,
bucketName,
region: 'us-east-1',
functionName,
});
// Download when done
if (progress.done) {
await downloadMedia({
bucketName,
renderId,
region: 'us-east-1',
outPath: 'video.mp4',
});
}
```
## @remotion/player API
```tsx
import { Player, PlayerRef } from '@remotion/player';
const playerRef = useRef<PlayerRef>(null);
<Player
ref={playerRef}
component={MyComp}
// OR lazyComponent={() => import('./MyComp')}
durationInFrames={150}
fps={30}
compositionWidth={1920}
compositionHeight={1080}
inputProps={{}}
style={{ width: '100%' }}
controls={true}
autoPlay={false}
loop={false}
showVolumeControls={true}
allowFullscreen={true}
clickToPlay={true}
doubleClickToFullscreen={true}
spaceKeyToPlayOrPause={true}
playbackRate={1}
renderLoading={() => <div>Loading...</div>}
errorFallback={({ error }) => <div>Error: {error.message}</div>}
numberOfSharedAudioTags={5}
initiallyShowControls={3000}
renderPlayPauseButton={() => null}
moveToBeginningWhenEnded={true}
/>
// Imperative API
playerRef.current?.play();
playerRef.current?.pause();
playerRef.current?.toggle();
playerRef.current?.seekTo(30);
playerRef.current?.getCurrentFrame();
playerRef.current?.isPlaying();
playerRef.current?.getVolume();
playerRef.current?.setVolume(0.5);
playerRef.current?.isMuted();
playerRef.current?.mute();
playerRef.current?.unmute();
playerRef.current?.requestFullscreen();
playerRef.current?.exitFullscreen();
playerRef.current?.isFullscreen();
// Events
<Player
onPlay={() => {}}
onPause={() => {}}
onEnded={() => {}}
onError={(e) => {}}
onSeeked={(frame) => {}}
onTimeUpdate={({ frame }) => {}}
onFullscreenChange={(isFullscreen) => {}}
/>
```
## calculateMetadata
Dynamic composition properties:
```tsx
export const calculateMetadata: CalculateMetadataFunction<Props> = async ({
props,
abortSignal,
defaultProps
}) => {
const data = await fetch('/api/data', { signal: abortSignal });
return {
durationInFrames: data.items.length * 30,
fps: 60,
width: 1920,
height: 1080,
props: { ...props, items: data.items },
};
};
<Composition
id="Dynamic"
component={MyComp}
calculateMetadata={calculateMetadata}
// Base values (can be overridden by calculateMetadata)
durationInFrames={1}
fps={30}
width={1920}
height={1080}
/>
```
SKILL.md
---
name: remotion
description: Toolkit-specific Remotion patterns — custom transitions, shared components, and project conventions. For core Remotion framework knowledge (hooks, animations, rendering, etc.), see the `remotion-official` skill.
---
# Remotion — Toolkit Extensions
> **Core Remotion knowledge** lives in `.claude/skills/remotion-official/` (synced from the official [remotion-dev/skills](https://github.com/remotion-dev/skills) repo). This file covers **toolkit-specific** patterns only.
## Shared Components
Reusable video components in `lib/components/`. Import in templates via:
```tsx
import { AnimatedBackground, SlideTransition, Label } from '../../../../lib/components';
```
| Component | Purpose |
|-----------|---------|
| `AnimatedBackground` | Floating shapes background (variants: subtle, tech, warm, dark) |
| `SlideTransition` | Scene transitions (fade, zoom, slide-up, blur-fade) |
| `Label` | Floating label badge with optional JIRA reference |
| `Vignette` | Cinematic edge darkening overlay |
| `LogoWatermark` | Corner logo branding |
| `SplitScreen` | Side-by-side video comparison |
| `NarratorPiP` | Picture-in-picture presenter overlay |
| `Envelope` | 3D envelope with opening flap animation |
| `PointingHand` | Animated hand emoji with slide-in and pulse |
| `MazeDecoration` | Animated isometric grid decoration for corners |
## Custom Transitions
The toolkit includes a transitions library at `lib/transitions/` for scene-to-scene effects beyond the official `@remotion/transitions` package.
### Using TransitionSeries
```tsx
import { TransitionSeries, linearTiming } from '@remotion/transitions';
// Import custom transitions from lib (adjust path based on your project location)
import { glitch, lightLeak, clockWipe, checkerboard } from '../../../../lib/transitions';
// Or import from @remotion/transitions for official ones
import { slide, fade } from '@remotion/transitions/slide';
<TransitionSeries>
<TransitionSeries.Sequence durationInFrames={90}>
<TitleSlide />
</TransitionSeries.Sequence>
<TransitionSeries.Transition
presentation={glitch({ intensity: 0.8 })}
timing={linearTiming({ durationInFrames: 30 })}
/>
<TransitionSeries.Sequence durationInFrames={120}>
<ContentSlide />
</TransitionSeries.Sequence>
</TransitionSeries>
```
### Available Custom Transitions
| Transition | Options | Best For |
|------------|---------|----------|
| `glitch()` | `intensity`, `slices`, `rgbShift` | Tech demos, edgy reveals, cyberpunk |
| `rgbSplit()` | `direction`, `displacement` | Modern tech, energetic transitions |
| `zoomBlur()` | `direction`, `blurAmount` | CTAs, high-energy moments, impact |
| `lightLeak()` | `temperature`, `direction` | Celebrations, film aesthetic, warm moments |
| `clockWipe()` | `startAngle`, `direction`, `segments` | Time-related content, playful reveals |
| `pixelate()` | `maxBlockSize`, `gridSize`, `scanlines`, `glitchArtifacts`, `randomness` | Retro/gaming, digital transformations |
| `checkerboard()` | `gridSize`, `pattern`, `stagger`, `squareAnimation` | Playful reveals, structured transitions |
**Checkerboard patterns:** `sequential`, `random`, `diagonal`, `alternating`, `spiral`, `rows`, `columns`, `center-out`, `corners-in`
### Transition Examples
```tsx
// Tech/cyberpunk feel
glitch({ intensity: 0.8, slices: 8, rgbShift: true })
// Warm celebration
lightLeak({ temperature: 'warm', direction: 'right' })
// High energy zoom
zoomBlur({ direction: 'in', blurAmount: 20 })
// Chromatic aberration
rgbSplit({ direction: 'diagonal', displacement: 30 })
// Clock sweep reveal
clockWipe({ direction: 'clockwise', startAngle: 0 })
// Retro pixelation
pixelate({ maxBlockSize: 50, glitchArtifacts: true })
// Checkerboard patterns
checkerboard({ pattern: 'diagonal', gridSize: 8 })
checkerboard({ pattern: 'spiral', gridSize: 10 })
checkerboard({ pattern: 'center-out', squareAnimation: 'scale' })
```
### Transition Duration Guidelines
| Type | Frames | Notes |
|------|--------|-------|
| Quick cut | 15-20 | Fast, punchy |
| Standard | 30-45 | Most common |
| Dramatic | 50-60 | Slow reveals |
| Glitch effects | 20-30 | Should feel sudden |
| Light leak | 45-60 | Needs time to sweep |
### Preview Transitions
Run the showcase gallery to see all transitions:
```bash
cd showcase/transitions && npm run studio
```
## Toolkit Best Practices
1. **Frame-based animations only** — Avoid CSS transitions/animations; they cause flickering during render
2. **Use fps from useVideoConfig()** — Make animations frame-rate independent
3. **Clamp interpolations** — Use `extrapolateRight: 'clamp'` to prevent runaway values
4. **Use OffthreadVideo** — Better performance than `<Video>` for complex compositions
5. **delayRender for async** — Always block rendering until data is ready
6. **staticFile for assets** — Reference files from `public/` folder correctly
7. **All projects use 30fps** — Timing: frames = seconds × 30
8. **playbackRate must be constant** — For variable/extreme speeds, pre-process with FFmpeg
## Project Timing Conventions
| Scene Type | Duration | Notes |
|------------|----------|-------|
| Title | 3-5s (90-150f) | Logo + headline |
| Overview | 10-20s | 3-5 bullet points |
| Demo | 10-30s | Adjust playbackRate to fit |
| Stats | 8-12s | 3-4 stat cards |
| Credits | 5-10s | Quick fade |
**Pacing:** ~150 words/minute for voiceover. Voiceover drives timing.
## Advanced API
For detailed API documentation on all hooks, components, renderer, Lambda, and Player APIs, see [reference.md](reference.md).
## License Note
Remotion has a special license. Companies may need to obtain a license for commercial use. Check https://remotion.dev/license
---
## Feedback & Contributions
If this skill is missing information or could be improved:
- **Missing a pattern?** Describe what you needed
- **Found an error?** Let me know what's wrong
- **Want to contribute?** I can help you:
1. Update this skill with improvements
2. Create a PR to github.com/digitalsamba/claude-code-video-toolkit
Just say "improve this skill" and I'll guide you through updating `.claude/skills/remotion/SKILL.md`.