""" Full hero reel regeneration — 7-shot narrative arc. 1. Door opens, muddy boots run in 2. Mud tracked across carpet 3. Stain on upholstered chair 4. Carpet cleaning machine extracting dirt 5. Clean bright staircase 6. Office building wide carpet 7. Restaurant with carpet """ import os, sys, time, subprocess try: from google import genai from google.genai import types except ImportError: os.system(f"{sys.executable} -m pip install google-genai --quiet") from google import genai from google.genai import types API_KEY = os.environ.get("GEMINI_API_KEY", "AIzaSyB_1p8KvaT_rdNJGPs8HKk8bKsvUlcL6Kg") BASE_DIR = os.path.dirname(os.path.dirname(__file__)) VID_DIR = os.path.join(BASE_DIR, "assets", "videos", "hero", "clips") REEL_OUT = os.path.join(BASE_DIR, "assets", "videos", "hero", "hero-reel.mp4") os.makedirs(VID_DIR, exist_ok=True) client = genai.Client(api_key=API_KEY) SHOTS = [ { "name": "v2-shot-01-door-entry", "prompt": ( "Cinematic slow-motion wide shot. A wooden front door of an upstate New York home swings open. " "A child and an adult walk inside wearing muddy boots. Camera stays low at floor level. " "The boots leave dark muddy tracks across the beige carpet in the entryway with each step. " "Warm afternoon light pours through the open door. Photorealistic." ), }, { "name": "v2-shot-02-mud-on-carpet", "prompt": ( "Extreme close-up slow-motion shot at carpet level. Muddy boot soles press into clean beige carpet, " "leaving dark brown mud stains and wet footprints with each step. " "Camera is low, tight on the boots and the mud soaking into carpet fibers. " "Dramatic side lighting. No faces visible. Photorealistic." ), }, { "name": "v2-shot-03-stain-on-chair", "prompt": ( "Close-up cinematic shot of a light grey upholstered armchair. " "A visible dark stain spreads across one cushion. " "Camera slowly pushes in on the stain, showing the soiled fabric texture. " "Warm natural light from a window. No people. No equipment. Photorealistic." ), }, { "name": "v2-shot-04-extraction-carpet", "prompt": ( "Cinematic slow-motion wide shot. A technician pushes a Rug Doctor style carpet cleaning machine " "steadily forward across a beige living room carpet. The machine is a tall upright unit with a handle " "and flat rectangular cleaning head — like a large upright vacuum cleaner. " "The carpet behind the machine is visibly brighter and cleaner than the carpet ahead of it. " "No steam. No water spraying. Warm room light. Photorealistic." ), }, { "name": "v2-shot-05-clean-stairs", "prompt": ( "Cinematic slow-motion shot looking up a bright residential carpeted staircase. " "Each step has clean, fresh, plush beige carpet. Warm natural light from above. " "Wood banisters on the sides. The carpet looks spotless and freshly cleaned. " "No people. No machines. Photorealistic." ), }, { "name": "v2-shot-06-office", "prompt": ( "Wide cinematic shot of a bright modern commercial office lobby. " "Large windows, abundant natural daylight. Clean grey commercial carpet covers the entire floor. " "White walls, glass partitions, professional lighting. Carpet looks spotlessly clean. " "No people. No machines. Photorealistic." ), }, { "name": "v2-shot-07-restaurant", "prompt": ( "Wide cinematic shot of an upscale restaurant dining room with carpeted floors. " "Warm ambient lighting, white tablecloths, wood accents. " "The carpet is clean, rich, and well-maintained throughout the space. " "No people. No machines. Photorealistic, luxurious atmosphere." ), }, ] MODEL = "veo-3.1-generate-preview" def poll(op, timeout=600): elapsed = 0 while not op.done: if elapsed >= timeout: return None print(f" Waiting... ({elapsed}s)") time.sleep(15) elapsed += 15 op = client.operations.get(op) return op saved = [] for item in SHOTS: out_path = os.path.join(VID_DIR, f"{item['name']}.mp4") print(f"\n[{SHOTS.index(item)+1}/{len(SHOTS)}] {item['name']}...") try: op = client.models.generate_videos( model=MODEL, prompt=item["prompt"], config=types.GenerateVideosConfig( aspect_ratio="16:9", resolution="720p", duration_seconds=6, number_of_videos=1, ), ) op = poll(op) if op and op.response and op.response.generated_videos: vid = op.response.generated_videos[0].video video_bytes = client.files.download(file=vid) if video_bytes: with open(out_path, "wb") as f: f.write(video_bytes) print(f" Saved ({os.path.getsize(out_path)//1024}KB)") saved.append(out_path) else: try: vid.save(out_path) print(f" Saved via .save()") saved.append(out_path) except Exception as e2: print(f" Download failed: {e2}") else: print(f" No video returned") except Exception as e: print(f" Error: {e}") print(f"\n{len(saved)}/{len(SHOTS)} shots saved") if len(saved) < 2: print("Not enough clips — skipping reconcat.") else: order = [os.path.join(VID_DIR, f"{s['name']}.mp4") for s in SHOTS if os.path.exists(os.path.join(VID_DIR, f"{s['name']}.mp4"))] concat_file = os.path.join(VID_DIR, "concat-v2.txt") with open(concat_file, "w") as f: for p in order: f.write(f"file '{p}'\n") result = subprocess.run( ["ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", concat_file, "-c:v", "libx264", "-crf", "22", "-preset", "fast", "-movflags", "+faststart", REEL_OUT], capture_output=True, text=True ) if result.returncode == 0: print(f" Reel saved ({os.path.getsize(REEL_OUT)//1024}KB)") else: print(f" ffmpeg error: {result.stderr[-300:]}") print("\nDone.")