Hằng năm Thanh toán theo năm và được miễn phí 2 tháng với mọi gói trả phí. Xem các gói
Đăng nhập Bắt đầu miễn phí

Video giải thích chuyển động có thuyết minh với ToRun MCP và Remotion

Cập nhật lần cuối: 11 thg 11 2026

Bạn có thể làm gì

Một tác nhân lập trình chạy lệnh trên máy tính của bạn, như Claude Code, opencode hoặc Cursor, có thể dùng ToRun để làm một video giải thích ngắn có thuyết minh. Tác nhân viết kịch bản, yêu cầu ToRun tạo hình ảnh cho các cảnh, lời thuyết minh và nhạc nền, rồi kết xuất video trên máy tính của bạn bằng Remotion, một thư viện biến các thành phần React thành video.

Bạn cần gì

  • Một công cụ lập trình đã kết nối với máy chủ MCP của ToRun, có thể chạy lệnh và ghi tệp vào một thư mục: opencode, Claude Code hoặc Cursor.
  • Node.js 18 trở lên. Lần kết xuất đầu tiên sẽ tải trình duyệt mà Remotion dùng để kết xuất.
  • Hạn mức của gói trả phí hoặc số dư trong ví: lệnh gọi API không bao giờ dùng hạn mức miễn phí. Xem lệnh gọi được thanh toán thế nào.

Remotion có giấy phép riêng: miễn phí cho cá nhân và các công ty rất nhỏ, còn các công ty lớn hơn cần giấy phép công ty. Hãy xem remotion.dev/license trước khi dùng cho công việc.

Dự án khởi đầu

Bốn tệp. Hãy nhờ tác nhân tạo chúng trong một thư mục trống, hoặc tự sao chép, rồi chạy npm install ở đó. Các tệp được tạo ra sẽ nằm trong thư mục public bên cạnh 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 chứa toàn bộ video. Mỗi cảnh có caption hiển thị trên màn hình, văn bản thuyết minh say, prompt cho hình ảnh, tên tệp của hình ảnh và lời thuyết minh, và độ dài tính bằng 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 phát các cảnh lần lượt. Mỗi hình ảnh phóng to chậm, phụ đề trượt lên, lời thuyết minh của cảnh bắt đầu cùng lúc, còn nhạc phát nhỏ suốt cả video:

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

Tác nhân làm video như thế nào

  1. Kịch bản. Tác nhân viết các cảnh vào video.json: mỗi cảnh một ý, phụ đề vài từ, một hoặc hai câu để nói. Hãy yêu cầu nó cho bạn xem kịch bản trước khi tạo bất cứ thứ gì.

  2. Số dư. Nó gọi account_balance để xem tài khoản có thể chi bao nhiêu. Lệnh gọi này miễn phí.

  3. Hình ảnh. Với mỗi cảnh, nó gọi image_generate với prompt của cảnh và size 1792x1024, rồi lưu hình ảnh thành public/scene-1.png, public/scene-2.png và cứ thế. Nếu hình ảnh chưa xong trong thời gian chờ, công cụ trả về một mã công việc, và tác nhân hỏi job_status cho đến khi hình ảnh xong.

  4. Thuyết minh. Với mỗi cảnh, nó gọi tts_generate với văn bản say của cảnh và một giọng, ví dụ nova, rồi lưu âm thanh thành public/scene-1.mp3 và cứ thế.

  5. Thời lượng. Nó đặt seconds của mỗi cảnh bằng độ dài lời thuyết minh cộng thêm khoảng nửa giây. tts_generate trả về độ dài đó dưới dạng duration_seconds, được đo từ tệp âm thanh. Nếu câu trả lời còn có duration_estimated: true, độ dài chỉ có thể ước tính từ văn bản, nên tác nhân sẽ đo tệp. Để tự kiểm tra một tệp:

    npx remotion ffprobe -v error -show_entries format=duration -of csv=p=0 public/scene-1.mp3
    
  6. Âm nhạc (không bắt buộc). Nó gọi music_generate với một câu lệnh như "calm instrumental background, soft piano", with_vocals đặt là false và duration_seconds gần với độ dài video. Công cụ trả về một mã công việc; tác nhân hỏi job_status cho đến khi bản nhạc xong và lưu thành public/music.mp3. Nếu không cần nhạc, hãy xóa dòng music khỏi video.json.

  7. Xem trước và kết xuất. npm run studio mở bản xem trước trong trình duyệt của bạn. npm run render ghi ra out/explainer.mp4.

Mọi liên kết tệp mà công cụ trả về đều tải xuống được mà không cần đăng nhập. Hãy giữ nguyên toàn bộ liên kết, kể cả phần sau dấu chấm hỏi:

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

Yêu cầu tác nhân của bạn

Làm một video giải thích dài 30 giây về cách tính năng đồng bộ của ứng dụng chúng tôi hoạt động, dùng dự án khởi đầu Remotion từ torun.ai/docs/mcp-motion-explainers. Bốn cảnh. Trước tiên cho tôi xem kịch bản trong video.json. Sau đó tạo một hình ảnh không có chữ cho mỗi cảnh, thuyết minh mỗi cảnh bằng giọng nova, thêm nhạc không lời nhẹ nhàng, căn thời lượng mỗi cảnh theo lời thuyết minh của nó, rồi kết xuất.

Để có video dọc, hãy yêu cầu width 1080 và height 1920 trong video.json, và hình ảnh với size 1024x1792.

Chi phí bao nhiêu

  • image_generate, tts_generate và music_generate là các lệnh gọi trả phí, được tính bằng Runs theo cùng mức giá như trong ứng dụng. Một video bốn cảnh có nhạc gồm bốn hình ảnh, bốn lời thuyết minh và một bản nhạc.
  • account_balance và job_status miễn phí.
  • Việc kết xuất chạy trên máy tính của bạn. Thay đổi phụ đề, thời lượng hoặc chuyển động rồi kết xuất lại không tốn gì trên ToRun; tạo lại một hình ảnh hoặc lời thuyết minh là một lệnh gọi trả phí mới.
  • Khi một lần tạo sẽ tốn nhiều hơn ngưỡng của khóa, công cụ sẽ trả lời bằng giá trước, và tác nhân của bạn sẽ hỏi bạn trước khi chạy lệnh gọi.
  • Mọi thứ tác nhân tạo ra cũng được lưu vào thư viện ToRun của bạn.

Mẹo

  • Yêu cầu hình ảnh không có chữ. Phụ đề đã mang phần chữ, và mô hình hình ảnh thường viết sai chính tả.
  • Giữ mỗi cảnh ở một ý và một hoặc hai câu thuyết minh.
  • Chọn một giọng và giữ nguyên trong cả video.
  • Giữ âm lượng nhạc trong khoảng 0.1 đến 0.15 để lời thuyết minh vẫn rõ ràng.
Gặp khó khăn? Diễn đàn thường trả lời trong vài giờ; bộ phận hỗ trợ trong vòng 24 giờ. Hỏi trên diễn đàn Liên hệ hỗ trợ