GEMINI LABEN
NANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定ですNANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定です
記事一覧/API / SDK
API / SDK/2026-04-26中級

Gemini API で動画から多言語字幕(SRT/VTT)を生成する実装パターン

Gemini API のマルチモーダル動画入力を使い、SRT/VTT 形式の多言語字幕を生成する実装パターンをまとめました。タイムスタンプ精度と翻訳品質を両立させるプロンプト設計を解説します。

gemini-api279video5subtitlesrtvttmultimodal24

自分で撮影したショート動画に字幕を付けたい、というだけの単純な要件なのですが、いざ自動化しようとすると意外と手間がかかります。Whisper で音声を文字起こしして、それを別の翻訳 API に投げて、最後にタイムスタンプを SRT 形式に整える ... と、3〜4 個のステップを連結することになります。

Gemini API のマルチモーダル動画入力を使うと、この一連の流れを 1 リクエストで完結できます。ただし「動く実装」と「実用に耐える実装」の差は意外と大きく、特にタイムスタンプ精度と翻訳品質の両立で何度かつまずきました。ここでは5 分前後の動画を想定して、私自身が辿り着いた実装パターンをまとめます。

Gemini API への動画入力 — 2 つの渡し方を使い分ける

Gemini API に動画を渡す方法は、大きく 2 つあります。

  • インライン送信: Base64 エンコードして parts に直接埋め込む方法。20 MB 程度までの短い動画向けです
  • File API 経由: client.files.upload() でアップロードしてから参照する方法。最大 2 GB まで、48 時間キャッシュされます

5 分のスマホ動画で 50〜80 MB になりますので、実用上はほぼ File API の出番になります。ただしアップロードが完了してから stateACTIVE になるまで数秒〜十数秒かかるので、ポーリングを入れる必要があります。

from google import genai
from google.genai import types
import time
 
client = genai.Client(api_key="YOUR_API_KEY")
 
# 動画ファイルをアップロード
video_file = client.files.upload(file="meeting.mp4")
print(f"Uploaded: {video_file.name}, state: {video_file.state}")
 
# state が ACTIVE になるまでポーリング
while video_file.state == "PROCESSING":
    time.sleep(2)
    video_file = client.files.get(name=video_file.name)
print(f"Ready: {video_file.state}")  # 期待値: ACTIVE

PROCESSING のまま投げると 400 INVALID_ARGUMENT で弾かれますので、必ず ACTIVE を待ってからリクエストを送ってください。

タイムスタンプ精度を上げる 3 つのコツ

字幕生成で最初にぶつかる壁は「タイムスタンプがずれる」問題です。Gemini はおおまかな時刻を返してくれますが、何も指示しないと 1〜3 秒のずれが普通に出ます。

私が試した中で効果が高かったのは、次の 3 つです。

  1. 動画のサンプリング fps を明示する: デフォルトでは 1 fps でサンプリングされます。video_metadata.fps で 2〜4 fps を指定すると、短いセリフの取りこぼしが減ります
  2. プロンプトで「MM:SS.mmm 形式で」と指示する: ミリ秒精度で書くよう明示すると、Gemini は内部的に細かく時刻を管理してくれます
  3. 1 字幕あたりの最大文字数を指定する: 「1 字幕は最大 35 文字、2 行まで」のように制約を入れると、長すぎる字幕で表示時間が破綻するのを防げます
config = types.GenerateContentConfig(
    response_mime_type="application/json",
    response_schema={
        "type": "ARRAY",
        "items": {
            "type": "OBJECT",
            "properties": {
                "start": {"type": "STRING", "description": "MM:SS.mmm format"},
                "end": {"type": "STRING", "description": "MM:SS.mmm format"},
                "ja": {"type": "STRING"},
                "en": {"type": "STRING"},
            },
            "required": ["start", "end", "ja", "en"]
        }
    }
)
 
prompt = """この動画の話者音声を聞き取り、字幕データを生成してください。
要件:
- タイムスタンプは MM:SS.mmm 形式(例: 01:23.450)
- 1字幕は最大35文字、2行まで(改行は \\n で表現)
- ja は日本語の話者発話そのまま、en は自然な英語訳
- 沈黙・BGMのみの区間は出力しない
"""
 
response = client.models.generate_content(
    model="gemini-2.5-pro",
    contents=[video_file, prompt],
    config=config,
)
 
import json
captions = json.loads(response.text)
print(f"生成字幕数: {len(captions)}")

response_schema を使うと出力が JSON として保証されるため、後段の SRT 変換が楽になります。

SRT / VTT への構造化出力

SRT と VTT はほぼ同じ構造ですが、タイムスタンプの区切り文字(,.)と先頭の WEBVTT ヘッダの有無が違います。

def to_srt(captions: list[dict]) -> str:
    """captions は [{start, end, ja, en}, ...] 形式"""
    lines = []
    for i, c in enumerate(captions, 1):
        start = c["start"].replace(".", ",")  # SRT は ms を , で区切る
        end = c["end"].replace(".", ",")
        # MM:SS,mmm を HH:MM:SS,mmm に揃える
        start = f"00:{start}" if start.count(":") == 1 else start
        end = f"00:{end}" if end.count(":") == 1 else end
        lines.append(f"{i}\n{start} --> {end}\n{c['ja']}\n")
    return "\n".join(lines)
 
def to_vtt(captions: list[dict], lang: str = "ja") -> str:
    """VTT はヘッダ付き、ms は . 区切り"""
    body = []
    for c in captions:
        start = f"00:{c['start']}" if c['start'].count(":") == 1 else c['start']
        end = f"00:{c['end']}" if c['end'].count(":") == 1 else c['end']
        body.append(f"{start} --> {end}\n{c[lang]}")
    return "WEBVTT\n\n" + "\n\n".join(body)
 
with open("output_ja.srt", "w") as f:
    f.write(to_srt(captions))
with open("output_en.vtt", "w") as f:
    f.write(to_vtt(captions, lang="en"))

YouTube は SRT と VTT どちらもアップロードできますが、自前 Web 配信であれば HTML5 の <track> タグが要求する VTT のほうが扱いやすいです。

多言語字幕を 1 リクエストで完結させる

字幕用途では、聴覚障害者向けに同じ言語の字幕を出しつつ、海外向けに英語字幕も用意する、というケースがよくあります。

「先に日本語字幕を生成 → 別途翻訳 API に投げる」という 2 段階構成にすると、タイムスタンプの整合性が取れず、字幕が画面に出るタイミングと音声がずれてしまいます。

そこで、response_schema の中に複数言語のフィールドを入れて 1 リクエストで両方とも生成させるパターンが効きます。先ほどのコード例では jaen を同時に出力していましたが、ここに zh-Hanskoes などを追加するだけで多言語字幕が一気に揃います。

ただし言語数を増やすほど出力トークンが増え、コストが線形に上がっていきますので、現実的には 2〜3 言語までが扱いやすいラインです。私は基本「ja + en の 2 本立て」にしておき、必要に応じて他言語を後段で追加生成するようにしています。

モデル選び — Flash と Pro の使い分け

実験してみたところ、Gemini 2.5 Flash は 5 分の動画を 2.5 Pro の約半分の時間、コストもおおよそ 10〜15% 程度で処理してくれます。個人用の軽いコンテンツで自分でチェックできる範囲なら、Flash から始めるのが現実的です。

Pro が効くのは、訛りの強い話者・複数言語が混ざる動画・BGM や環境音が大きい素材など、音声分離が難しいケースです。同じ response_schema とプロンプトがそのまま動くので、モデル名を変えるだけで切り替えられます。

# まずは Flash で叩き台を生成
response = client.models.generate_content(model="gemini-2.5-flash", contents=[video_file, prompt], config=config)
# ノイズが多い素材は Pro で再生成
response = client.models.generate_content(model="gemini-2.5-pro", contents=[video_file, prompt], config=config)

Flash で全件処理 → 一部サンプリングして手直し → ダメな素材だけ Pro に回す、という二段構えにしておくと、コストとクオリティのバランスを取りやすくなります。

公開前の軽量 QA パス

プロンプトを詰めても、機械生成字幕はそのまま視聴者に出すと細かい違和感が残ります。次の 3 つを自動チェックするだけで、致命的な問題はだいたい拾えます。

  1. 読みやすさ(CPS): 1 秒あたり 17 文字を超える字幕は人間がまず追いつけません。len(text) / (end - start) で算出して閾値オーバーをマークします
  2. オーバーラップ検出: 隣接する字幕の end が次の start より後ろに来ていると、両方が画面に重なって表示されます。線形スキャンですぐ拾えます
  3. 空白区間の検出: 隣接字幕の間が 10 秒以上空いている場合、無音検出が誤動作したか、モデルが発話を取りこぼした可能性があります
def qa_captions(captions: list[dict]) -> list[str]:
    issues = []
    def to_seconds(ts: str) -> float:
        # MM:SS.mmm -> 秒
        m, rest = ts.split(":", 1)
        return int(m) * 60 + float(rest)
    prev_end = 0.0
    for i, c in enumerate(captions):
        s, e = to_seconds(c["start"]), to_seconds(c["end"])
        cps = len(c["ja"]) / max(e - s, 0.1)
        if cps > 17:
            issues.append(f"cue {i}: 速すぎ ({cps:.1f} cps)")
        if s < prev_end:
            issues.append(f"cue {i}: 前の字幕と重複")
        if s - prev_end > 10:
            issues.append(f"cue {i}: 直前と {s - prev_end:.1f}s の空白")
        prev_end = e
    return issues
 
for problem in qa_captions(captions):
    print(problem)

私はこのチェックを生成スクリプトの最後に挟んでおき、問題リストが空でないときだけ手動レビューに回しています。クリーンに収録された素材であればたいていリストは空で返ってきて、SRT はそのままアップロード可能です。

本番運用で気づいた落とし穴

  • 1 時間超の動画は分割が必須: Gemini 2.5 Pro でも 1 時間の動画は処理時間が長く、タイムアウトしやすいです。15〜20 分単位でカットして並列処理し、後段で時刻オフセットを足し込むほうが安定します
  • 無音区間で誤検出が出ることがある: BGM だけで話し声がない区間でも、Gemini が「えーっと」「うーん」のような相槌を勝手に入れてくることがあります。プロンプトに「沈黙・BGM のみの区間は出力しない」と明記してください
  • 固有名詞が認識されにくい: 自社サービス名や人名は何度試しても正しく拾えないことがあるので、プロンプトの末尾に「以下の固有名詞は正確に表記すること: Dolice, gemilab.net, ...」のように単語リストを与えるのがおすすめです

関連コンテンツ

字幕生成の前提となる動画入力の基本仕様は、Gemini File API完全ガイド:ファイルのアップロードと活用 と Gemini 2.5 Pro 動画理解の実践ガイド で詳しく扱っています。構造化出力(response_schema)の応用パターンを深掘りしたい方は JSON モードと構造化出力の基本 も併読すると理解が深まります。

全体を振り返って — まず 30 秒の動画で試してみる

動画字幕の自動化は、いきなり長尺で試すと「どこで失敗しているか分からない」状態に陥りがちです。30 秒の自撮り動画で response_schema 付きの最小構成を動かして、SRT として再生できるところまで確認してから、本番動画にスケールするのがおすすめです。

最初の 1 本が動けば、あとはプロンプトのチューニングだけで品質が伸びていきます。Google AI Studio の無料枠でも十分に試せますので、ぜひ手元の素材で動かしてみてください。

シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API / SDK2026-03-26
Gemini API マルチモーダル実践テクニック集 — 画像・動画・音声・PDFを自在に扱う
Gemini APIの4つのモダリティ(画像・動画・音声・PDF)を統合する実践的なテクニック。ストリーミング対応パイプラインとFunction Calling併用の高度な実装ガイド。
API / SDK2026-07-07
Omni Flash で短尺動画から SNS 告知メタデータを1パス抽出する
公開プレビューの Gemini Omni Flash に短尺動画を1回渡すだけで、告知文・チャプター・見せ場タイムスタンプを構造化 JSON で受け取る実装をまとめます。フレーム抽出を挟む多コール構成との違い、fps と media_resolution の効かせどころ、1本あたりのコスト試算まで、個人開発の告知作業を回す視点で扱います。
API / SDK2026-06-23
Nano Banana 2 に動画を渡してサムネイルを1枚生成する — gemini-3.1-flash-image の動画→画像を実装したメモ
GA になった gemini-3.1-flash-image(Nano Banana 2)に動画ファイルを文脈として渡し、サムネイルを1枚生成する実装手順です。フレーム抽出との違い、preview からの移行差分、1枚あたりのコストと所要時間の実測までまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →