年払い 年払いなら、すべての有料プランで2か月分が無料。 プランを見る
サインイン 無料で始める

ToRun MCP と Remotion で作るナレーション付きモーション解説動画

最終更新: 11 11月 2026

作れるもの

Claude Code、opencode、Cursor など、お使いのパソコンでコマンドを実行できるコーディングエージェントは、ToRun を使ってナレーション付きの短い解説動画を作れます。エージェントが台本を書き、シーンの画像、ナレーション、BGM を ToRun に依頼し、Remotion を使ってお使いのパソコンで動画をレンダリングします。Remotion は React コンポーネントを動画にするライブラリです。

必要なもの

  • ToRun MCP サーバーに接続され、コマンドを実行してフォルダーにファイルを書き込めるコーディングツール: opencode、Claude Code、Cursor。
  • Node.js 18 以降。初回のレンダリングで、Remotion がレンダリングに使うブラウザーがダウンロードされます。
  • 有料プランの利用枠またはウォレット残高。API 呼び出しに無料枠が使われることはありません。呼び出しの支払い方法を参照してください。

Remotion には独自のライセンスがあります。個人とごく小規模な企業は無料で使えますが、それより大きな企業には企業ライセンスが必要です。仕事で使う前に remotion.dev/license を確認してください。

スタータープロジェクト

ファイルは 4 つです。空のフォルダーに作成するようエージェントに頼むか、自分でコピーして、そのフォルダーで npm install を実行します。生成されたファイルは package.json の隣の public フォルダーに入ります。

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 には動画全体が入ります。各シーンには、画面に表示される caption、ナレーションのテキスト say、画像の prompt、画像とナレーションのファイル名、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 はシーンを順番に再生します。各画像はゆっくりズームインし、キャプションが下から現れ、シーンのナレーションが同時に始まり、動画全体を通して音楽が静かに流れます。

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)}
  />
);

エージェントが動画を作る手順

  1. 台本。 エージェントはシーンを video.json に書きます。1 シーンに 1 つのアイデア、数語のキャプション、話す内容は 1〜2 文です。何かを生成する前に台本を見せるよう頼んでください。

  2. 残高。 account_balance を呼び出して、アカウントで使える額を確認します。この呼び出しは無料です。

  3. 画像。 シーンごとに、そのシーンの prompt と size 1792x1024 を指定して image_generate を呼び出し、画像を public/scene-1.png、public/scene-2.png のように保存します。待ち時間内に画像ができあがらない場合、ツールはジョブ ID を返し、エージェントは画像ができるまで job_status で確認します。

  4. ナレーション。 シーンごとに、そのシーンの say テキストと声(例: nova)を指定して tts_generate を呼び出し、音声を public/scene-1.mp3 のように保存します。

  5. タイミング。 各シーンの seconds を、そのナレーションの長さに約 0.5 秒を足した値にします。tts_generate はこの長さを、音声ファイルから測った duration_seconds として返します。応答に duration_estimated: true も含まれている場合は、長さをテキストから推定することしかできなかったため、エージェントがファイルを測ります。自分でファイルを確認するには:

    npx remotion ffprobe -v error -show_entries format=duration -of csv=p=0 public/scene-1.mp3
    
  6. 音楽(任意)。「calm instrumental background, soft piano」のようなプロンプト、false に設定した with_vocals、動画の長さに近い duration_seconds を指定して music_generate を呼び出します。ツールはジョブ ID を返すので、エージェントはトラックができるまで job_status で確認し、public/music.mp3 として保存します。音楽が不要なら、video.json から music の行を削除します。

  7. プレビューとレンダリング。 npm run studio でブラウザーにプレビューが開きます。npm run render で out/explainer.mp4 が書き出されます。

ツールが返すファイルのリンクは、どれもサインインせずにダウンロードできます。疑問符より後ろの部分も含めて、リンク全体を保存してください。

curl -L -o public/scene-1.png "<the image link>"

エージェントに頼む

torun.ai/docs/mcp-motion-explainers の Remotion スタータープロジェクトを使って、うちのアプリの同期の仕組みを説明する 30 秒の解説動画を作って。シーンは 4 つ。まず video.json の台本を見せて。そのあと、文字の入っていない画像をシーンごとに 1 枚生成し、各シーンのナレーションを nova の声で付け、静かなインストゥルメンタルの音楽を加え、各シーンの長さをナレーションに合わせてからレンダリングして。

縦型の動画にするには、video.json で width を 1080、height を 1920 にし、画像は size 1024x1792 で頼みます。

費用

  • image_generate、tts_generate、music_generate は有料の呼び出しで、アプリと同じ価格で Runs として課金されます。4 シーンで音楽付きの動画なら、画像 4 枚、ナレーション 4 本、トラック 1 本です。
  • account_balance と job_status は無料です。
  • レンダリングはお使いのパソコンで行われます。キャプション、タイミング、動きを変えて再レンダリングしても ToRun の費用はかかりません。画像やナレーションを生成し直すと、新しい有料の呼び出しになります。
  • 1 回の生成がキーのしきい値を超える場合、ツールはまず価格を返し、エージェントは呼び出しを実行する前にあなたに確認します。
  • エージェントが生成したものは、すべて ToRun のライブラリにも保存されます。

ヒント

  • 文字の入っていない画像を頼みましょう。言葉はキャプションが伝えますし、画像モデルは文字をよく間違えます。
  • 1 シーンには 1 つのアイデアと、1〜2 文のナレーションだけにしましょう。
  • 声は 1 つに決めて、動画全体で同じ声を使いましょう。
  • ナレーションが聞き取りやすいように、音楽の音量は 0.1 から 0.15 の間にしましょう。
お困りですか? フォーラムでは通常数時間以内に、サポートは 24 時間以内に回答します。 フォーラムで質問する サポートに問い合わせる