Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8958735
Vendor Remotion's official agent skills
Jing-yilin Sep 2, 2026
80f18d2
Remotion skills live in .agents/skills/, symlinked like the other three
Jing-yilin Sep 2, 2026
0d8b7a5
Add motion/: one folder per animation asset, discovered like the boards
Jing-yilin Sep 2, 2026
48b6f2d
Merge origin/main into worktree-remotion-skill
Jing-yilin Sep 2, 2026
cf03d59
Welcome board: a third row of cards, the boards moving
Jing-yilin Sep 2, 2026
2108373
Welcome board: motion cards at full board width, on one heading line
Jing-yilin Sep 2, 2026
178acea
Motion: make the discovery contract hold at thirty assets, not one
Jing-yilin Sep 2, 2026
14d7f98
Merge origin/main into worktree-remotion-skill
Jing-yilin Sep 2, 2026
ef72f0d
Fifteen motion templates off one film, one effect each
Jing-yilin Sep 2, 2026
0e312b0
Measure the shots again: two templates were the wrong effect
Jing-yilin Sep 2, 2026
5ba8556
Cut the fifteen into a film, and find twelve of them were too small
Jing-yilin Sep 3, 2026
f18c1f0
Re-measure the cut against the source, and fix what the measuring got…
Jing-yilin Sep 3, 2026
dc5cbfc
pill-expand: land the title and body together, and stop counting ink …
Jing-yilin Sep 3, 2026
7d37379
Open on black instead of the brightest frame in the piece, and fit ca…
Jing-yilin Sep 3, 2026
e1c140a
Dissolve the seven joins the source dissolves, and stop word-cascade …
Jing-yilin Sep 3, 2026
dcb077f
Stack the type over the figure, and fade it up as the source does
Jing-yilin Sep 3, 2026
5547e30
Ten seconds of the Duolingo clone, from grid to diff
Jing-yilin Sep 4, 2026
87f3acc
Repaint the promo reel in Primer light
Jing-yilin Sep 4, 2026
df68f7c
Join the shots instead of cutting between them
Jing-yilin Sep 4, 2026
20b3fc1
Say it in six plain steps instead of thirty measurements
Jing-yilin Sep 5, 2026
a52d3ad
Carry four objects across the joins instead of dissolving them
Jing-yilin Sep 5, 2026
48c4a96
Open on the phone, centre frame, and walk it into the measurement
Jing-yilin Sep 5, 2026
cfa0a1f
Show the two steps the film was skipping: the block, and the artwork
Jing-yilin Sep 5, 2026
d300f34
Face: drop the confidence bar, show the two numbers a size is read from
Jing-yilin Sep 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
59 changes: 59 additions & 0 deletions .agents/skills/remotion-best-practices/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
name: remotion-best-practices
description: Router for all Remotion skills
version: 4.0.520
---

## Preserve user changes

Users may make edits in the code outside of the conversation.

If you detect a surprising change made in the meanwhile, don't overwrite it, assume it was intentional or ask for confirmation.

## Creating a video

If the user asks to make, create, or build a new video or composition, load [Create a new Remotion video](./remotion-create/REFERENCE.md), whether or not a Remotion project already exists.

## New project setup

If no Remotion project currently exists, load [Create a new Remotion project](./remotion-create/REFERENCE.md)

## React Markup Best Practices

If you are writing Remotion React Markup, load [Remotion Markup Best Practices](./remotion-markup/REFERENCE.md)

## Maps

For static maps, animated routes and markers, geographic explainers, Mapbox, MapLibre, MapTiler, GeoJSON, or 3D geographic flyovers, load [Remotion Maps](./remotion-maps/REFERENCE.md).

## Multimedia

For achieving multimedia tasks in the browser, such as trimming, cropping videos, or getting metadata from them, load [Remotion Multimedia](./remotion-multimedia/REFERENCE.md)

## Improving Interactivity

By structuring the Remotion markup well, we can allow users to interactively change things in the Studio and write back to code. If relevant: [Interactivity Best Practices](./remotion-interactivity/REFERENCE.md)

## Rendering

For advanced rendering beyond simple `npx remotion render`, see: [Rendering Best Practices](./remotion-render/REFERENCE.md)

## Opening Remotion Studio

To launch a project in Remotion Studio, open its exact local URL, or configure Studio CLI flags, load [Remotion Studio](./remotion-studio/REFERENCE.md).

## Captions

When working with Captions, load [Remotion Captions](./remotion-captions/REFERENCE.md).

## Creating a SaaS, automation or application

Use the [Remotion SaaS skill](./remotion-saas/REFERENCE.md) for knowledge about Remotion-powered SaaS apps, such as `<Player>`, rendering on Lambda, Vercel, Cloudflare, via Express.js, client-side rendering, or for finding the right SaaS template.

## Looking up Remotion APIs and documentation

To find and read current Remotion documentation, load [Remotion Docs](./remotion-docs/REFERENCE.md).

## Upgrading

To upgrade Remotion, related packages, compatible Mediabunny packages, and installed Remotion Agent Skills, load [Remotion Upgrade](./remotion-upgrade/REFERENCE.md).
7 changes: 7 additions & 0 deletions .agents/skills/remotion-best-practices/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
interface:
display_name: '/remotion-best-practices'
short_description: 'Route Remotion tasks to the right workflow'
icon_small: './assets/remotion-icon.png'
icon_large: './assets/remotion-icon.png'
brand_color: '#0B84F3'
default_prompt: '$remotion-best-practices: Make a promo video for my product'
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
name: remotion-captions
description: Transcribing, displaying and animating captions
version: 4.0.520
---

All captions must be processed in JSON. The captions must use the [`Caption`](https://www.remotion.dev/docs/captions/caption.md) type which is the following:

```ts
import type { Caption } from "@remotion/captions";
```

This is the definition:

```ts
type Caption = {
text: string;
startMs: number;
endMs: number;
timestampMs: number | null;
confidence: number | null;
pageBreakAfter?: boolean;
};
```

## Generating captions

To transcribe video and audio files to generate captions, load the [transcribe-captions.md](transcribe-captions.md) file for more instructions.

## Displaying captions

To display captions in your video, load the [display-captions.md](display-captions.md) file for more instructions.

## Importing captions

To import captions from a .srt file, load the [import-srt-captions.md](import-srt-captions.md) file for more instructions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
interface:
display_name: '/remotion-captions'
short_description: 'Transcribe, display, and animate captions'
icon_small: './assets/remotion-icon.png'
icon_large: './assets/remotion-icon.png'
brand_color: '#0B84F3'
default_prompt: '$remotion-captions: Transcribe my video and display captions'
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
---
name: display-captions
description: Displaying captions in Remotion with TikTok-style pages and word highlighting
metadata:
tags: captions, subtitles, display, tiktok, highlight
---

# Displaying captions in Remotion

This guide explains how to display captions in Remotion, assuming you already have captions in the [`Caption`](https://www.remotion.dev/docs/captions/caption) format.

## Prerequisites

Read [Transcribing audio](transcribe-captions.md) for how to generate captions.

First, the [`@remotion/captions`](https://www.remotion.dev/docs/captions) package needs to be installed.
If it is not installed, use the following command:

```bash
npx remotion add @remotion/captions
```

## Fetching captions

First, fetch your captions JSON file. Use [`useDelayRender()`](https://www.remotion.dev/docs/use-delay-render) to hold the render until the captions are loaded:

```tsx
import { useState, useEffect, useCallback } from "react";
import { AbsoluteFill, staticFile, useDelayRender } from "remotion";
import type { Caption } from "@remotion/captions";

export const MyComponent: React.FC = () => {
const [captions, setCaptions] = useState<Caption[] | null>(null);
const { delayRender, continueRender, cancelRender } = useDelayRender();
const [handle] = useState(() => delayRender());

const fetchCaptions = useCallback(async () => {
try {
// Assuming captions.json is in the public/ folder.
const response = await fetch(staticFile("captions123.json"));
const data = await response.json();
setCaptions(data);
continueRender(handle);
} catch (e) {
cancelRender(e);
}
}, [continueRender, cancelRender, handle]);

useEffect(() => {
fetchCaptions();
}, [fetchCaptions]);

if (!captions) {
return null;
}

return (
<AbsoluteFill>
{/* Render captions here */}
</AbsoluteFill>
);
};
```

## Creating pages

Use `createTikTokStyleCaptions()` to group captions into pages. The `combineTokensWithinMilliseconds` option controls how many words appear at once:

```tsx
import { useMemo } from "react";
import { createTikTokStyleCaptions } from "@remotion/captions";
import type { Caption } from "@remotion/captions";

// How often captions should switch (in milliseconds)
// Higher values = more words per page
// Lower values = fewer words (more word-by-word)
const SWITCH_CAPTIONS_EVERY_MS = 1200;

const { pages } = useMemo(() => {
return createTikTokStyleCaptions({
captions,
combineTokensWithinMilliseconds: SWITCH_CAPTIONS_EVERY_MS,
});
}, [captions]);
```

If a caption has `pageBreakAfter: true`, the current page ends after that caption and the next caption starts a new page.

## Rendering with Sequences

Map over the pages and render each one in a `<Sequence>`. Calculate the start frame and duration from the page timing:

```tsx
import { Sequence, useVideoConfig, AbsoluteFill } from "remotion";
import type { TikTokPage } from "@remotion/captions";

const CaptionedContent: React.FC = () => {
const { fps } = useVideoConfig();

return (
<AbsoluteFill>
{pages.map((page, index) => {
const nextPage = pages[index + 1] ?? null;
const startFrame = (page.startMs / 1000) * fps;
const endFrame = Math.min(
nextPage ? (nextPage.startMs / 1000) * fps : Infinity,
startFrame + (SWITCH_CAPTIONS_EVERY_MS / 1000) * fps,
);
const durationInFrames = endFrame - startFrame;

if (durationInFrames <= 0) {
return null;
}

return (
<Sequence
key={index}
from={startFrame}
durationInFrames={durationInFrames}
>
<CaptionPage page={page} />
</Sequence>
);
})}
</AbsoluteFill>
);
};
```

## White-space preservation

The captions are whitespace sensitive. You should include spaces in the `text` field before each word. Use `whiteSpace: "pre"` to preserve the whitespace in the captions.

## Separate component for captions

Put captioning logic in a separate component.
Make a new file for it.

## Word highlighting

A caption page contains `tokens` which you can use to highlight the currently spoken word:

```tsx
import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "remotion";
import type { TikTokPage } from "@remotion/captions";

const HIGHLIGHT_COLOR = "#39E508";

const CaptionPage: React.FC<{ page: TikTokPage }> = ({ page }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();

// Current time relative to the start of the sequence
const currentTimeMs = (frame / fps) * 1000;
// Convert to absolute time by adding the page start
const absoluteTimeMs = page.startMs + currentTimeMs;

return (
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center" }}>
<div style={{ fontSize: 80, fontWeight: "bold", whiteSpace: "pre" }}>
{page.tokens.map((token, tokenIndex) => {
const isActive =
token.fromMs <= absoluteTimeMs && token.toMs > absoluteTimeMs;

return (
<span
key={`${token.fromMs}-${tokenIndex}`}
style={{ color: isActive ? HIGHLIGHT_COLOR : "white" }}
>
{token.text}
</span>
);
})}
</div>
</AbsoluteFill>
);
};
```

## Display captions alongside video content

By default, put the captions alongside the video content, so the captions are in sync.
For each video, make a new captions JSON file.

```tsx
<AbsoluteFill>
<Video src={staticFile("video.mp4")} />
<CaptionPage page={page} />
</AbsoluteFill>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
name: import-srt-captions
description: Importing .srt subtitle files into Remotion using @remotion/captions
metadata:
tags: captions, subtitles, srt, import, parse
---

# Importing .srt subtitles into Remotion

If you have an existing `.srt` subtitle file, you can import it into Remotion using `parseSrt()` from `@remotion/captions`.

If you don't have a .srt file, read [Transcribing audio](transcribe-captions.md) for how to generate captions instead.

## Prerequisites

First, the @remotion/captions package needs to be installed.
If it is not installed, use the following command:

```bash
npx remotion add @remotion/captions # If project uses npm
bunx remotion add @remotion/captions # If project uses bun
yarn remotion add @remotion/captions # If project uses yarn
pnpm exec remotion add @remotion/captions # If project uses pnpm
```

## Reading an .srt file

Use `staticFile()` to reference an `.srt` file in your `public` folder, then fetch and parse it:

```tsx
import { useState, useEffect, useCallback } from "react";
import { AbsoluteFill, staticFile, useDelayRender } from "remotion";
import { parseSrt } from "@remotion/captions";
import type { Caption } from "@remotion/captions";

export const MyComponent: React.FC = () => {
const [captions, setCaptions] = useState<Caption[] | null>(null);
const { delayRender, continueRender, cancelRender } = useDelayRender();
const [handle] = useState(() => delayRender());

const fetchCaptions = useCallback(async () => {
try {
const response = await fetch(staticFile("subtitles.srt"));
const text = await response.text();
const { captions: parsed } = parseSrt({ input: text });
setCaptions(parsed);
continueRender(handle);
} catch (e) {
cancelRender(e);
}
}, [continueRender, cancelRender, handle]);

useEffect(() => {
fetchCaptions();
}, [fetchCaptions]);

if (!captions) {
return null;
}

return (
<AbsoluteFill>
{/* Use captions here */}
</AbsoluteFill>
);
};
```

Remote URLs are also supported - you can `fetch()` a remote file via URL instead of using `staticFile()`.

## Using imported captions

Once parsed, the captions are in the `Caption` format and can be used with all `@remotion/captions` utilities.
Loading
Loading