動画のサムネイルを手で切り出す作業が、地味に時間を奪っていました。個人開発で複数のアプリ紹介動画や短い解説クリップを回していると、1本ごとに「見栄えのする1フレーム」を探してトリミングし、文字を載せる下地を作る——この前段だけで1本あたり10分近く溶けます。10本あれば、それだけで午後が終わります。
6月22日に gemini-3.1-flash-image(通称 Nano Banana 2)が GA になり、動画ファイルそのものをマルチモーダルの文脈として渡して、サムネイル・ポスター・インフォグラフィックを生成できるようになりました。フレームを1枚選んで渡すのではなく、動画を丸ごと文脈として読ませて「この動画を象徴する静止画を作って」と頼める、という点が今までと違います。私自身、半信半疑で手元のクリップで試したところ、思った以上に「動画の主題」を拾った1枚が返ってきたので、実装の手順と、運用に乗せるうえで引っかかった点を残しておきます。
「フレームを選ぶ」から「動画を文脈として渡す」への変化
これまで動画からサムネイルを作る場合、自分か ffmpeg が代表フレームを1枚選び、その静止画を画像理解モデルに渡す、という二段構えが普通でした。問題は「代表フレームを選ぶ」工程が機械的になりがちなことです。動きの山場や、文字が出る瞬間といった「人間が見て良いと感じる瞬間」は、明るさやシャープネスのスコアだけでは拾えません。
gemini-3.1-flash-image の動画入力は、ここを一段飛ばします。動画を文脈として渡すと、モデルは時間方向の流れを踏まえたうえで「象徴的な1枚」を生成します。既存のフレームをそのまま返すのではなく、新しく描き起こす点に注意してください。つまり出力は「動画の中の実在フレーム」ではなく「動画の主題を表す生成画像」です。実写の正確な再現が要件なら ffmpeg のフレーム抽出が向きますが、SNS のサムネイルやポスターのように「雰囲気が伝わる1枚」が欲しい用途では、生成側が圧倒的に速いというのが触ってみた実感です。
この使い分けは、以前 Gemini 3.2 Flash の Image Output で壁紙の色違いを量産した実装メモ で触れた「実写の改変は避け、生成は生成として割り切る」という線引きとも地続きです。
最小構成:動画を渡して1枚を生成する
まずは動かして感触をつかむのが早いです。Files API で動画をアップロードし、その参照を画像生成リクエストの文脈に含めるだけです。
動画をアップロードする
# pip install google-genai
from google import genai
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
# 動画を Files API にアップロード(数十秒のクリップを想定)
video = client.files.upload(file="intro_clip.mp4")
# アップロード直後は state=PROCESSING のことがあるので ACTIVE を待つ
import time
while video.state.name == "PROCESSING":
time.sleep(2)
video = client.files.get(name=video.name)
if video.state.name != "ACTIVE":
raise RuntimeError(f"upload failed: {video.state.name}")
print("uploaded:", video.name) # files/xxxxxxxxここで PROCESSING を待たずに次へ進むと、生成リクエスト側で「ファイルがまだ使えない」というエラーになります。最初にここで詰まったので、ACTIVE になるまで待つループは省かないでください。
動画を文脈に画像を生成する
from google.genai import types
resp = client.models.generate_content(
model="gemini-3.1-flash-image", # GA 版。preview サフィックスは付けない
contents=[
video, # 動画そのものを文脈として渡す
(
"この動画を象徴する1枚のサムネイル画像を生成してください。"
"縦横比は16:9。中央に主題の被写体を据え、"
"上部に短いテキストを載せられる余白を残してください。"
"実写の写し取りではなく、雰囲気が伝わる構図で。"
),
],
config=types.GenerateContentConfig(
response_modalities=["IMAGE"],
),
)生成画像を保存する
saved = 0
for part in resp.candidates[0].content.parts:
if getattr(part, "inline_data", None) and part.inline_data.data:
with open(f"thumb_{saved}.png", "wb") as f:
f.write(part.inline_data.data)
saved += 1
print(f"saved {saved} image(s)") # 期待値: saved 1 image(s)ポイントは3つです。第一に、モデル名は GA 版の gemini-3.1-flash-image を使い、-preview を付けないこと。第二に、response_modalities に IMAGE を含めること(テキストだけ返ってきて画像が空、という時はここの設定漏れがほとんどです)。第三に、プロンプトで「余白」「縦横比」「実写の写し取りはしない」を明示することです。後段で文字を載せるなら、上部の余白を頼んでおくと作業が楽になります。