AIで作る業務動画 Day 12|Remotionで音声と字幕を同期させる
今日のゴール
Remotionで音声ファイルを組み込み、字幕と同期させる動画を作成する。
「音声に合わせて字幕が切り替わる」という、解説動画の基本機能を実装します。
前提条件
- Day 11でRemotionプロジェクトが動作している
- Phase 1で生成した音声ファイル(WAV)がある
手順
Step 1: Audioコンポーネントの理解
Remotionには <Html5Audio> コンポーネントがあり、これで音声を再生できます。
import { Html5Audio, staticFile } from "remotion";
<Html5Audio src={staticFile("audio.wav")} />
staticFile関数
staticFile() は public/ フォルダ内のファイルをURLに変換する関数です。音声ファイルは public/ フォルダに配置します。
remotion-project/
├─ public/
│ └─ voice_kore.wav ← ここに配置
├─ src/
└─ package.json
Step 2: 音声ファイルの組み込み
Phase 1で生成した音声ファイルをRemotionプロジェクトに組み込みます。
# publicフォルダを作成
mkdir -p public
# 音声ファイルをコピー
cp ../day03/voices/voice_kore.wav public/
音声付き動画コンポーネントを作成します。
// src/AudioVideo.tsx
import {
AbsoluteFill,
Html5Audio,
staticFile,
useCurrentFrame,
useVideoConfig,
interpolate,
} from "remotion";
export const AudioVideo: React.FC = () => {
const frame = useCurrentFrame();
const { durationInFrames } = useVideoConfig();
// フェードイン/フェードアウト
const audioVolume = interpolate(
frame,
[0, 15, durationInFrames - 30, durationInFrames - 10],
[0, 1, 1, 0],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
);
return (
<AbsoluteFill style={{ backgroundColor: "#f0f8ff" }}>
<Html5Audio
src={staticFile("voice_kore.wav")}
volume={audioVolume}
/>
{/* 他のコンテンツ */}
</AbsoluteFill>
);
};
音量のフレーム制御
volume propsに関数を渡すことで、フレームごとに音量を変化させられます。interpolate() を使えば、フェードイン/フェードアウトを簡単に実装できます。
Step 3: 字幕の同期
音声に合わせて字幕を表示するには、台本データを定義して <Sequence> で制御します。
台本データの定義
interface SubtitleCue {
text: string;
startFrame: number;
endFrame: number;
}
// voice_kore.wav: 「こんにちは。本日は動画制作AIについてご紹介します。」(約4.8秒)
const subtitleCues: SubtitleCue[] = [
{ text: "こんにちは。", startFrame: 0, endFrame: 40 },
{ text: "本日は動画制作AIについて", startFrame: 40, endFrame: 100 },
{ text: "ご紹介します。", startFrame: 100, endFrame: 144 },
];
Sequenceによるタイミング制御
{subtitleCues.map((cue, index) => (
<Sequence
key={index}
from={cue.startFrame}
durationInFrames={cue.endFrame - cue.startFrame}
name={`Subtitle-${index}`}
>
<Subtitle text={cue.text} />
</Sequence>
))}
字幕コンポーネント
const Subtitle: React.FC<{ text: string }> = ({ text }) => {
const frame = useCurrentFrame();
// フェードイン
const opacity = interpolate(frame, [0, 10], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<div style={{
position: "absolute",
bottom: 100,
left: 0,
right: 0,
textAlign: "center",
}}>
<div style={{
display: "inline-block",
opacity,
fontSize: 48,
fontWeight: "bold",
color: "#fff",
backgroundColor: "rgba(0, 0, 0, 0.7)",
padding: "16px 32px",
borderRadius: 8,
fontFamily: '"Noto Sans JP", sans-serif',
}}>
{text}
</div>
</div>
);
};
動作確認
プレビュー
npm run dev
Remotion Studio(localhost:3001)で確認できること:
- タイムラインに音声の波形が表示される
- 字幕が音声に合わせて切り替わる
- フェードイン/フェードアウトが動作する
レンダリング
npx remotion render SubtitledVideo out/subtitled-video.mp4
成功時の出力:
Composition SubtitledVideo
Codec h264
Output out/subtitled-video.mp4
+ out/subtitled-video.mp4 556.1 kB
今日の成果物
| 成果物 | 内容 |
|---|---|
| AudioVideo.tsx | 音声付き動画コンポーネント |
| SubtitledVideo.tsx | 字幕同期動画コンポーネント |
| audio-video.mp4 | 音声付き動画(462 KB) |
| subtitled-video.mp4 | 字幕同期動画(556 KB) |
検証環境
| 項目 | 値 |
|---|---|
| OS | Windows 11 Pro |
| Node.js | v24.11.0 |
| Remotion | v4.0.x |
| 検証日 | 2026-01-12 |
学んだこと
Html5Audioの主要Props
| Prop | 説明 |
|---|---|
src |
音声ファイルのパス(staticFile使用) |
volume |
音量(0〜1)または関数 |
playbackRate |
再生速度 |
trimBefore |
先頭カット(フレーム数) |
Sequenceのフレームカウント
Sequence内の useCurrentFrame() は、Sequenceの開始時点からカウントされます。つまり、字幕コンポーネント内でframe=0は、その字幕が表示され始めた瞬間を指します。
台本データの設計
台本データを配列として定義することで、以下のメリットがあります:
- タイミング調整が容易
- 将来的にJSONファイルから読み込み可能
- リップシンクJSONとの統合が可能
つまずいたポイント
プレビュー時に以下の警告が出ました:
Remotion: The audio with src /static-.../voice_kore.wav has changed it's volume.
これは音量が変化していることを示す警告で、フェード効果の実装によるものです。意図した動作なので問題ありません。
明日のテーマ: Day 13では、キャラクターコンポーネントの基礎実装を行います。画像の切り替えロジックを作成し、静的なキャラクター表示を実現します。
シリーズを追いかける
Discussion