Remotionで顔出しなしYouTube動画を自動生成する方法
Remotionで顔出しなしYouTube動画を自動生成する方法(自動化パイプライン)
顔出しなしのYouTube動画とは、出演者がカメラに映らない動画のことです。書かれた台本がナレーションになり、そのナレーションが画面上の字幕を駆動し、B-roll映像と画像がビジュアルを担います。Remotionを使えば、これらの要素はすべてコードになります。つまり、台本から完成MP4までのパイプライン全体を一度組み立てておけば、新しい動画を作るたびに再実行できるのです。カメラも不要、タイムライン上でクリップをドラッグする作業も不要です。
得られるメリットはシンプルです。コンポジションはReactコンポーネントなので、テキストと音声をpropsとして渡せば、Remotionがフレームをレンダリングします。こうして再現可能な制作ラインが手に入ります——台本を書き、ナレーションを生成し、それを文字起こしして字幕にし、ビジュアルを配置し、シーンを並べ、レンダリングする。台本を変えて再レンダリングすれば、まったく同じレイアウトの上に次のエピソードが出来上がります。
本記事ではこのパイプラインを最初から最後までたどり、各段階で使うRemotionのAPIを示します。これはコンテンツワークフローに対する開発者的なアプローチ——静かで、再現可能で、バージョン管理されたやり方——であり、再生数を増やすための近道ではありません。Remotionが担うのは制作までです。配信とパフォーマンスは、あくまであなたのコンテンツ次第です。
顔出しなしYouTube動画とは何か
顔出しなし(「ノーフェイス」)動画は、ナレーション・字幕・補助的なビジュアルだけで内容を伝えます。よくあるフォーマットには、解説動画・リスト形式の動画・要約・チュートリアル・ナレーション付きのストック映像モンタージュなどがあります。共通する特徴は、制作者が一度も画面に登場しないことです——だからこそ、撮影ではなくテキストとアセットから動画全体を制作できます。
この構造はRemotionにほぼ完璧に対応します。Remotionは、ReactとTypeScriptで動画を構築するためのオープンソースフレームワークです。1フレームが1枚のレンダリングされたReactコンポーネントであり、ヘッドレスのChromiumランタイムがそのフレームをキャプチャしてMP4にエンコードします。顔出しなし動画は本質的に「ナレーション + タイミング付き字幕 + タイミング付きビジュアル」であり、その3つはいずれも単なるデータとコンポーネントに過ぎません。だからこそ、このフォーマットはコード駆動のパイプラインと自然に噛み合います。
パイプラインの全体像
以下が全体のフローです。順を追って並べています。各ステップは、次のステップが消費する成果物を生み出します。
- 台本 — プレーンテキスト(またはJSON)の台本を、シーンごとに分割します。
- ナレーション — text-to-speechで各シーンのテキストを音声ファイルに変換し、
@remotion/mediaの<Audio>コンポーネントでコンポジション内に再生します。 - 字幕 — ナレーション音声を(Whisperで)文字起こしし、
@remotion/captionsを使ってタイミング付きの字幕データにします。 - ビジュアル — B-rollクリップは
<Video>(@remotion/media)または<OffthreadVideo>で、画像は<Img>とstaticFile()で配置します。 - 組み立て — シーンを
<Sequence>でタイムラインに配置します。 - レンダリング — 完成した動画を、ローカルなら
@remotion/rendererで、スケールさせるなら@remotion/lambdaで書き出します。
以下のセクションでは、各段階を該当するAPIとともに解説します。すべて remotion.dev にドキュメントがあります。
ステップ1 — 台本から始める
台本はパイプライン全体にとっての「信頼できる唯一の情報源(source of truth)」です。下流のステップが反復処理できるよう、構造化されたデータのまま保持しましょう。シンプルな形は、シーンごとに1エントリを持ち、それぞれに id とナレーション用テキストを持たせる構成です。
export const script = [
{ id: "intro", text: "Three quiet habits that change how you plan your week." },
{ id: "point-1", text: "First, write the outcome before you write the task." },
{ id: "point-2", text: "Second, protect one block of deep-focus time each day." },
{ id: "outro", text: "Small, repeatable systems beat bursts of motivation." },
];
これはコードなので、同じ配列から音声を生成し、字幕の元データを用意し、シーンの区切りを定義できます。次の動画を作るときは、この配列を編集するだけ——パイプラインの残りの部分は変わりません。
ステップ2 — 台本をナレーションに変換する
顔出しなし動画は、ナレーションの出来で成否が決まります。text-to-speech(TTS)サービスが、各シーンのテキストを音声ファイルに変換します。ElevenLabs は自然に聞こえる音声を生成する選択肢のひとつですが、音声ファイルを返すTTSであれば、どれもパイプライン内で同じように機能します。小さなNodeスクリプトで台本をループし、シーンごとにMP3を1本ずつ public/ フォルダに書き出しておけば、Remotionは staticFile() 経由でそれを参照できます。
import { writeFileSync } from "fs";
import { script } from "./script";
const VOICE_ID = "your-voice-id";
for (const line of script) {
const res = await fetch(
`https://api.elevenlabs.io/v1/text-to-speech/${VOICE_ID}`,
{
method: "POST",
headers: {
"xi-api-key": process.env.ELEVENLABS_API_KEY!,
"Content-Type": "application/json",
Accept: "audio/mpeg",
},
body: JSON.stringify({
text: line.text,
model_id: "eleven_multilingual_v2",
}),
},
);
const audio = Buffer.from(await res.arrayBuffer());
writeFileSync(`public/voiceover/${line.id}.mp3`, audio);
}
コンポジションの内側では、@remotion/media の <Audio> コンポーネントで各ファイルを再生し、staticFile() で参照します。
import { Audio } from "@remotion/media";
import { staticFile } from "remotion";
export const Narration: React.FC<{ file: string }> = ({ file }) => {
return <Audio src={staticFile(file)} />;
};
各シーンはそのナレーションとちょうど同じ長さであるべきなので、コンポジションの尺を音声に合わせて自動で決められます。RemotionのcalculateMetadata 関数を使えば、レンダリング前に音声の長さを計測し、それに応じて durationInFrames を設定できるため、タイムラインをハードコードするのではなく、常にナレーションと一致させられます。これにより、手作業でのトリミングなしに字幕とビジュアルの同期が保たれます。詳しいパターンは calculateMetadataのドキュメント を参照してください。
ステップ3 — 字幕を自動生成する
顔出しなしコンテンツでは、画面上の字幕はほぼ必須です。その多くが音声オフで視聴されるからです。手打ちする代わりに、いま生成したばかりのナレーションを文字起こししましょう。Remotionは @remotion/install-whisper-cpp を提供しており、これはWhisperをローカルで実行してタイミング付きの文字起こしデータを返します。次のNodeスクリプトは、Whisperをインストールし、モデルをダウンロードし、クリップを文字起こしし、toCaptions() で出力を標準的な Caption 形式に変換します。
import {
installWhisperCpp,
downloadWhisperModel,
transcribe,
toCaptions,
} from "@remotion/install-whisper-cpp";
import { writeFileSync } from "fs";
import path from "path";
const whisperPath = path.join(process.cwd(), "whisper.cpp");
await installWhisperCpp({ to: whisperPath, version: "1.5.5" });
await downloadWhisperModel({ model: "medium.en", folder: whisperPath });
// Whisper expects a 16 kHz WAV. Convert the MP3 first with ffmpeg if needed:
// ffmpeg -i public/voiceover/intro.mp3 -ar 16000 public/voiceover/intro.wav -y
const output = await transcribe({
model: "medium.en",
whisperPath,
whisperCppVersion: "1.5.5",
inputPath: "public/voiceover/intro.wav",
tokenLevelTimestamps: true,
});
const { captions } = toCaptions({ whisperCppOutput: output });
writeFileSync("public/captions/intro.json", JSON.stringify(captions, null, 2));
字幕はごく普通のJSON——text・startMs・endMs を持つ Caption オブジェクトのリスト——なので、他のデータファイルと同じようにレビューして軽く手直しできます。表示については、@remotion/captions パッケージに createTikTokStyleCaptions() などのヘルパーが含まれています。これは単語を読みやすいページ単位にまとめてくれるので、<Sequence> ブロック内でレンダリングし、発話されるたびに単語ごとにハイライトできます。表示用ヘルパーの詳細は 字幕のドキュメント を参照してください。
ステップ4 — B-rollと画像を加える
ビジュアルこそ、顔出しなし動画が単なるスライドショーから脱する分かれ目です。Remotionは映像も画像も、フレーム上に配置するコンポーネントとして扱います。
動画のB-rollには、@remotion/media の <Video> コンポーネント(または remotion の <OffthreadVideo>)を使い、ローカルファイルは staticFile() で、リモートファイルはURLで参照します。クリップ自身の音声がナレーションとぶつからないよう、muted propを付けましょう。
import { AbsoluteFill, Img, interpolate, staticFile, useCurrentFrame } from "remotion";
import { Video } from "@remotion/media";
export const BrollScene: React.FC = () => {
const frame = useCurrentFrame();
// A slow push-in, recomputed on every frame.
const scale = interpolate(frame, [0, 90], [1, 1.08], {
extrapolateRight: "clamp",
});
return (
<AbsoluteFill style={{ backgroundColor: "#0B0B0F" }}>
<Video src={staticFile("broll/city.mp4")} muted style={{ scale }} />
<Img
src={staticFile("logo.png")}
style={{ position: "absolute", bottom: 60, right: 60, width: 120 }}
/>
</AbsoluteFill>
);
};
注目すべき点が2つあります。1つ目は、静止画は <Img> と staticFile() を使う——動画とまったく同じパターンだということ。2つ目は、上のプッシュイン(ゆっくり寄っていく動き)は useCurrentFrame() と interpolate() でアニメーションされており、これらがフレームごとに値を再計算していることです。ここはCSSから来た人にとって重要なポイントです。CSSトランジションとCSSアニメーションはRemotionではレンダリングされません。 動きはフレーム番号によって駆動される必要があります。そうすることで、レンダラーが毎フレームを決定論的に再現できるのです。使用権を持っているB-rollと画像だけを使ってください。
ステップ5 — シーンを組み立てる
音声・字幕・ビジュアルが定義できたら、<Sequence> が各シーンをタイムライン上に配置します。シーケンスの from propはシーンの開始タイミングを、durationInFrames はその長さを設定します。台本をたどりながらオフセットを進めていけば、シーンを隙間なく連続させられます。
import { AbsoluteFill, Sequence, staticFile } from "remotion";
import { Audio } from "@remotion/media";
import { BrollScene } from "./BrollScene";
type Scene = { id: string; durationInFrames: number };
export const FacelessVideo: React.FC<{ scenes: Scene[] }> = ({ scenes }) => {
let from = 0;
return (
<AbsoluteFill>
{scenes.map((scene) => {
const el = (
<Sequence key={scene.id} from={from} durationInFrames={scene.durationInFrames}>
<Audio src={staticFile(`voiceover/${scene.id}.mp3`)} />
<BrollScene />
{/* caption pages for this scene render here */}
</Sequence>
);
from += scene.durationInFrames;
return el;
})}
</AbsoluteFill>
);
};
各シーンは自身のナレーション・B-roll・字幕を抱え、同じシーケンスを共有しているため一緒に再生されます。イントロ・ロワーサード・エンドカードも、その上に重ねるシーケンスが増えるだけです。
ステップ6 — 完成した動画をレンダリングする
Remotion Studio(npx remotion studio)でプレビューが正しく見えたら、ファイルに書き出します。1本の動画であれば、CLIまたは @remotion/renderer パッケージがローカルで実行します。
npx remotion render FacelessVideo out/episode-01.mp4 --props=./episode-01.json
--props(または inputProps)でデータを渡すことこそ、パイプラインを再現可能にする鍵です。同じコンポジションが、propsファイルごとに異なるエピソードをレンダリングします。多数の動画を作るには、台本をループしてエントリごとに1ファイルずつレンダリングします。本数が増えてきたら、@remotion/lambda がレンダリングを複数のAWS Lambda関数に分散して並列処理します——バッチ処理に便利です——一方で、数本ずつであればローカルレンダリングで十分こなせます。
なぜコード化で顔出しなしチャンネルは再現可能になるのか
Remotionでこれを行う利点は、何か1つの機能ではありません——動画全体がコードであることそのものです。顔出しなしチャンネルはたいてい、すべてのエピソードで1つのビジュアルアイデンティティを使い回します。同じイントロ、同じ字幕スタイル、同じロワーサード、同じエンドカード。それらがpropsを受け取るReactコンポーネントとして存在していれば、次の動画を作ることは新しい入力の問題であって、新しいデザイン作業ではありません。レイアウトはコードベースの他の部分と同じように、バージョン管理され、レビュー可能で、差分が取れます。
ここはテンプレートライブラリが最も時間を節約してくれる場面でもあります。あらゆる顔出しなし動画が必要とする要素——イントロ、アニメーション字幕、ロワーサード、エンドカード——は、まさに一から作ると面倒で、一度作れば再利用が容易なコンポーネントです。これらの要素について既製のRemotionテンプレートから始めれば、10回目の登場アニメーションを組み直すのではなく、台本とビジュアルに時間を使えます。それこそ RenderComp が埋める領域です。
パイプライン要約表
| 段階 | 入力 | Remotion API / ツール | 出力 |
|---|---|---|---|
| 台本 | アイデア | プレーンなTS/JSON配列 | テキスト付きのシーン |
| ナレーション | 台本のテキスト | TTS(例: ElevenLabs)→ @remotion/media の <Audio> | シーンごとのMP3 |
| 字幕 | ナレーション音声 | @remotion/install-whisper-cpp → @remotion/captions | タイミング付き字幕JSON |
| ビジュアル | 使用権のあるアセット | <Video> / <OffthreadVideo>(muted)、<Img> + staticFile() | 画面上のB-rollと画像 |
| 動き | フレーム番号 | useCurrentFrame() + interpolate() | フレーム精度のアニメーション |
| 組み立て | 上記すべて | <Sequence> | 完成したコンポジション |
| レンダリング | コンポジション + props | @remotion/renderer(ローカル)または @remotion/lambda(スケール) | 完成MP4 |
FAQ
Q: カメラに映ったり、ウェブカメラで録画したりする必要はありますか? いいえ。動画全体がナレーション・字幕・ビジュアルアセットから構築されます。パイプラインのどこにもカメラは必要ありません。
Q: どのtext-to-speechサービスを使うべきですか?
ElevenLabsは自然なナレーションを生成する、よく知られた選択肢のひとつで、標準的な音声ファイルを返します。音声ファイルを出力するTTSであれば、どれもパイプラインに適合します——コンポジションが必要とするのは、<Audio> で再生するためのファイルだけです。
Q: 動画はどうやってナレーションと同期を保つのですか?
各シーンの音声の長さを計測し、calculateMetadata を使ってコンポジションの尺をそこから決定します。タイムラインが音声から導かれるため、字幕とビジュアルはハードコードされるのではなく、ナレーションに合わせて伸縮します。
Q: 自動生成された字幕は完璧でなければなりませんか?
Whisperの文字起こしは強力な出発点であって、最終的な確証ではありません。字幕はプレーンなJSON(text・startMs・endMs)なので、レンダリング前にファイルを開いて文言やタイミングを修正できます。
Q: B-rollクリップや画像はどこから調達しますか?
使用権を持っているアセット——自前の映像、ライセンス済みのストック、生成したビジュアル——から調達します。ローカルファイルは staticFile() で参照し、<Video>/<Img> をリモートURLに向けることもできます。B-rollには muted を付けて、その音声がナレーションと重ならないようにしてください。
Q: 1つのコンポジションから複数の動画をバッチ生成できますか?
できます。動画ごとに異なるpropsオブジェクトを(--props または inputProps で)渡し、台本をループします。小さなバッチなら @remotion/renderer でローカルにレンダリングし、多数を並列にレンダリングするなら @remotion/lambda を使います。
Q: これで再生数や収益化は保証されますか? いいえ。Remotionが自動化するのは制作——台本を、完成した一貫性のある動画に変えること——だけです。動画がうまくいくか、収益化の条件を満たすかは、あなたのコンテンツとYouTube自身のポリシー次第であり、このパイプラインが左右するものではありません。
RenderCompでもっと速く作る
顔出しなしパイプラインは設計上、再現可能です。そして最も繰り返し使う要素——イントロ、アニメーション字幕、ロワーサード、エンドカード——こそ、毎回作り直さない価値のあるものです。RenderComp は、まさにそうしたコンポーネントについて、プロダクション品質のRemotionテンプレートライブラリを提供します。すべてTypeScriptのソースとして提供されるので、直接カスタマイズし、レンダリングパイプラインから inputProps で駆動できます。
コレクションは rendercomp.com でご覧いただけます。白紙のキャンバスからではなく、しっかりした土台の上で、あなたの顔出しなしチャンネルを始めてください。