164 lines
6.4 KiB
Python
164 lines
6.4 KiB
Python
"""
|
|
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.")
|