Add remotion-video skill #1
No files matched your search
@@ -80,5 +80,12 @@ export const Hello = ({ title }: { title: string }) => {
|
|||||||
- **Flicker**: an image or font not loaded; use `<Img>` and `@remotion/google-fonts`.
|
- **Flicker**: an image or font not loaded; use `<Img>` and `@remotion/google-fonts`.
|
||||||
- **Slow render**: lower resolution/fps while testing, or use `--concurrency`.
|
- **Slow render**: lower resolution/fps while testing, or use `--concurrency`.
|
||||||
|
|
||||||
|
## Templates
|
||||||
|
Copy-paste starting points. Read the one that fits:
|
||||||
|
- `templates/captions.md` - words highlighted in time with speech
|
||||||
|
- `templates/charts.md` - animated bar chart
|
||||||
|
- `templates/intro-outro.md` - title card + chaining scenes with `Series`
|
||||||
|
- `templates/vertical.md` - 1080x1920 clips for Reels/TikTok/Shorts
|
||||||
|
|
||||||
## Docs
|
## Docs
|
||||||
https://www.remotion.dev/docs
|
https://www.remotion.dev/docs
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Template: Captions
|
||||||
|
|
||||||
|
Words appear in time with speech. Give each word a start time in seconds.
|
||||||
|
|
||||||
|
`src/Captions.tsx`
|
||||||
|
```tsx
|
||||||
|
import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "remotion";
|
||||||
|
|
||||||
|
type Word = { text: string; start: number; end: number }; // seconds
|
||||||
|
|
||||||
|
export const Captions = ({ words }: { words: Word[] }) => {
|
||||||
|
const frame = useCurrentFrame();
|
||||||
|
const { fps } = useVideoConfig();
|
||||||
|
const t = frame / fps;
|
||||||
|
|
||||||
|
// Show a window of up to 5 words around the current one.
|
||||||
|
const current = words.findIndex((w) => t >= w.start && t < w.end);
|
||||||
|
const i = current === -1 ? 0 : current;
|
||||||
|
const visible = words.slice(Math.floor(i / 5) * 5, Math.floor(i / 5) * 5 + 5);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AbsoluteFill style={{ justifyContent: "flex-end", alignItems: "center", paddingBottom: 200 }}>
|
||||||
|
<div style={{ fontSize: 72, fontWeight: 800, color: "white", textShadow: "0 4px 12px black", textAlign: "center", maxWidth: "80%" }}>
|
||||||
|
{visible.map((w, k) => (
|
||||||
|
<span key={k} style={{ color: t >= w.start && t < w.end ? "#ffd400" : "white", marginRight: 16 }}>
|
||||||
|
{w.text}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</AbsoluteFill>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Tips
|
||||||
|
- Get word times from a transcription tool (e.g. `@remotion/captions` or Whisper) and save them as JSON in `public/`.
|
||||||
|
- Layer it over the video: `<AbsoluteFill><OffthreadVideo src={staticFile("clip.mp4")} /><Captions words={words} /></AbsoluteFill>`.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Template: Animated bar chart
|
||||||
|
|
||||||
|
Bars grow one after another.
|
||||||
|
|
||||||
|
`src/BarChart.tsx`
|
||||||
|
```tsx
|
||||||
|
import { AbsoluteFill, spring, useCurrentFrame, useVideoConfig } from "remotion";
|
||||||
|
|
||||||
|
type Bar = { label: string; value: number };
|
||||||
|
|
||||||
|
export const BarChart = ({ data, color = "#4f8cff" }: { data: Bar[]; color?: string }) => {
|
||||||
|
const frame = useCurrentFrame();
|
||||||
|
const { fps } = useVideoConfig();
|
||||||
|
const max = Math.max(...data.map((d) => d.value));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AbsoluteFill style={{ backgroundColor: "#0b1020", padding: 120, justifyContent: "flex-end" }}>
|
||||||
|
<div style={{ display: "flex", alignItems: "flex-end", gap: 40, height: 700 }}>
|
||||||
|
{data.map((d, i) => {
|
||||||
|
const grow = spring({ frame: frame - i * 6, fps, config: { damping: 15 } });
|
||||||
|
return (
|
||||||
|
<div key={d.label} style={{ flex: 1, textAlign: "center", color: "white", fontSize: 36 }}>
|
||||||
|
<div style={{ height: (d.value / max) * 600 * grow, background: color, borderRadius: 8 }} />
|
||||||
|
<div style={{ marginTop: 16 }}>{d.label}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</AbsoluteFill>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Tips
|
||||||
|
- Delay each bar with `frame - i * 6` (6 frames apart).
|
||||||
|
- For lines, animate `strokeDashoffset` on an SVG path using `interpolate`.
|
||||||
|
- Keep numbers readable: show the value as text, rounded.
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Template: Title intro / outro card
|
||||||
|
|
||||||
|
A short card (2-3 seconds) with a title and subtitle. Use at the start or end.
|
||||||
|
|
||||||
|
`src/TitleCard.tsx`
|
||||||
|
```tsx
|
||||||
|
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
|
||||||
|
|
||||||
|
export const TitleCard = ({ title, subtitle }: { title: string; subtitle?: string }) => {
|
||||||
|
const frame = useCurrentFrame();
|
||||||
|
const { fps, durationInFrames } = useVideoConfig();
|
||||||
|
const rise = spring({ frame, fps, config: { damping: 14 } });
|
||||||
|
const fadeOut = interpolate(frame, [durationInFrames - 15, durationInFrames], [1, 0], {
|
||||||
|
extrapolateLeft: "clamp",
|
||||||
|
extrapolateRight: "clamp",
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AbsoluteFill style={{ background: "linear-gradient(135deg,#1b2a6b,#0b1020)", justifyContent: "center", alignItems: "center", opacity: fadeOut }}>
|
||||||
|
<h1 style={{ color: "white", fontSize: 130, margin: 0, transform: `translateY(${(1 - rise) * 60}px)`, opacity: rise }}>{title}</h1>
|
||||||
|
{subtitle && <p style={{ color: "#9fb3ff", fontSize: 48, opacity: rise }}>{subtitle}</p>}
|
||||||
|
</AbsoluteFill>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Chain with the main video using `<Series>`:
|
||||||
|
```tsx
|
||||||
|
<Series>
|
||||||
|
<Series.Sequence durationInFrames={90}><TitleCard title="My Video" /></Series.Sequence>
|
||||||
|
<Series.Sequence durationInFrames={600}><MainScene /></Series.Sequence>
|
||||||
|
<Series.Sequence durationInFrames={90}><TitleCard title="Thanks for watching" /></Series.Sequence>
|
||||||
|
</Series>
|
||||||
|
```
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Template: Vertical clips (Reels / TikTok / Shorts)
|
||||||
|
|
||||||
|
Same code, different size. Register a vertical composition:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Composition
|
||||||
|
id="Vertical"
|
||||||
|
component={MyScene}
|
||||||
|
width={1080}
|
||||||
|
height={1920}
|
||||||
|
fps={30}
|
||||||
|
durationInFrames={30 * 30} // 30 seconds
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
Render: `npx remotion render Vertical out/vertical.mp4`
|
||||||
|
|
||||||
|
Rules
|
||||||
|
- Keep text inside the **safe zone**: about 150px from the top, 350px from the bottom (apps cover those parts with buttons and captions), and 60px from the sides.
|
||||||
|
- Use big text (60px+). Phone screens are small.
|
||||||
|
- Keep clips under 60 seconds (Shorts limit); 15-30 seconds does best.
|
||||||
|
- Use `useVideoConfig()` for width/height instead of hard-coding, so the same scene works wide and tall.
|
||||||
|
- Put captions in the lower-middle, above the bottom safe zone (see captions.md).
|
||||||
Reference in new issue
Block a user