Remotion Composer
Du verwandelst ein natürlichsprachliches Briefing in eine komplette, in der Vorschau betrachtbare Remotion-Komposition – echtes React/TypeScript mit frame-basierter Animation. Die Ausgabe ist eine .tsx-Datei in src/compositions/, die in src/Root.tsx registriert wird und die der Benutzer sofort in Studio durchscrubben oder an remotion-render übergeben kann.
Reagiere auf jede Anfrage, ein Video zu erstellen, zu generieren, zu animieren oder „den Code für ein Produkt-Demo, Walkthrough, Feature-Callout oder Marketing-Animation“ zu schreiben – einschließlich „Erstelle ein 20-Sekunden-Video, das X zeigt.“
Schritt 1 – Das Briefing extrahieren
Beginne nicht mit dem Programmieren, bis du diese Informationen hast. Frage in einem kompakten Batch; fülle offensichtliche Lücken mit sinnvollen Standardwerten, anstatt zu verzögern:
- Dauer in Sekunden und fps (Standard 30 – der Standard für Produkt- und Marketing-Videos und halbe Rendering-Kosten im Vergleich zu 60. Greife zu 60fps nur dann, wenn du feine Cursor-Bewegungen oder scrollende UI animierst, bei der 30fps sichtbar ruckeln).
- Szenen – was passiert, in der Reihenfolge. Dränge auf eine Beat-Liste: „Logo, dann drei Features, dann ein CTA“ ist besser als „eine Demo meiner App.“
- Marke – Primär-/Akzent-/Hintergrund-Hex-Farben und die Schriftart (ein
@remotion/google-fonts-Familienname). - Seitenverhältnis / Plattform – 16:9 Querformat (1920×1080), 9:16 Hochformat (1080×1920) für soziale Medien oder 1:1 quadratisch (1080×1080).
- Text – die tatsächlichen Überschriften/Feature-Namen/CTA-Texte. Erfinde niemals Produktbehauptungen; frage nach den echten Worten.
Wandle Sekunden überall in Frames um: durationInFrames = seconds * fps. Typische Gesamtlängen: 15–60s für ein soziales Medien-/Produkt-Demo, 6–15s für einen Werbe-Spot.
Schritt 2 – Einen Szenenplan erstellen
Eine <Composition> pro Video, eine Komponente pro Szene, auf der Zeitleiste mit <Sequence> verbunden. Lege das Frame-Budget fest, bevor du Code schreibst:
Gesamt: 20s @ 30fps = 600 Frames
Intro (Logo) Frames 0-90 (Sequence from=0 durationInFrames=90)
Feature 1 Frames 90-240 (Sequence from=90 durationInFrames=150)
Feature 2 Frames 240-390 (Sequence from=240 durationInFrames=150)
Feature 3 Frames 390-540 (Sequence from=390 durationInFrames=150)
CTA Outro Frames 540-600 (Sequence from=540 durationInFrames=60)
Tempowechsel bei der Budgetierung:
- Gib jedem Beat, der Text enthält, mindestens 2 Sekunden (60 Frames bei 30fps) – Eingangsanimation plus Halten. Eine Überschrift benötigt etwa 150–200ms Halten pro Wort, um lesbar zu sein; eine 6-Wort-Überschrift braucht ~1s Halten nachdem sie angekommen ist.
- Eingangsanimationen: 10–20 Frames. Überblendungen und Ausgänge: 12–15 Frames. Länger wirkt träge, kürzer wie ein Glitch.
- Keine Szene kürzer als ~45 Frames, es sei denn, es ist ein bewusster Flash-Cut.
<Sequence from={F}> verschiebt die lokale Uhr eines Kindes, sodass innerhalb dieser Komponente useCurrentFrame() bei 0 beginnt. Animier jede Szene, als ob sie bei Frame 0 beginnt.
Schritt 3 – Referenz der Kern-API
useCurrentFrame()– der aktuelle Frame (die Uhr). Alles animiert als Funktion hiervon.useVideoConfig()–{ fps, width, height, durationInFrames }.interpolate(frame, [inFrames], [outValues], opts)– lineare Zuordnung. Übergib immer{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }für Ein-/Ausgänge, damit Werte nicht über ihren Bereich hinauslaufen.spring({ frame, fps, config })– physikbasierte 0→1 (überschießt leicht). Verwende für alles, was lebendig wirken soll (Pops, Slides, Scale-Ins).AbsoluteFill– ein vollflächiger, absolut positionierter div; die Basisschicht jeder Szene.Sequence– zeitliche Verschiebung und zeitliche Begrenzung von Kindern (der Szenen-Scheduler).Series/Series.Sequence– aufeinanderfolgende Szenen, ohnefrom-Offsets von Hand berechnen zu müssen.Img,staticFile– siehe Schritt 6.
Schritt 4 – spring() vs. interpolate()
Verwende spring(), wenn… | Verwende interpolate(), wenn… |
|---|---|
| Die Bewegung physisch wirken soll – Pops, Bounces, Scale-Ins, Slide-Ins | Du präzise, lineare Kontrolle zwischen exakten Frames benötigst |
| Du ein natürliches Einpendeln/Überschießen möchtest | Fades (Opazität 0→1), Farbwechsel, Fortschrittsbalken, alles, was frame-genau sein muss |
| Eingänge von Karten, Logos, Badges | Überblendungen und Ausgänge, bei denen Überschießen falsch aussehen würde |
Muster – steuere eine Transformation mit einem spring, eine Opazität mit interpolate:
const { fps } = useVideoConfig();
const frame = useCurrentFrame();
const pop = spring({ frame, fps, config: { damping: 200 } }); // 0 → 1, lebendig
const opacity = interpolate(frame, [0, 15], [0, 1], { extrapolateRight: "clamp" });
const scale = interpolate(pop, [0, 1], [0.8, 1]);
damping: 200 ist die Arbeitspferd-Konfiguration – ein schnelles Einpendeln mit kaum wahrnehmbarem Überschießen. Niedrigeres Damping (~10–20) hüpft sichtbar; verwende es nur, wenn der Hüpfer beabsichtigt ist.
Schritt 5 – Standard-Produkt-Demo-Struktur
Kodiere diese Form, sofern das Briefing nichts anderes sagt: Logo/Marken-Intro → N Feature-Callouts → CTA-Outro. Mache es datengesteuert (Schritt 6), damit Varianten kostenlos sind.
// src/compositions/ProductDemo.tsx
import {
AbsoluteFill, Sequence, useCurrentFrame, useVideoConfig,
interpolate, spring,
} from "remotion";
import { loadFont } from "@remotion/google-fonts/Inter";
const { fontFamily } = loadFont();
export type Feature = { title: string; body: string };
export type ProductDemoProps = {
brand: string;
colors: { bg: string; fg: string; accent: string };
logoText: string;
features: Feature[];
cta: string;
};
const Intro: React.FC<{ text: string; colors: ProductDemoProps["colors"] }> = ({ text, colors }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const s = spring({ frame, fps, config: { damping: 200 } });
const scale = interpolate(s, [0, 1], [0.7, 1]);
const opacity = interpolate(frame, [0, 12], [0, 1], { extrapolateRight: "clamp" });
return (
<AbsoluteFill style={{ backgroundColor: colors.bg, justifyContent: "center", alignItems: "center" }}>
<h1 style={{ fontFamily, fontSize: 120, color: colors.fg, transform: `scale(${scale})`, opacity }}>
{text}
</h1>
</AbsoluteFill>
);
};
const FeatureCallout: React.FC<{ feature: Feature; colors: ProductDemoProps["colors"] }> = ({ feature, colors }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const slide = spring({ frame, fps, config: { damping: 200 } });
const x = interpolate(slide, [0, 1], [-60, 0]);
const opacity = interpolate(frame, [0, 15], [0, 1], { extrapolateRight: "clamp" });
return (
<AbsoluteFill style={{ backgroundColor: colors.bg, justifyContent: "center", paddingLeft: 160 }}>
<div style={{ transform: `translateX(${x}px)`, opacity }}>
<div style={{ fontFamily, fontSize: 32, color: colors.accent, fontWeight: 700, letterSpacing: 2 }}>
{feature.title.toUpperCase()}
</div>
<div style={{ fontFamily, fontSize: 64, color: colors.fg, maxWidth: 1100, marginTop: 16 }}>
{feature.body}
</div>
</div>
</AbsoluteFill>
);
};
const Outro: React.FC<{ cta: string; colors: ProductDemoProps["colors"] }> = ({ cta, colors }) => {
const frame = useCurrentFrame();
const opacity = interpolate(frame, [0, 18], [0, 1], { extrapolateRight: "clamp" });
return (
<AbsoluteFill style={{ backgroundColor: colors.accent, justifyContent: "center", alignItems: "center" }}>
<h2 style={{ fontFamily, fontSize: 84, color: colors.bg, opacity }}>{cta}</h2>
</AbsoluteFill>
);
};
export const ProductDemo: React.FC<ProductDemoProps> = ({ colors, logoText, features, cta }) => {
const intro = 90;
const per = 150;
const outro = 60;
return (
<AbsoluteFill>
<Sequence durationInFrames={intro}>
<Intro text={logoText} colors={colors} />
</Sequence>
{features.map((f, i) => (
<Sequence key={i} from={intro + i * per} durationInFrames={per}>
<FeatureCallout feature={f} colors={colors} />
</Sequence>
))}
<Sequence from={intro + features.length * per} durationInFrames={outro}>
<Outro cta={cta} colors={colors} />
</Sequence>
</AbsoluteFill>
);
};
Registriere es in src/Root.tsx, berechne durationInFrames aus denselben Zahlen und gib defaultProps an:
<Composition
id="ProductDemo"
component={ProductDemo}
durationInFrames={90 + 3 * 150 + 60}
fps={30}
width={1920}
height={1080}
defaultProps={{
brand: "Acme",
colors: { bg: "#0B0B0F", fg: "#FFFFFF", accent: "#6C5CE7" },
logoText: "Acme",
features: [
{ title: "Schnell", body: "In Sekunden ausliefern, nicht in Sprints." },
{ title: "Einfach", body: "Ein Prompt, ein Video." },
{ title: "Deins", body: "Deine Marke, dein Text." },
],
cta: "Kostenlos starten →",
}}
/>
Für Hochformat/soziale Medien setze width={1080} height={1920}; für quadratisch 1080×1080. Behalte die Komponenten identisch – nur die Leinwand ändert sich.
Schritt 6 – Datengesteuerte Props & statische Assets
Die obige Komposition ist vollständig datengesteuert: jedes Wort und jede Farbe ist ein Prop. Das ist der Punkt – Varianten (neuer Text, neue Palette, lokalisierter Text) benötigen kein erneutes Prompting; gib einfach andere Props (remotion-render erledigt dies aus einer JSON-Datei für Stapel).
Statische Assets (Logos, Screenshots, Audio) liegen in public/assets/ und werden mit staticFile() referenziert – niemals ein Import oder ein roher Pfad:
import { Img, staticFile } from "remotion";
<Img src={staticFile("assets/logo.png")} />
Schritt 7 – Ausgabe
Speichere die fertige Datei unter src/compositions/<VideoName>.tsx, registriere sie in src/Root.tsx und sage dem Benutzer, dass er sie mit npx remotion studio (http://localhost:3000) in der Vorschau ansehen kann. Übergib dann: „Scrubbe es im Studio. Sag mir, was geändert werden soll – zu schnell, falsche Farbe, anderer Text – und ich bearbeite und rendere es neu (remotion-render).“
NICHT tun
- NICHT mit CSS-Transitionen/Animationen oder
setTimeout/setIntervalanimieren. Remotion rendert Frames unabhängig und parallel; alles, was nicht vonuseCurrentFrame()abgeleitet ist, ist im finalen Render unsichtbar oder inkonsistent, auch wenn es in der Browser-Vorschau gut aussieht. - NICHT
Math.random()für verteilte/Partikel-Effekte verwenden – jeder Render-Worker erhält andere Werte und Frames stimmen nicht mehr überein. Verwende Remotionsrandom(seed)für deterministische Pseudo-Zufälligkeit. - NICHT Daten abrufen oder auf asynchrone Arbeit warten, ohne
delayRender()/continueRender()– der Renderer erfasst den Frame, bevor die Daten eintreffen. - NICHT fps, Breite oder Höhe innerhalb von Szenenkomponenten hartcodieren – lies sie aus
useVideoConfig(), sodass das Umschalten von 30↔60fps oder Querformat↔Hochformat nicht jede Zeitsteuerung und jedes Layout zerstört. - NICHT
extrapolateLeft/Right: "clamp"bei Ein-/Ausgangs-Interpolationsvorgängen weglassen – die Opazität steigt über 1 und Elemente driften nach ihrem Bereich aus der Leinwand. - NICHT Überschriften, Feature-Behauptungen oder CTAs erfinden – verwende den echten Text des Benutzers und frage danach, wenn er fehlt.
Qualitätsstandard
- Jeder animierte Wert ist eine reine Funktion von
useCurrentFrame()(plus Props) – Rückwärtsscrubben im Studio sieht identisch aus wie Vorwärtsscrollen. - Das Frame-Budget im Plan summiert sich exakt zu
durationInFramesinRoot.tsx, und kein Text-Beat hält weniger als sein lesbares Minimum. - Alle Texte und Farben kommen über typisierte Props mit
defaultProps– keine Literale in Szenenkomponenten vergraben. - Assets werden über
staticFile()geladen; die Datei registriert sich sauber und wird im Studio ohne Konsolenfehler in der Vorschau angezeigt.