TL;DR — This tutorial shows how to build a two-stage "zero-studio" pipeline: first generate a high-fidelity static image using Gemini 3.1 Flash Image (community nickname: Nano Banana), then animate it with an AI video generator from image (Veo 3.1 or Gemini Omni Flash Preview). Includes working Python code, cost estimates, and failure handling.
Why a Two-Stage Pipeline?
Direct text-to-video models often struggle with:
- Precise text rendering (e.g., product labels, UI mockups)
- Spatial consistency across frames
- Complex scene composition (lighting, reflections, depth)
By splitting the workflow:
-
Stage 1 (Anchor): Use Nano Banana 2.0 (
gemini-3.1-flash-image) to generate a structurally accurate, high-resolution image with exact text and stable composition. - Stage 2 (Motion): Feed that image into an ai video generator from image (Veo 3.1 or Gemini Omni Flash Preview) to apply camera motion, lighting changes, or particle effects.
This approach trades a bit of latency for significantly higher control and consistency—especially useful for product demos, ad creatives, or UI animations.
Architecture Overview
[ Text Prompt ]
│
▼
┌─────────────────────────┐
│ Nano Banana │
│ (gemini-3.1-flash-image)│
└─────────────────────────┘
│
▼
┌─────────────────────────┐
│ Generated Image (PNG) │
└─────────────────────────┘
│
▼
┌─────────────────────────┐
│ ai video generator │
│ from image │
│ (Veo 3.1 / Omni Flash) │
└─────────────────────────┘
│
▼
┌─────────────────────────┐
│ Cinematic Video (.mp4) │
│ 5–10 sec, 720p/1080p │
└─────────────────────────┘
Prerequisites
pip install google-genai pillow
export GEMINI_API_KEY="your_api_key_here"
Note: As of August 2026:
gemini-3.1-flash-imageis GA.veo-3.1-generate-previewandgemini-omni-flash-previeware in preview (max 10 sec, 720p).- Veo 3.0 models were deprecated on 2026-06-30.
Step-by-Step Implementation
1. Generate Static Asset with Nano Banana
import os
from google import genai
from google.genai import types
from PIL import Image
client = genai.Client()
def generate_static_asset(prompt: str, output_path: str = "base_image.png"):
"""
Generates a high-fidelity static image using Gemini 3.1 Flash Image.
Community nickname: Nano Banana.
Model: gemini-3.1-flash-image
"""
print(f"Generating image with Nano Banana for: '{prompt}'...")
response = client.models.generate_content(
model="gemini-3.1-flash-image",
contents=[prompt],
config=types.GenerateContentConfig(
response_modalities=["IMAGE"],
),
)
for part in response.candidates[0].content.parts:
if part.inline_data is not None:
image = Image.from_bytes(part.inline_data.data)
image.save(output_path)
print(f"✓ Base image saved to {output_path}")
return output_path
raise ValueError("Failed to generate image or retrieve inline data.")
Tip: For consistent branding across multiple shots, pass up to 7 reference images via
config.reference_images(Veo 3.1) or use Nano Banana's multi-image composition. openpaths
2. Animate with Veo 3.1 (Recommended) or Gemini Omni Flash
import time
from google.genai import types
def generate_video_from_image(
image_path: str,
motion_prompt: str,
output_video_path: str = "output_cinematic.mp4",
model: str = "veo-3.1-generate-preview" # or "gemini-omni-flash-preview"
):
Feeds the static image into an AI video generator from image.
Uses async polling for production readiness.
print(f"Animating image with motion prompt: '{motion_prompt}'...")
# Load local image
image = types.Image.from_file(image_path)
# Submit async video generation job
operation = client.models.generate_videos(
model=model,
prompt=motion_prompt,
image=image,
config=types.GenerateVideosConfig(
duration_seconds=5,
aspect_ratio="16:9",
number_of_videos=1,
enhance_prompt=True,
),
)
print(f"Video job submitted. Polling for completion...")
# Poll until done
while not operation.done:
time.sleep(5)
operation = client.operations.get(operation)
if operation.response:
video_uri = operation.result.generated_videos[0].video.uri
print(f"✓ Video generated: {video_uri}")
# Download if GCS URI
if video_uri.startswith("gs://"):
from google.cloud import storage
client_gcs = storage.Client()
bucket_name, blob_name = video_uri.replace("gs://", "").split("/", 1)
bucket = client_gcs.bucket(bucket_name)
blob = bucket.blob(blob_name)
blob.download_to_filename(output_video_path)
print(f"✓ Video saved to {output_video_path}")
else:
# For Omni Flash (direct URL)
import requests
resp = requests.get(video_uri)
with open(output_video_path, "wb") as f:
f.write(resp.content)
print(f"✓ Video saved to {output_video_path}")
else:
raise RuntimeError(f"Video generation failed: {operation.error}")
3. Full Pipeline Example
if __name__ == "__main__":
base_prompt = (
"A futuristic smart watch floating slightly above a sleek dark marble surface. "
"The watch screen is glowing blue and displays legible text 'VIBE 2026'. "
"Cyberpunk ambient lighting with neon reflection."
)
motion_prompt = (
"A slow, steady orbital pan tracking right. Subtle dust particles in the air, "
"natural light refraction across the metal casing of the watch."
)
try:
img_path = generate_static_asset(base_prompt)
generate_video_from_image(
img_path,
motion_prompt,
model="veo-3.1-generate-preview" # or "gemini-omni-flash-preview"
)
except Exception as e:
print(f"An error occurred: {e}")
Cost & Limits (as of August 2026)
| Model | Status | Max Duration | Resolution | Est. Cost |
|---|---|---|---|---|
gemini-3.1-flash-image |
GA | N/A | Up to 4K | ~$0.02–0.05/image |
veo-3.1-generate-preview |
Preview | 10 sec | 720p/1080p | ~$0.11/sec |
gemini-omni-flash-preview |
Preview | 10 sec | 720p | ~$0.11/sec |
Common Pitfalls & How to Avoid Them
- Aspect Ratio Mismatch: Ensure your Nano Banana image uses the same aspect ratio (e.g., 16:9) as your video config to avoid cropping.
- Prompt Vagueness: Avoid "make it cool". Use director-level terms: "slow handheld drift", "lens flare from top-left", "dust particles in volumetric light".
- Async Timeouts: Add a max retry count (e.g., 60 polls × 5 sec = 5 min) to avoid infinite loops.
-
GCS Permissions: If using Veo, ensure your service account has
storage.objects.geton the output bucket.
What's Next?
- Batch Generation: Wrap the pipeline in a loop to test 10–20 prompt variations in parallel.
- A/B Testing: Compare "Nano Banana + Veo" vs. "Veo direct text-to-video" for consistency and cost.
- Audio Sync: Use Gemini Omni Flash's audio generation (preview) to add background music or SFX.
Join the Discussion
Have you tried this pipeline? What's your experience with Nano Banana for image generation or ai video generator from image workflows? Share your results, failures, or alternative architectures in the comments! 👇

Top comments (0)