Gemini 動画理解の設定変更によるトークン数の違い

エヌデーデー関口です。

みなさんもご存じの通り、Gemini はテキスト以外にも画像、動画、音声を理解できるマルチモーダル対応の生成 AI モデルです。

テキストのみのプロンプトに比べ、動画、それも音声付きの動画を理解するのには、トークン数が多くなるということは想像ができると思います。

実はこの動画理解のトークン数については、Gemini への指示の仕方で増減させることができます。
今回はその設定の仕方によるトークン数の違いについてご説明します。

Gemini 動画理解の基本的なトークン数の考え方

公式ドキュメントによると、動画のトークンの計算は次のようになっています。

  • ファイル API の処理: ファイル API を使用する場合、動画は 1 フレーム/秒(FPS)で保存され、音声は 1 Kbps(シングル チャンネル)で処理されます。タイムスタンプは 1 秒ごとに追加されます。
  • トークンの計算: 動画の各秒は次のようにトークン化されます。
    • 個々のフレーム(1 FPS でサンプリング):
      • mediaResolution が低に設定されている場合、フレームはフレームあたり 66 個のトークンでトークン化されます。
      • それ以外の場合、フレームはフレームあたり 258 個のトークンでトークン化されます。
    • 音声: 1 秒あたり 32 トークン。
    • メタデータも含まれます。
    • 合計: デフォルトのメディア解像度では動画 1 秒あたり約 300 トークン、低メディア解像度では動画 1 秒あたり 100 トークン。

標準の設定だと、動画 1 秒あたりに約 300 トークンとあります。

ただし、動画理解については、標準以外にいろいろと設定変更することができますので、設定に応じてどのようにトークン数が変化するのかを見てみようと思います。

検証の条件

次のような条件で検証をしました。

  • Gemini 2.5 pro を使用
  • 素材には YouTube に公開されている 18分ほどのショートムービーを利用
  • システムプロンプトは各設定で同じものを使用: 「映画評論家として振る舞ってください。」
  • ユーザープロンプトは基本的に同じ「動画について評論してみてください。」を使用。ただし、後述する時間指定の設定の時だけ、この文面に追記を行った

標準の設定

標準の設定では、モデル、システムプロンプト、動画のURL、ユーザープロンプトを指定するだけです。

コード

import json
from google import genai
from google.genai import types

# クライアント設定
client = genai.Client(
    vertexai=True,
    project="your-project",
    location="us-central1",
)

system_prompt = "映画評論家として振る舞ってください。"
model = "gemini-2.5-pro"
video_file_uri = "https://youtu.be/****************"  # YouTube 動画の URL
user_prompt = "動画について評論してみてください。"

# 標準の設定
config_normal = types.GenerateContentConfig(
    temperature=0,
    top_p=0.95,
    seed=0,
    max_output_tokens=65535,
    safety_settings=[
        types.SafetySetting(
            category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
            threshold=types.HarmBlockThreshold.OFF
        ),
        types.SafetySetting(
            category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
            threshold=types.HarmBlockThreshold.OFF
        ),
        types.SafetySetting(
            category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
            threshold=types.HarmBlockThreshold.OFF
        ),
        types.SafetySetting(
            category=types.HarmCategory.HARM_CATEGORY_HARASSMENT,
            threshold=types.HarmBlockThreshold.OFF
        )
    ],
    thinking_config=types.ThinkingConfig(
        thinking_budget=-1,
    ),
    # システムプロンプトを指定
    system_instruction=[
            types.Part.from_text(text=system_prompt),
    ],
)

# コンテンツを設定
contents = types.Content(
    role="user",
    parts=[
        types.Part(
            file_data=types.FileData(
                file_uri=video_file_uri,  # 動画のURL
                mime_type="video/*"
            )
        ),
        types.Part(text=user_prompt)  # ユーザープロンプト
    ]
)

以上のように設定したら、Gemini にコンテンツの生成を指示します。

なお、今回は出力内容までは判断せず、トークン数のみを調べます。

full_response_text = ""

for chunk in client.models.generate_content_stream(
    model=model,
    contents=contents,
    config=config_normal):

    full_response_text += chunk.text or ""

# ストリーミングの場合、チャンクが最後まで到達したら dict にすると user_metadata でトークン数が確認できる
chunk_dict = chunk.to_json_dict()
usage_metadata = chunk_dict.get("usage_metadata")

print(json.dumps(usage_metadata, indent=2))

結果

次のような結果になりました。

{
    "candidates_token_count": 799,
    "candidates_tokens_details": [
        {
            "modality": "TEXT",
            "token_count": 799
        }
    ],
    "prompt_token_count": 299711,
    "prompt_tokens_details": [
        {
            "modality": "AUDIO",
            "token_count": 26475
        },
        {
            "modality": "TEXT",
            "token_count": 14
        },
        {
            "modality": "VIDEO",
            "token_count": 273222
        }
    ],
    "thoughts_token_count": 1872,
    "total_token_count": 302382,
    "traffic_type": "ON_DEMAND"
}

各属性の意味は次のとおりです。

  • prompt_token_count: 入力のみのトークン数
  • candidates_token_count: 出力のみのトークン数
  • thoughts_token_count: レスポンスの生成に使用された思考トークンのトークン数
  • total_token_count: 入力と出力の両方のトークンの合計数

prompt_tokens_details"modality": "VIDEO" が動画のトークンを示しています。入力プロンプトを比較するので、今回は prompt_token_count を参照します。この場合は、299,711 トークンを消費したことになります。

Gemini 2.5 pro の最大入力トークン数が約 100 万ですから、約 1/3 を消費しています。なお、音声付き動画については約 45 分までという技術仕様が公開されています。

解像度を低くする

動画を理解するのには、非常に細かい動画の変化を見つける必要が無い限りは低解像度で充分なケースが多いはずです。Gemini は前処理で動画の解像度を下げなくても、コンテンツ指示の設定で低解像度で理解するように指定ができます。

この場合は、1 フレーム 66 トークンとなるため、標準の 258 に比べ、約 1/4 のトークン数消費に節約することができます。

コード

低解像度の指定は次のように media_resolution="MEDIA_RESOLUTION_LOW" を指定します。

config_low_reso = types.GenerateContentConfig(
    temperature=0,
# このあたりの設定は前回と同一
...
    system_instruction=[
            types.Part.from_text(text=system_prompt),
    ],
    media_resolution="MEDIA_RESOLUTION_LOW",  # <<< 解像度を低く指定
)

この設定を client.models.generate_content_streamconfig パラメータに指定してやると低解像度での生成が実行できます。

結果

低解像度でのトークン消費は次の通りです。

{
    "candidates_token_count": 790,
    "candidates_tokens_details": [
        {
            "modality": "TEXT",
            "token_count": 790
        }
    ],
    "prompt_token_count": 96383,
    "prompt_tokens_details": [
        {
            "modality": "AUDIO",
            "token_count": 26475
        },
        {
            "modality": "TEXT",
            "token_count": 14
        },
        {
            "modality": "VIDEO",
            "token_count": 69894
        }
    ],
    "thoughts_token_count": 1834,
    "total_token_count": 99007,
    "traffic_type": "ON_DEMAND"
}

VIDEO のトークンが 69,894、prompt_token_count96,383 と標準設定の約 1/3 になりました。

fps を変更する

解像度は標準のまま、fps (1秒あたりのフレーム数)を増減させるとどのようになるでしょうか。

標準では 1 秒 1 フレーム、つまり 1fps で解析をするので、これを増やすと、単純にトークン数が増えていくという計算になりますし、1 より減らせばトークン数が減るという計算になります。

fps を高くするのは、動画の変化が激しいもの(例えばアクション映画)を調べる場合に適しており、低い fps は変化の少ない動画(例えば講義とか会議など)を調べるのに適しています。

なお、fps の最大値は 24 となっています。

コード

fps を変更コードは次のように、コンテンツ内の動画ファイルを示す Part オブジェクトに、video_metadata というパラメータを加え、そこに fps を設定します。

contents_with_fps = types.Content(
    role="user",
    parts=[
        types.Part(
            file_data=types.FileData(
                file_uri=video_file_uri,
                mime_type="video/*"
            ),
            video_metadata=types.VideoMetadata(
                fps=5  # 1 より大きい数字は高い fps、1 より小さいものが低い fps
            )
        ),
        types.Part(text=user_prompt)
    ]
)

結果

fps=5 とした場合(高い fps)の結果は次の通りです。

{
    "candidates_token_count": 818,
    "candidates_tokens_details": [
        {
            "modality": "TEXT",
            "token_count": 818
        }
    ],
    "prompt_token_count": 928404,
    "prompt_tokens_details": [
        {
            "modality": "VIDEO",
            "token_count": 910740
        },
        {
            "modality": "TEXT",
            "token_count": 14
        },
        {
            "modality": "AUDIO",
            "token_count": 17650
        }
    ],
    "thoughts_token_count": 1487,
    "total_token_count": 930709,
    "traffic_type": "ON_DEMAND"
}

VIDEO のトークン数が 910,740、prompt_token_count928,404 となっており、標準の約 3 倍になりました。これだけでもう 100 万トークンが間近ですね。

続いて fps=0.5 とした場合(低い fps)の場合は次のようになりました。

{
    "candidates_token_count": 856,
    "candidates_tokens_details": [
        {
            "modality": "TEXT",
            "token_count": 856
        }
    ],
    "prompt_token_count": 163229,
    "prompt_tokens_details": [
        {
            "modality": "AUDIO",
            "token_count": 26475
        },
        {
            "modality": "TEXT",
            "token_count": 14
        },
        {
            "modality": "VIDEO",
            "token_count": 136740
        }
    ],
    "thoughts_token_count": 2440,
    "total_token_count": 166525,
    "traffic_type": "ON_DEMAND"
}

VIDEO のトークン数が 136,740、prompt_token_count163,229 と、高い fps と比べると約 17% となっています。

クリッピング間隔を設定する

今回採用した動画は約 18 分間のショートムービーですが、この動画全体ではなく一部の時間帯(クリッピング間隔)について Gemini に理解させるという事もできます。この場合は動画全体を理解させるよりトークン数を節約できるとのことですが検証してみましょう。

コード

クリッピング間隔の指定は、先ほどの fps の指定と同じように video_metadatastart_offsetend_offset に秒数を示す文字列(例 60秒であれば ’60s’)を指定します。下記の例は動画開始 5 分から 14 分までの 9 分間のみ参照することを指示しています。

contents_clip = types.Content(
    role="user",
    parts=[
        types.Part(
            file_data=types.FileData(
                file_uri=video_file_uri,
                mime_type="video/*"
            ),
            video_metadata=types.VideoMetadata(
                start_offset='300s',  # 動画開始5分後から
                end_offset='840s',  # 動画開始14分後まで
            )
        ),
        types.Part(text=user_prompt)
    ]
)

結果

結果はどのようになったでしょうか?

{
    "candidates_token_count": 700,
    "candidates_tokens_details": [
        {
            "modality": "TEXT",
            "token_count": 700
        }
    ],
    "prompt_token_count": 152834,
    "prompt_tokens_details": [
        {
            "modality": "VIDEO",
            "token_count": 139320
        },
        {
            "modality": "TEXT",
            "token_count": 14
        },
        {
            "modality": "AUDIO",
            "token_count": 13500
        }
    ],
    "thoughts_token_count": 1439,
    "total_token_count": 154973,
    "traffic_type": "ON_DEMAND"
}

VIDEO が 139,320 ですから、元々の 273,222 約 51% まで削減できました。prompt_token_count152,834 で同じくらいの節約ですね。動画全体で 18 分弱中の 9 分 なので、予想通り約半分になったということで、クリッピング間隔で指定した時間のみを解析していることがわかります。

プロンプトで時間を指定してみる

では、プロンプト上で時間を指定した場合はどうなるでしょうか?

コード

例えば、次のようにプロンプト上で、先ほどのクリッピング間隔と同じ時間帯を指定してみます。

contents_prompt_timing = types.Content(
    role="user",
    parts=[
        types.Part(
            file_data=types.FileData(
                file_uri=video_file_uri,
                mime_type="video/*"
            ),
        ),
        types.Part(text=user_prompt + "\n動画の開始300秒後から840秒後までについてコメントしてください。")  # プロンプトに時間を指定する
    ]
)

結果

結果は次のように、この場合は動画全体をトークン数にカウントするようです。

従って、時間を指定して解析させたい場合はクリッピング間隔の指定方法で実行する方がトークン数の削減になります。

{
    "candidates_token_count": 829,
    "candidates_tokens_details": [
        {
            "modality": "TEXT",
            "token_count": 829
        }
    ],
    "prompt_token_count": 299731,
    "prompt_tokens_details": [
        {
            "modality": "AUDIO",
            "token_count": 26475
        },
        {
            "modality": "TEXT",
            "token_count": 34
        },
        {
            "modality": "VIDEO",
            "token_count": 273222
        }
    ],
    "thoughts_token_count": 1412,
    "total_token_count": 301972,
    "traffic_type": "ON_DEMAND"
}

トークンをできるだけ削減してみる

低い解像度で、低い fps(0.5) で時間を指定する(9分間)というすべての方法を掛け合わせると、どれくらい削減できるでしょうか?

結果

結果は次のようになりました。

{
    "candidates_token_count": 716,
    "candidates_tokens_details": [
        {
            "modality": "TEXT",
            "token_count": 716
        }
    ],
    "prompt_token_count": 31334,
    "prompt_tokens_details": [
        {
            "modality": "AUDIO",
            "token_count": 13500
        },
        {
            "modality": "TEXT",
            "token_count": 14
        },
        {
            "modality": "VIDEO",
            "token_count": 17820
        }
    ],
    "thoughts_token_count": 1436,
    "total_token_count": 33486,
    "traffic_type": "ON_DEMAND"
}

VIDEO が 17,820、prompt_token_count31,334 と、標準の設定(299,711)に比べると 10% とかなり削減できましたね。

まとめ

これまでの検証結果をまとめます。

設定VIDEOのトークントークン全体トークン全体の比較
標準273,222299,711100%
解像度を下げる(A)69,89496,38332%
fps=5910,740928,404310%
fps=0.5(B)136,740163,22954%
クリッピング間隔(全体の約半分)(C)139,320152,83451%
(A)+(B)+(C)17,82031,33410%

以上のことから、即効性のあるトークン削減方法としては、解像度を下げるのを優先的に考えるべきでしょう。

その上で、動画の種類に応じて適切な fps を設定、また解析する時間がわかるのであれば、クリッピング間隔を指定して限定的に解析させるとコストにも優しくなります。

なお、今回は生成内容までは踏み込んでいませんので、コストと精度のバランスを考慮して Gemini とうまく付き合っていきましょう!