agentsclimarketplace

Remotion

Skill tinh2/skills-hub-registry/build/remotion

Build programmatic videos with Remotion (React). Covers compositions, animations, sequencing, transitions, audio/video embedding, spring physics, text animations, voiceover generation with ElevenLabs TTS, FFmpeg integration, captions/subtitles, 3D with Three.js, charts, and rendering. Based on official Remotion best practices.From its SKILL.md

Install
npx -y skills add tinh2/skills-hub-registry --skill remotion

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 12 stars12 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

10.3 KB, ~2.7k tokens by cl100k_base, as published. Nobody here has run it

Remotion -- Video Creation in React

Use this skill when working with Remotion code. Remotion lets you create videos programmatically using React components.

Project Setup

npx create-video@latest my-video
cd my-video
npm start        # Preview in browser
npm run render   # Render to MP4

Compositions

A <Composition> defines a renderable video with dimensions, fps, and duration.

import { Composition } from "remotion";
import { MyVideo } from "./MyVideo";

export const RemotionRoot = () => (
  <Composition
    id="MyVideo"
    component={MyVideo}
    durationInFrames={300}
    fps={30}
    width={1920}
    height={1080}
    defaultProps={{ title: "Hello World" } satisfies MyVideoProps}
  />
);

Use type declarations for props (not interface) for defaultProps type safety.

Use <Folder> to organize compositions in the sidebar. Use <Still> for single-frame outputs (thumbnails, OG images).

Animations

All animations MUST use useCurrentFrame(). CSS transitions and Tailwind animation classes are FORBIDDEN -- they will not render correctly.

import { useCurrentFrame, useVideoConfig, interpolate } from "remotion";

export const FadeIn = () => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  const opacity = interpolate(frame, [0, 2 * fps], [0, 1], {
    extrapolateRight: "clamp",
  });

  return <div style={{ opacity }}>Hello World!</div>;
};

Spring Animations

Springs provide natural motion. They animate from 0 to 1.

import { spring, useCurrentFrame, useVideoConfig } from "remotion";

const frame = useCurrentFrame();
const { fps } = useVideoConfig();

const scale = spring({ frame, fps });
// With config:
const smooth = spring({ frame, fps, config: { damping: 200 } }); // No bounce
const snappy = spring({ frame, fps, config: { damping: 20, stiffness: 200 } });
const bouncy = spring({ frame, fps, config: { damping: 8 } });
const heavy  = spring({ frame, fps, config: { damping: 15, stiffness: 80, mass: 2 } });

Use delay parameter to delay spring start:

const entrance = spring({ frame, fps, delay: 20 });

Interpolation

// Linear interpolation with clamping
const opacity = interpolate(frame, [0, 100], [0, 1], {
  extrapolateRight: "clamp",
  extrapolateLeft: "clamp",
});

// Multi-step interpolation
const y = interpolate(frame, [0, 30, 60, 90], [100, 0, 0, -100]);

Sequencing

Sequence

Use <Sequence> to delay when elements appear. Always use premountFor to preload components.

import { Sequence } from "remotion";

const { fps } = useVideoConfig();

<Sequence from={1 * fps} durationInFrames={2 * fps} premountFor={1 * fps}>
  <Title />
</Sequence>
<Sequence from={2 * fps} durationInFrames={2 * fps} premountFor={1 * fps}>
  <Subtitle />
</Sequence>

Use layout="none" to prevent absolute positioning wrapper.

Series

Use <Series> for sequential playback without overlap:

import { Series } from "remotion";

<Series>
  <Series.Sequence durationInFrames={45}><Intro /></Series.Sequence>
  <Series.Sequence durationInFrames={60}><MainContent /></Series.Sequence>
  <Series.Sequence durationInFrames={30}><Outro /></Series.Sequence>
</Series>

Negative offset creates overlapping transitions:

<Series.Sequence offset={-15} durationInFrames={60}>
  <SceneB />
</Series.Sequence>

Transitions

npx remotion add @remotion/transitions
import { TransitionSeries, linearTiming } from "@remotion/transitions";
import { fade } from "@remotion/transitions/fade";
import { slide } from "@remotion/transitions/slide";
import { wipe } from "@remotion/transitions/wipe";

<TransitionSeries>
  <TransitionSeries.Sequence durationInFrames={60}>
    <SceneA />
  </TransitionSeries.Sequence>
  <TransitionSeries.Transition
    presentation={fade()}
    timing={linearTiming({ durationInFrames: 15 })}
  />
  <TransitionSeries.Sequence durationInFrames={60}>
    <SceneB />
  </TransitionSeries.Sequence>
</TransitionSeries>

Light Leak Overlays

import { LightLeak } from "@remotion/light-leaks";

<TransitionSeries>
  <TransitionSeries.Sequence durationInFrames={60}>
    <SceneA />
  </TransitionSeries.Sequence>
  <TransitionSeries.Overlay durationInFrames={20}>
    <LightLeak />
  </TransitionSeries.Overlay>
  <TransitionSeries.Sequence durationInFrames={60}>
    <SceneB />
  </TransitionSeries.Sequence>
</TransitionSeries>

An overlay cannot be adjacent to a transition or another overlay.

Audio

npx remotion add @remotion/media
import { Audio } from "@remotion/media";
import { staticFile } from "remotion";

// Basic audio
<Audio src={staticFile("audio.mp3")} />

// With trimming (values in frames)
<Audio src={staticFile("audio.mp3")} trimBefore={2 * fps} trimAfter={10 * fps} />

// Volume control
<Audio src={staticFile("audio.mp3")} volume={0.5} />

// Dynamic volume (callback receives frame)
<Audio src={staticFile("audio.mp3")} volume={(f) =>
  interpolate(f, [0, 30], [0, 1], { extrapolateRight: "clamp" })
} />

// Speed change
<Audio src={staticFile("audio.mp3")} playbackRate={1.5} />

// Delay audio start
<Sequence from={1 * fps}>
  <Audio src={staticFile("audio.mp3")} />
</Sequence>

Multiple <Audio> components layer automatically.

Video Embedding

import { Video } from "@remotion/media";

// Basic video
<Video src={staticFile("video.mp4")} />

// With trimming
<Video src={staticFile("video.mp4")} trimBefore={2 * fps} trimAfter={10 * fps} />

// Speed, volume, looping
<Video src={staticFile("video.mp4")} playbackRate={2} volume={0.5} loop />

// For rendering performance, use OffthreadVideo
import { OffthreadVideo } from "remotion";
<OffthreadVideo src={staticFile("video.mp4")} />

ALWAYS use <OffthreadVideo> for better rendering performance. Use <Video> only during preview.

Voiceover with ElevenLabs TTS

Generate speech per scene, then use calculateMetadata to size the composition dynamically.

Generate Audio

const response = await fetch(
  `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,
  {
    method: "POST",
    headers: {
      "xi-api-key": process.env.ELEVENLABS_API_KEY!,
      "Content-Type": "application/json",
      Accept: "audio/mpeg",
    },
    body: JSON.stringify({
      text: "Welcome to the show.",
      model_id: "eleven_multilingual_v2",
      voice_settings: { stability: 0.5, similarity_boost: 0.75, style: 0.3 },
    }),
  },
);
const audioBuffer = Buffer.from(await response.arrayBuffer());
writeFileSync(`public/voiceover/scene-01.mp3`, audioBuffer);

Dynamic Duration

import { CalculateMetadataFunction, staticFile } from "remotion";

const FPS = 30;
const SCENES = ["voiceover/scene-01.mp3", "voiceover/scene-02.mp3"];

export const calculateMetadata: CalculateMetadataFunction<Props> = async () => {
  const durations = await Promise.all(
    SCENES.map((file) => getAudioDuration(staticFile(file)))
  );
  const totalFrames = durations.reduce((sum, d) => sum + Math.ceil(d * FPS), 0);
  return { durationInFrames: totalFrames };
};

Assets

Place files in public/ and reference with staticFile():

import { staticFile, Img } from "remotion";

<Img src={staticFile("logo.png")} />

NEVER use require() or import for media files. Always use staticFile().

Fonts

import { loadFont } from "@remotion/google-fonts/Inter";
const { fontFamily } = loadFont();

// Or local fonts
const { fontFamily } = loadFont({
  url: staticFile("fonts/MyFont.woff2"),
  display: "swap",
});

FFmpeg in Remotion

FFmpeg is bundled -- no install needed:

bunx remotion ffmpeg -i input.mp4 output.mp3
bunx remotion ffprobe input.mp4

For trimming, prefer the trimBefore/trimAfter props over FFmpeg. Use FFmpeg for format conversion or operations Remotion cannot do natively.

When using FFmpeg to trim, MUST re-encode to avoid frozen frames:

bunx remotion ffmpeg -ss 00:00:05 -i public/input.mp4 -to 00:00:10 -c:v libx264 -c:a aac public/output.mp4

Rendering

# Render specific composition
npx remotion render MyVideo output.mp4

# Render a still frame
npx remotion still MyVideo --frame=100 thumbnail.png

# With custom props
npx remotion render MyVideo output.mp4 --props='{"title": "Custom"}'

# Specific codec
npx remotion render MyVideo output.webm --codec=vp8

Common Patterns

Scene-Based Video

const SCENES = [
  { component: TitleScene, duration: 90 },
  { component: ProblemScene, duration: 150 },
  { component: SolutionScene, duration: 120 },
  { component: CTAScene, duration: 90 },
];

export const MyVideo = () => {
  let offset = 0;
  return (
    <>
      {SCENES.map(({ component: Component, duration }, i) => {
        const from = offset;
        offset += duration;
        return (
          <Sequence key={i} from={from} durationInFrames={duration}>
            <Component />
          </Sequence>
        );
      })}
    </>
  );
};

Background Music + Voiceover

<>
  {/* Background music at low volume */}
  <Audio src={staticFile("music/bg.mp3")} volume={0.12} loop />

  {/* Scene voiceovers at full volume */}
  <Sequence from={0} durationInFrames={scene1Frames}>
    <Audio src={staticFile("voiceover/scene-01.mp3")} volume={1} />
  </Sequence>
  <Sequence from={scene1Frames} durationInFrames={scene2Frames}>
    <Audio src={staticFile("voiceover/scene-02.mp3")} volume={1} />
  </Sequence>
</>

Key Rules

  1. No CSS animations -- all motion via useCurrentFrame() + interpolate()/spring()
  2. No Tailwind animation classes -- they don't render correctly
  3. Always staticFile() for assets, never require()
  4. Always premount Sequences with premountFor
  5. Use <OffthreadVideo> not <video> for rendering
  6. Write durations in seconds and multiply by fps
  7. Use satisfies keyword for defaultProps type safety

Source: remotion-dev/skills, remotion.dev

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.