I opened the deprecations table late on a Tuesday morning. Three Veo 3.1 previews, shutting down on October 22. The replacement column said gemini-omni-1.1-flash.
My first thought wasn't "I'll just find and replace." It was that I no longer remember everywhere I call Veo. As an indie developer, a few scripts that turn assets into short clips always get left behind. Some still run; some I ran once and forgot.
Decide what is calling before deciding what to fix
My first attempt was to search for veo-3.1 and rewrite every hit. It didn't go well. The search caught stale model names saved in notebook output cells, and it missed a script that reads its model name from an environment variable.
Here is the order I use now.
- Search for the model name as a string (code, config files, notebooks)
- Search for indirect references the string search can't see (environment variables, config keys)
- Sort every hit into "fix before the deadline," "safe to retire," or "not actually used"
Count before you rewrite. I try to hold that line harder the closer a deadline gets.
A script that lists every call site
The shutdown covers three preview models. Check the exact IDs in the deprecations table and paste them into PATTERNS. The version below casts a wide net: anything that starts with veo-3.1 and ends with preview.
import re
import sys
from pathlib import Path
# What this solves: list which file and line still carries a retiring model name
PATTERNS = [
re.compile(r"veo-3\.1[\w.\-]*preview"), # hard-coded strings
re.compile(r"VEO_MODEL|VIDEO_MODEL"), # markers for indirect references (match your own naming)
]
SKIP_DIRS = {".git", "node_modules", ".venv", "__pycache__"}
EXTS = {".py", ".js", ".ts", ".json", ".yaml", ".yml", ".env", ".ipynb", ".md", ".sh"}
def scan(root: Path):
for path in root.rglob("*"):
if any(part in SKIP_DIRS for part in path.parts):
continue
if not path.is_file() or (path.suffix not in EXTS and path.name != ".env"):
continue
try:
lines = path.read_text(encoding="utf-8", errors="ignore").splitlines()
except OSError:
continue
for no, line in enumerate(lines, 1):
for pat in PATTERNS:
if pat.search(line):
yield path, no, line.strip()[:120]
if __name__ == "__main__":
root = Path(sys.argv[1] if len(sys.argv) > 1 else ".")
hits = list(scan(root))
for path, no, text in hits:
print(f"{path}:{no}: {text}")
print(f"\nTotal: {len(hits)}")Why include .ipynb and .md? Notebooks keep model names in their outputs, and a step-by-step note in Markdown is exactly what someone copies and runs six months later.
Paste the output into a spreadsheet and mark each row fix, retire, or unused. A few dozen rows take about ten minutes.
Three places an inventory tends to miss
The script only finds what is inside the repository. At least three places sit outside it.
First, scheduled jobs. cron or a cloud scheduler may pass the model name as a command-line argument, and no amount of code search will show it.
Second, a .env left on another machine. You fix it on your daily laptop, and the stale variable on the second machine is what greets you on deadline day. That's the one that stops my hands.
Third, shared copies of notebooks. You can fix your own repository and still leave the copy you handed to someone else untouched.
These three are about remembering, not searching. I write down every place where I run video jobs on paper and check it against the script's output.
Put the replacement in config, in exactly one place
Once the inventory is done, I move the model name into configuration. One thing I want to be upfront about: I haven't run gemini-omni-1.1-flash myself as of this writing, so I can't tell you whether it's called through generate_videos like Veo or through a different call shape. Check the official migration guide for that. The code below only handles resolving the model name and handling the deadline.
import os
from datetime import date
# What this solves: one place for the model name, and a hard stop after the deadline
SHUTDOWN = date(2026, 10, 22)
def resolve_video_model() -> str:
model = os.environ.get("VIDEO_MODEL")
if not model:
raise RuntimeError("VIDEO_MODEL is not set. Put it in .env or the runtime environment")
is_veo_preview = model.startswith("veo-3.1") and model.endswith("preview")
if is_veo_preview and date.today() >= SHUTDOWN:
# Past the deadline, stop instead of silently switching to a replacement
raise RuntimeError(f"{model} was shut down on {SHUTDOWN}. Update your configuration")
if is_veo_preview:
remaining = (SHUTDOWN - date.today()).days
print(f"WARNING: {model} stops in {remaining} days")
return modelWhy not fall back to a replacement automatically? Output style, pricing, and supported length and resolution differ from model to model. If traffic drifts to another model unnoticed, your bill and your results move out of step without any alarm. A loud stop is cheaper to clean up.
Before and after, the change is this small:
# Before: the model name is hard-coded at every call
operation = client.models.generate_videos(model="veo-3.1-...-preview", prompt=prompt)
# After: resolved in one place; to change models, touch only the environment variable
model_id = resolve_video_model()The ... stands for whatever real name your inventory turned up. After switching, run one short clip all the way to a finished video. If the call shape differs, this is where you'll find out.
How I plan to spend the 16 days
- This week: run the inventory and mark each row fix, retire, or unused
- Early next week: move only the "fix" rows into config and test them one at a time with short clips
- Three days before Oct 22: run the script once more to confirm no old model names remain
Scripts I no longer use get switched off, not repaired. Keeping the repair scope small is part of the line I hold before a deadline.
One first step
Run the search script once over a folder where you've touched video generation. The hit count tells you how big this week's work is. If it's small, that's a weight off; if it's large, you'll be glad you learned it today.
For the basics of calling Veo 3.1 Lite, see Getting started with low-cost video generation using the Veo 3.1 Lite API. It may help as a footing when you check the new call shape.