Animerade förklaringsvideor med berättarröst, med ToRun MCP och Remotion
Senast uppdaterad 11 nov. 2026
Vad du kan göra
En kodningsagent som kör kommandon på din dator, som Claude Code, opencode eller Cursor, kan göra en kort förklaringsvideo med berättarröst med ToRun. Agenten skriver manuset, ber ToRun om bilderna till scenerna, berättarrösten och bakgrundsmusiken, och renderar videon på din dator med Remotion, ett bibliotek som gör React-komponenter till video.
Vad du behöver
- Ett kodningsverktyg anslutet till ToRuns MCP-server som kan köra kommandon och skriva filer i en mapp: opencode, Claude Code eller Cursor.
- Node.js 18 eller senare. Den första renderingen laddar ner den webbläsare som Remotion renderar med.
- Kvot från en betald plan eller saldo i plånboken: API-anrop använder aldrig den kostnadsfria kvoten. Se hur anrop betalas.
Remotion har en egen licens: den är gratis för privatpersoner och mycket små företag, och större företag behöver en företagslicens. Läs remotion.dev/license innan du använder den i arbetet.
Startprojektet
Fyra filer. Be agenten skapa dem i en tom mapp, eller kopiera dem själv, och kör sedan npm install där. De genererade filerna hamnar i public, bredvid package.json.
package.json:
{
"name": "motion-explainer",
"private": true,
"scripts": {
"studio": "remotion studio src/index.ts",
"render": "remotion render src/index.ts Explainer out/explainer.mp4"
},
"dependencies": {
"@remotion/cli": "4.0.539",
"react": "18.3.1",
"react-dom": "18.3.1",
"remotion": "4.0.539"
}
}
video.json innehåller hela videon. Varje scen har en caption som visas på skärmen, texten say för berättarrösten, en prompt för bilden, filnamnen för dess bild och berättarröst, och dess längd i seconds:
{
"width": 1920,
"height": 1080,
"fps": 30,
"music": { "file": "music.mp3", "volume": 0.12 },
"scenes": [
{
"caption": "Every team has a backlog.",
"say": "Every team has a backlog, and it only grows.",
"prompt": "flat illustration of a wall covered in sticky notes, soft morning light, no text",
"image": "scene-1.png",
"narration": "scene-1.mp3",
"seconds": 5.5
},
{
"caption": "Sort it by what users feel.",
"say": "Sort it by what your users feel, not by what is loudest.",
"prompt": "flat illustration of hands arranging cards into three columns on a desk, no text",
"image": "scene-2.png",
"narration": "scene-2.mp3",
"seconds": 6
},
{
"caption": "Ship the top one this week.",
"say": "Then ship the top one this week.",
"prompt": "flat illustration of a small paper boat leaving a harbour at sunrise, no text",
"image": "scene-3.png",
"narration": "scene-3.mp3",
"seconds": 5
}
]
}
src/index.ts:
import {registerRoot} from 'remotion';
import {Root} from './Root';
registerRoot(Root);
src/Root.tsx spelar scenerna efter varandra. Varje bild zoomar långsamt in, texten glider upp underifrån, scenens berättarröst börjar samtidigt, och musiken spelas lågt genom hela videon:
import React from 'react';
import {AbsoluteFill, Composition, Html5Audio, Img, interpolate, Series, staticFile, useCurrentFrame} from 'remotion';
import data from '../video.json';
// "say" and "prompt" are the script: the narration text and the image prompt. Rendering reads the rest.
type Scene = {image: string; narration?: string; seconds: number; caption?: string; say?: string; prompt?: string};
type Video = {width: number; height: number; fps: number; music?: {file: string; volume: number}; scenes: Scene[]};
const video = data as Video;
const clamp = {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'} as const;
const framesOf = (scene: Scene) => Math.round(scene.seconds * video.fps);
// One scene: the image slowly zooms in, the caption rises in, the narration plays from the scene's start.
const SceneView: React.FC<{scene: Scene; frames: number}> = ({scene, frames}) => {
const frame = useCurrentFrame();
const fade = interpolate(frame, [0, 10, frames - 10, frames], [0, 1, 1, 0], clamp);
const zoom = interpolate(frame, [0, frames], [1, 1.08]);
const rise = interpolate(frame, [8, 26], [40, 0], clamp);
return (
<AbsoluteFill style={{backgroundColor: 'black', opacity: fade}}>
<Img src={staticFile(scene.image)} style={{width: '100%', height: '100%', objectFit: 'cover', transform: `scale(${zoom})`}} />
{scene.caption ? (
<div
style={{
position: 'absolute', left: '6%', right: '6%', bottom: '8%', transform: `translateY(${rise}px)`,
color: 'white', fontFamily: 'Arial, sans-serif', fontSize: video.height / 18, fontWeight: 700,
textShadow: '0 2px 16px rgba(0,0,0,0.7)',
}}
>
{scene.caption}
</div>
) : null}
{scene.narration ? <Html5Audio src={staticFile(scene.narration)} /> : null}
</AbsoluteFill>
);
};
const Explainer: React.FC = () => (
<AbsoluteFill style={{backgroundColor: 'black'}}>
<Series>
{video.scenes.map((scene, i) => (
<Series.Sequence key={i} durationInFrames={framesOf(scene)}>
<SceneView scene={scene} frames={framesOf(scene)} />
</Series.Sequence>
))}
</Series>
{video.music ? <Html5Audio src={staticFile(video.music.file)} volume={video.music.volume} loop /> : null}
</AbsoluteFill>
);
export const Root: React.FC = () => (
<Composition
id="Explainer"
component={Explainer}
width={video.width}
height={video.height}
fps={video.fps}
durationInFrames={video.scenes.reduce((sum, scene) => sum + framesOf(scene), 0)}
/>
);
Hur agenten gör videon
Manus. Agenten skriver scenerna i
video.json: en idé per scen, en text på några ord, en eller två meningar att säga. Be den visa manuset innan den skapar något.Saldo. Den anropar
account_balanceför att se vad kontot kan spendera. Det anropet är kostnadsfritt.Bilder. För varje scen anropar den
image_generatemed scenenspromptoch ensizepå1792x1024, och sparar bilden sompublic/scene-1.png,public/scene-2.pngoch så vidare. Om en bild inte är klar inom väntetiden returnerar verktyget ett jobb-id, och agenten frågarjob_statustills den är klar.Berättarröst. För varje scen anropar den
tts_generatemed scenenssay-text och en röst, till exempelnova, och sparar ljudet sompublic/scene-1.mp3och så vidare.Tajming. Den sätter varje scens
secondstill längden på dess berättarröst plus ungefär en halv sekund.tts_generatereturnerar den längden somduration_seconds, mätt från ljudfilen. Om svaret också sägerduration_estimated: truekunde längden bara uppskattas från texten, så agenten mäter filen. För att kontrollera en fil själv:npx remotion ffprobe -v error -show_entries format=duration -of csv=p=0 public/scene-1.mp3Musik (valfritt). Den anropar
music_generatemed en prompt som ”calm instrumental background, soft piano”,with_vocalssatt tillfalseoch enduration_secondsnära videons längd. Verktyget returnerar ett jobb-id; agenten frågarjob_statustills spåret är klart och sparar det sompublic/music.mp3. Om du inte vill ha musik, ta bort radenmusicivideo.json.Förhandsgranska och rendera.
npm run studioöppnar en förhandsvisning i din webbläsare.npm run renderskriverout/explainer.mp4.
Varje fillänk som verktygen returnerar laddas ner utan inloggning. Behåll hela länken, även delen efter frågetecknet:
curl -L -o public/scene-1.png "<the image link>"
Be din agent
Gör en 30 sekunder lång förklaringsvideo om hur synkroniseringen i vår app fungerar, med Remotion-startprojektet på torun.ai/docs/mcp-motion-explainers. Fyra scener. Visa mig först manuset i video.json. Skapa sedan en bild per scen utan text, lägg berättarröst med rösten nova på varje scen, lägg till lugn instrumental musik, anpassa varje scens längd efter dess berättarröst och rendera.
För en stående video, be om width 1080 och height 1920 i video.json och bilder med size 1024x1792.
Vad det kostar
image_generate,tts_generateochmusic_generateär betalda anrop, debiterade i Runs till samma priser som i appen. En video med fyra scener och musik är fyra bilder, fyra berättarröster och ett spår.account_balanceochjob_statusär kostnadsfria.- Renderingen sker på din dator. Att ändra texter, tajming eller rörelse och rendera igen kostar ingenting på ToRun; att skapa en bild eller berättarröst på nytt är ett nytt betalt anrop.
- När en enskild generering skulle kosta mer än nyckelns tröskel svarar verktyget först med priset, och din agent frågar dig innan den genomför anropet.
- Allt agenten skapar sparas också i ditt Bibliotek på ToRun.
Tips
- Be om bilder utan text. Orden hör hemma i bildtexten, och bildmodeller stavar ofta text fel.
- Håll varje scen till en idé och en eller två meningar berättarröst.
- Välj en röst och behåll den genom hela videon.
- Håll musikvolymen mellan
0.1och0.15så att berättarrösten förblir tydlig.