🎬

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では、キャラクターコンポーネントの基礎実装を行います。画像の切り替えロジックを作成し、静的なキャラクター表示を実現します。


シリーズを追いかける

著者ページ(Zenn)

Discussion