告知用の短い動画を作り直していて、手が止まりました。8秒のクリップの照明だけを夕方の色にしたい。以前なら素材から組み直すか、動画編集ソフトでグレーディングをやり直すところです。Omni Flash の公開プレビューでは、生成済みの動画に「照明を夕方の斜光に」と自然文で伝えるだけで、登場人物も構図も保ったまま照明が差し替わりました。元の音声トラックはそのまま残ります。
同じ調子で「カメラを30度だけ右に振って」と頼んでみると、こちらは思ったほど素直ではありませんでした。少し寄っただけだったり、別のショットに置き換わってしまったり。効く指示と、効かない指示がある。感覚では分かっても、それを毎回の目視で確かめていると、1秒0.10ドルの編集を何度も回すことになり、費用の見通しが立ちません。
個人開発でアプリの告知動画を量産している私自身にとって、ここは看過できないところでした。そこで「どの種類の指示がどれくらい効くのか」を、生成前後のフレームを Gemini に照合させて機械的に仕分けながら測ってみました。この記事は、その42回分の編集ループの記録です。
会話編集は「作り直す」から「言い直す」への移行
Omni Flash の会話編集は、動画を最初から作り直すのではなく、いま手元にある動画へ差分の指示を重ねる操作です。「登場人物を眼鏡の男性に」「背景の雨をやませて」「全体をやや暖色へ」といった言い直しで、指定した部分だけが更新されます。公開プレビューの説明では、元の音声と映像のトラックはネイティブに保持されると案内されています。
この設計は、告知素材の制作フローを変えます。動画理解を1パスに畳む話は以前 Omni Flash で動画理解を1パスに畳む で扱いましたが、今回は逆向き、つまり生成物を対話で仕上げていく側の話です。
ただし「言い直せる」ことと「意図どおりに直る」ことは別です。ここを混同すると、外れた編集にも課金され、しかも外れたことに気づかないまま次の指示を重ねてしまいます。まず必要なのは、効いたかどうかを人手に頼らず判定する仕組みでした。
効いた編集を人手で確かめない — フレーム照合という考え方
編集が効いたかどうかは、突き詰めれば「要求した変化が、生成前後で実際に起きたか」に還元できます。であれば、生成前の代表フレームと生成後の代表フレームを1枚ずつ取り出し、「この指示が反映されているか」を Gemini に照合させれば、真偽を機械的に受け取れます。
判定を確率的な文章で受け取ると運用に乗りません。responseSchema で構造を固定し、landed(反映されたか)・confidence(確信度)・note(根拠)の3つだけを返させます。確信度が低いものは「反映されていない」側に寄せて扱う、と決めておくと、迷いが消えます。
これは、決定的に確かめられることを確率的な仕組みへ丸投げしない、という判断です。SynthID の陰性が「AI 生成でない証明」にならない話を SynthID の非対称性 で書きましたが、発想は同じで、確信の持てない出力は安全側(未反映)へ倒します。
最小の編集ループ
まず、編集を1回かける薄いラッパです。Omni Flash の動画編集は公開プレビューで、呼び出しの名前は今後変わり得ます。ここでは編集そのものは関数の内側に隠し、この記事の本題である「検証」と「コスト台帳」の側を外に出します。
from dataclasses import dataclass, field
from pathlib import Path
OMNI_FLASH_MODEL = "gemini-omni-flash" # 公開プレビュー
PRICE_PER_OUTPUT_SEC_USD = 0.10 # 出力1秒あたり0.10ドル
@dataclass
class EditResult :
instruction: str
out_path: Path
out_seconds: float
landed: bool = False
confidence: float = 0.0
note: str = ""
def omni_flash_edit (client, base_video: Path, instruction: str ,
out_dir: Path) -> EditResult:
"""会話編集を1回かけて、出力動画のパスと尺を返す薄いラッパ。
※ 編集APIの正確な呼び出しは公開プレビューで変わり得るため関数内に隔離する。
"""
resp = client.videos.edit( # preview: 名称は変わり得ます
model = OMNI_FLASH_MODEL ,
video = str (base_video),
instruction = instruction,
)
out = out_dir / f "edit_ { abs ( hash (instruction)) % 10_000 } .mp4"
out.write_bytes(resp.video_bytes)
return EditResult(
instruction = instruction,
out_path = out,
out_seconds = float (resp.duration_seconds),
)
尺(out_seconds)を必ず持ち帰るのが肝心です。課金は出力秒数に比例するので、ここを記録しておかないと後でコストが再現できません。
生成前後のフレームを Gemini に照合させる
次が本題の検証です。動画から代表フレームを1枚取り出し、編集前後の2枚と指示文を gemini-3.5-flash に渡して、反映の有無を構造化 JSON で受け取ります。
import json, subprocess
from pydantic import BaseModel
class EditVerdict ( BaseModel ):
landed: bool
confidence: float # 0.0〜1.0
note: str
def grab_frame (video: Path, at_sec: float , out: Path) -> Path:
# 代表フレームを1枚だけ取り出す(中央付近を既定にする)
subprocess.run(
[ "ffmpeg" , "-y" , "-ss" , str (at_sec), "-i" , str (video),
"-frames:v" , "1" , str (out)],
check = True , capture_output = True ,
)
return out
def verify_edit (client, before_png: Path, after_png: Path,
instruction: str ) -> EditVerdict:
prompt = (
"2枚の画像は、ある動画の編集前と編集後の代表フレームです。 \n "
f "要求した編集: 「 { instruction } 」 \n "
"この編集が後者に実際に反映されているかを判定してください。 \n "
"確信が持てない場合は landed=false 側に寄せてください。"
)
resp = client.models.generate_content(
model = "gemini-3.5-flash" ,
contents = [
prompt,
{ "inline_data" : { "mime_type" : "image/png" ,
"data" : before_png.read_bytes()}},
{ "inline_data" : { "mime_type" : "image/png" ,
"data" : after_png.read_bytes()}},
],
config = {
"response_mime_type" : "application/json" ,
"response_schema" : EditVerdict,
},
)
return EditVerdict( ** json.loads(resp.text))
確信度の低い判定を未反映へ寄せる一行が、後の意思決定を静かに支えます。効いていないのに「効いた」と数える誤りは、外れた編集をそのまま採用してしまう最悪の側なので、そこだけは保守的に倒します。
実測 — どの種類の指示が効いて、どこから外れたか
告知用の8秒クリップ3本に対し、種類の異なる編集指示を合計42回かけ、上のループで反映率を測りました。確信度0.6未満は未反映として集計しています。数字は私の素材・私のプロンプトでの傾向で、環境が変われば動きます。
編集の種類 試行 反映 反映率 備考
全体の照明・色調(夕方・寒色・彩度) 12 11 約92% 最も素直。グレーディング相当は安定
雰囲気・天候(雨をやませる 等) 8 7 約88% 大域的な変化は効きやすい
登場人物の差し替え(属性で指定) 10 7 約70% 「眼鏡の男性」等の役割指定は通る
精密なアングル指定(30度右へ 等) 7 3 約43% 寄っただけ・別ショット化が混じる
小物の位置・サイズ(ロゴを左下へ2割小さく) 5 2 約40% 空間的に精密な指定は外れやすい
はっきりした傾向が出ました。大域的な見た目の変化(照明・色調・天候)は素直に効き、空間的に精密な指定(角度・位置・サイズ)はしばしば外れます。 登場人物の差し替えはその中間で、細かな同一性ではなく役割や属性で言い表したときに通りやすい、という手応えでした。
外れた側の内訳を見ると、精密なアングル指定では「少しだけ寄った」「まったく別のショットに置き換わった」の2パターンが多く、指示を数値で刻むほど当たりにくくなります。ここは数字(30度・2割)に頼らず、狙いを言葉で述べたほうが結果が安定しました。
Before / After — 目視で回す編集と、検証ゲート付きの編集
反映率が分かると、運用の形が変わります。従来は「良さそうに見えるまで編集を重ねる」やり方で、これは外れた回にも課金され、しかも何回で終わるか読めません。
# Before: 目視で「良くなるまで」重ねる — コストが読めない
def edit_until_ok (client, base, instructions, out_dir):
cur = base
for ins in instructions:
r = omni_flash_edit(client, cur, ins, out_dir)
cur = r.out_path # 効いたか確かめずに次へ積む
# 人が動画を再生して目視で判断 … 外れも課金されている
return cur
これを、1編集ごとに検証を挟み、効かなかった指示は言い換えて上限回だけ再試行し、それでも通らなければ採用を見送る形に置き換えます。
# After: 検証ゲートで「効いた編集だけ」採用し、費用を台帳に積む
@dataclass
class CostLedger :
spent_usd: float = 0.0
wasted_usd: float = 0.0 # 未反映に消えた分
kept: list = field( default_factory = list )
def charge (self, r: EditResult):
cost = r.out_seconds * PRICE_PER_OUTPUT_SEC_USD
self .spent_usd += cost
if r.landed:
self .kept.append(r)
else :
self .wasted_usd += cost
def edit_with_gate (client, base, instructions, out_dir,
max_retry = 1 , conf_floor = 0.6 ):
cur, ledger = base, CostLedger()
for ins in instructions:
attempt, landed = 0 , False
while attempt <= max_retry and not landed:
r = omni_flash_edit(client, cur, ins, out_dir)
mid = r.out_seconds / 2
before = grab_frame(cur, mid, out_dir / "b.png" )
after = grab_frame(r.out_path, mid, out_dir / "a.png" )
v = verify_edit(client, before, after, ins)
r.landed = v.landed and v.confidence >= conf_floor
r.confidence, r.note = v.confidence, v.note
ledger.charge(r)
if r.landed:
cur, landed = r.out_path, True
attempt += 1
if not landed:
print ( f "見送り: { ins } (最終確信度 { r.confidence :.2f } )" )
return cur, ledger
3本のクリップで両者を回したところ、目視方式は編集18回で採用12・費用の約31%が外れた回に消えていました。検証ゲート方式は同じ狙いを14回で達成し、外れは早期に見送ったため無駄は約12%に収まりました。金額にすると、8秒クリップ1本あたり0.80ドルの編集で、この差は積み重なると効いてきます。
トラックは保持されるが、編集を重ねると同期が緩む
公開プレビューの案内どおり、音声トラックは編集後も保持されました。ただし本番運用で気づいた落とし穴があります。編集を3回、4回と重ねると、映像側の尺がわずかに前後することがあり、そのたびに口の動きとナレーションの同期が少しずつ緩みました。1回の編集では気にならない程度でも、積み重なると告知動画としては目につきます。
対処として、音声を差し替えない編集は3回までに抑え、それ以上直したい場合は音声を一旦外して映像だけを仕上げ、最後にナレーションを載せ直す運用にしました。同期は編集回数に対して単調に劣化するわけではありませんが、回数が増えるほど緩む方向に振れる、という前提で組んでおくと安全です。
もう一つ、検証で使う代表フレームは中央付近1枚を既定にしましたが、カット割りのある動画では冒頭・中央・末尾の3枚に増やしたほうが判定が安定しました。フレーム1枚では、変化が起きた区間をたまたま外すことがあります。
個人開発でどう使うか — 効く指示に寄せる
測ってみて、運用の方針がはっきりしました。告知動画の仕上げは、効きやすい指示(照明・色調・天候・属性での人物指定)に寄せて設計し、空間的に精密な指定はプロンプト側でなく生成時の構図で決めておく。 アングルや小物配置を後から数値で直そうとすると、外れて課金される回が増えます。
私の場合、壁紙アプリや癒し系アプリの告知は、最初の生成で構図を固め、Omni Flash の会話編集は色と雰囲気の微調整に絞ることで、費用と手戻りの両方が落ち着きました。App Store 向けのプレビュー動画を差し替えるときも、同じ切り分けで回しています。実務では、次の3点を運用の型として推奨します。
生成時に構図・アングル・小物配置を決め切る(後からの数値指定に頼らない)
会話編集は照明・色調・天候・属性での人物指定に寄せ、1編集ごとに反映を検証する
音声を差し替えない編集は3回までに抑え、それ以上は映像だけ仕上げてから音声を載せ直す
画像側のコスト設計は Nano Banana 2 Lite のコスト設計 と合わせて考えると、静止画と動画で桁の違う単価をどこに割り当てるかが見えてきます。
会話で動画を直せるのは、確かに制作の敷居を下げてくれます。ただ「言えば直る」を過信せず、効いたかどうかを機械で確かめ、外れる指示は設計側へ移す。この地味な仕分けが、プレビュー段階のモデルを個人開発の実務に乗せる近道でした。実装の参考になれば幸いです。お読みいただきありがとうございました。