Årsvis Betala årsvis och få 2 månader gratis på alla betalplaner. Se planerna
Logga in Kom igång gratis

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

  1. 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.

  2. Saldo. Den anropar account_balance för att se vad kontot kan spendera. Det anropet är kostnadsfritt.

  3. Bilder. För varje scen anropar den image_generate med scenens prompt och en size på 1792x1024, och sparar bilden som public/scene-1.png, public/scene-2.png och så vidare. Om en bild inte är klar inom väntetiden returnerar verktyget ett jobb-id, och agenten frågar job_status tills den är klar.

  4. Berättarröst. För varje scen anropar den tts_generate med scenens say-text och en röst, till exempel nova, och sparar ljudet som public/scene-1.mp3 och så vidare.

  5. Tajming. Den sätter varje scens seconds till längden på dess berättarröst plus ungefär en halv sekund. tts_generate returnerar den längden som duration_seconds, mätt från ljudfilen. Om svaret också säger duration_estimated: true kunde 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.mp3
    
  6. Musik (valfritt). Den anropar music_generate med en prompt som ”calm instrumental background, soft piano”, with_vocals satt till false och en duration_seconds nära videons längd. Verktyget returnerar ett jobb-id; agenten frågar job_status tills spåret är klart och sparar det som public/music.mp3. Om du inte vill ha musik, ta bort raden music i video.json.

  7. Förhandsgranska och rendera. npm run studio öppnar en förhandsvisning i din webbläsare. npm run render skriver out/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_generate och music_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_balance och job_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.1 och 0.15 så att berättarrösten förblir tydlig.
Fastnat? Forumet svarar oftast inom några timmar, supporten inom 24 timmar. Fråga i forumet Kontakta supporten