前の記事では、画面下部に表示するシンプルな字幕(Caption)を作りました。
この記事では一歩進んで、テロップ(Telop)を実装します。

字幕との違いは主に2点です。

  • 表示位置を柔軟に切り替えられる(画面中央 / 下部)
  • 日本語フォントや約物カーニングなど、見た目の品質にこだわれる

マニュアル動画・PV動画・製品紹介動画など、用途に応じてスタイルを変えたい場合に役立ちます。

環境構築・プロジェクト作成・基本的な書き出し手順は「Claude Code × Remotion で動画字幕を自動化する」で解説しています。この記事では Telop.tsx の実装に絞って解説します。

Claude Code × Remotion で動画字幕を自動化する【環境構築から書き出しまで】 Claude CodeとRemotionを組み合わせて、動画の字幕(キャプション)をProRes 4444透過動画として生成するワークフローを解説。content.tsでタイムスタンプを一元管理し、マニュアル動画・PV動画・YouTube動画など幅広い動画制作...  続きを読む

    プロジェクト構成

    テロップ用に追加・変更するファイルは次の通りです。

    [project-name]/
    ├─ src/
    │     ├─ components/
    │     │     └─ Telop.tsx                   # ← この記事で作成
    │     ├─ compositions/
    │     │     └─ overlays/
    │     │           └─ TelopOverlayFull.tsx  # ← この記事で作成
    │     ├─ content.ts                         # ← Gemini の出力を貼る
    │     └─ Root.tsx
    └─ public/
           └─ fonts/
                └─ ShipporiMincho-OTF/
                     ├─ ShipporiMincho-OTF-Regular.otf
                     └─ ShipporiMincho-OTF-Medium.otf
    

    Step 1: content.ts の設計

    Gemini でタイムスタンプを自動生成する

    Gemini に動画を渡して content.ts 形式で出力させると大幅に効率化できます。以下のプロンプトをそのまま使えます。

    この動画をもとに、Remotion用のタイムスタンプデータ/各シーンでのテロップ案を作成してください。
    
    ## 出力形式
    TypeScript の配列のみ出力してください。Markdown は不要です。
    以下のインターフェースに従ってください。
    
    interface Step {
      id: number;
      start: string;           // "MM:SS.ff"
      end: string;             // "MM:SS.ff"
      scene: string;           // シーン名
      telop: string;           // 日本語 30 文字以内。不要なシーンは ""
      telopDraft?: {
        draft1?: string;       // テロップ案1
        draft2?: string;       // テロップ案2
        draft3?: string;       // テロップ案3
      }
    }
    
    export const STEPS: Step[] = [...];
    
    ## 動画情報
    - 総尺: [秒数]秒
    - 内容: [動画の内容を簡潔に]
    

    出力されたものをそのまま content.ts に貼り付けるだけです。Markdown テーブルよりパースエラーが少なく、interface 定義をプロンプトに含めることでフィールド漏れも防止できます。


    Step 2: Telop.tsx の実装

    フォントの読み込み

    Remotion では staticFile() を使って public/ フォルダ内のフォントを参照します。@font-face を文字列として定義し、<style> タグで注入するのが基本パターンです。

    import { staticFile } from "remotion";
    
    const fontFace = `
    @font-face {
      font-family: 'Shippori Mincho OTF';
      src: url('${staticFile("fonts/ShipporiMincho-OTF/ShipporiMincho-OTF-Regular.otf")}') format('opentype');
      font-weight: 400;
    }
    @font-face {
      font-family: 'Shippori Mincho OTF';
      src: url('${staticFile("fonts/ShipporiMincho-OTF/ShipporiMincho-OTF-Medium.otf")}') format('opentype');
      font-weight: 500;
    }
    `;
    

    staticFile() を使う理由/fonts/... とハードコードすると、レンダリング時にパスが解決されずフォントが適用されません。必ず staticFile() を経由してください。

    コンポーネント全体

    // src/components/Telop.tsx
    
    import React from "react";
    import { useCurrentFrame, interpolate, staticFile } from "remotion";
    
    const fontFace = `
    @font-face {
      font-family: 'Shippori Mincho OTF';
      src: url('${staticFile("fonts/ShipporiMincho-OTF/ShipporiMincho-OTF-Regular.otf")}') format('opentype');
      font-weight: 400;
    }
    @font-face {
      font-family: 'Shippori Mincho OTF';
      src: url('${staticFile("fonts/ShipporiMincho-OTF/ShipporiMincho-OTF-Medium.otf")}') format('opentype');
      font-weight: 500;
    }
    `;
    
    interface TelopProps {
      text: string;
      startFrame: number;
      endFrame: number;
      /** 表示位置: "center"(デフォルト)または "lower"(画面下部) */
      position?: "center" | "lower";
    }
    
    export const Telop: React.FC<TelopProps> = ({
      text,
      startFrame,
      endFrame,
      position = "center",
    }) => {
      const frame = useCurrentFrame();
      const fadeInDuration = 15;
      const fadeOutDuration = 15;
    
      const opacity = interpolate(
        frame,
        [startFrame, startFrame + fadeInDuration, endFrame - fadeOutDuration, endFrame],
        [0, 1, 1, 0],
        { extrapolateLeft: "clamp", extrapolateRight: "clamp" }
      );
    
      if (frame < startFrame || frame > endFrame) return null;
    
      const positionStyle =
        position === "center"
          ? {
              top: 0, bottom: 0, left: 0, right: 0,
              margin: "auto",
              display: "flex",
              justifyContent: "center",
              alignItems: "center",
            }
          : {
              top: 0, bottom: 0, left: 0, right: 0,
              margin: "auto",
              display: "flex",
              justifyContent: "center",
              alignItems: "end",
              paddingBottom: 80,
            };
    
      return (
        <>
          <style>{fontFace}</style>
          <div
            style={{
              position: "absolute",
              ...positionStyle,
              opacity,
    
              // フォント設定
              fontFamily: "'Shippori Mincho OTF', serif",
              fontSize: 176,          // 4K 基準。1080p なら 96 前後
              fontWeight: 500,
              color: "#FFFFFF",
    
              // 約物カーニング
              fontKerning: "normal",
              fontFeatureSettings: '"palt"',
    
              // テキスト設定
              letterSpacing: "0.03em",
              lineHeight: 1.66,
              textAlign: "center",
              whiteSpace: "pre-wrap", // \n を改行として表示
            }}
          >
            {text}
          </div>
        </>
      );
    };
    

    実装のポイント

    縦位置の切り替え(position prop)

    "center" で画面中央、"lower" で画面下部に表示します。用途に応じて使い分けてください。

    // 画面中央(PV・プロモーション映像向け)
    <Telop text="未来を、標準に。" position="center" ... />
    
    // 画面下部(マニュアル動画・操作説明向け)
    <Telop text="「設定」をクリックします" position="lower" ... />
    

    改行対応

    content.ts\n を使った改行は、CSS の whiteSpace: "pre-wrap" で自動的に改行として表示されます。

    telop: "上質な暮らしを、\nここから始める。",
    

    約物カーニング

    OpenType の palt 機能を有効にすることで、句読点・カギ括弧などの余白が自然に詰まります。日本語テロップでは指定しておくことをおすすめします。

    fontKerning: "normal",
    fontFeatureSettings: '"palt"',
    

    4K 制作時のフォントサイズ

    解像度によって適切なフォントサイズが変わります。目安は以下の通りです。

    解像度fontSize の目安
    1080p(1920×1080)80〜96px
    4K(3840×2160)160〜200px

    Step 3: TelopOverlayFull コンポジションの作成

    // src/compositions/overlays/TelopOverlayFull.tsx
    
    import React from "react";
    import { AbsoluteFill, Sequence } from "remotion";
    import { Telop } from "../../components/Telop";
    import { STEPS, toFrame, VIDEO_CONFIG } from "../../content";
    
    export const TelopOverlayFull: React.FC = () => {
      const { fps } = VIDEO_CONFIG;
    
      return (
        <AbsoluteFill style={{ backgroundColor: "transparent" }}>
          {STEPS.filter((step) => step.telop !== "").map((step) => {
            const startFrame = toFrame(step.start, fps);
            const endFrame = toFrame(step.end, fps);
    
            return (
              <Sequence
                key={step.id}
                from={startFrame}
                durationInFrames={endFrame - startFrame}
              >
                <Telop
                  text={step.telop}
                  startFrame={0}
                  endFrame={endFrame - startFrame}
                  position="center"
                />
              </Sequence>
            );
          })}
        </AbsoluteFill>
      );
    };
    

    step.telop !== "" のフィルターにより、空文字のシーンは自動的にスキップされます。


    Step 4: Root.tsx への登録と書き出し

    // src/Root.tsx
    
    import { Composition } from "remotion";
    import { TelopOverlayFull } from "./compositions/overlays/TelopOverlayFull";
    import { VIDEO_CONFIG } from "./content";
    
    const { fps, width, height, durationSeconds } = VIDEO_CONFIG;
    
    export const RemotionRoot: React.FC = () => (
      <>
        <Composition
          id="TelopOverlayFull"
          component={TelopOverlayFull}
          durationInFrames={durationSeconds * fps}
          fps={fps}
          width={width}
          height={height}
        />
      </>
    );
    

    書き出しコマンドは字幕記事と同じ形式です。

    npx remotion render TelopOverlayFull output/telop.mov \
      --image-format=png \
      --pixel-format=yuva444p10le \
      --codec=prores \
      --prores-profile=4444 \
      --muted
    

    package.json にスクリプト登録しておくと便利です。

    {
      "scripts": {
        "render:telop": "npx remotion render TelopOverlayFull output/telop.mov --image-format=png --pixel-format=yuva444p10le --codec=prores --prores-profile=4444 --muted"
      }
    }
    

    Premiere Pro での合成

    書き出した telop.mov を元動画の上のトラックに配置するだけです。

    # Premiere Pro タイムライン
      ├─ V2: telop.mov(テロップオーバーレイ)
      └─ V1: [元動画]
    

    タイムスタンプは content.ts 側で制御されているため、Premiere Pro での位置調整は基本的に不要です。修正が必要な場合も content.tsstart / end を変更して再レンダリングするだけです。


    まとめ・関連記事

    この記事では Telop.tsx の実装を中心に、日本語フォントの読み込み・約物カーニング・縦位置の切り替えを解説しました。

    content.ts への Gemini 出力の流し込みと組み合わせることで、テロップ付けの作業を大幅に効率化できます。

    Claude Code × Remotion で動画字幕を自動化する【環境構築から書き出しまで】 Claude CodeとRemotionを組み合わせて、動画の字幕(キャプション)をProRes 4444透過動画として生成するワークフローを解説。content.tsでタイムスタンプを一元管理し、マニュアル動画・PV動画・YouTube動画など幅広い動画制作...  続きを読む Remotion で Callout アニメーションを作る【Studio × Zod でリアルタイム座標調整】 Remotionでコールアウト(地図ピン・波紋・コネクター線・ラベル)をdivアニメーションで実装する方法を解説。SVGが使えない問題の解決策、段階的アニメーションのチェイン、Remotion Studio+Zodスキーマによるリアルタイム座標調整まで踏み込ん...  続きを読む Remotion で LottieFiles のアニメーションを使う【矢印・アイコンをオーバーレイ動画に組み込む】 RemotionにLottieFilesのアニメーション(矢印・アイコン等)を組み込む方法を解説。@remotion/lottieのインストールからJSON配置・カラー変更・フェードアニメーション・透過書き出しまで、動画オーバーレイへの実践的な活用方法を紹介し...  続きを読む

    参考リンク