backup
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
"""
|
||||
Lahr Carpet Cleaning — Veo hero video generator.
|
||||
5 shots x 4s = 20s reel. Concatenated by ffmpeg into hero-reel.mp4.
|
||||
Saves clips to: assets/videos/hero/clips/
|
||||
Saves final to: assets/videos/hero/hero-reel.mp4
|
||||
Run: python3 tools/gen-video.py
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
from google import genai
|
||||
from google.genai import types
|
||||
except ImportError:
|
||||
print("Installing google-genai...")
|
||||
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__))
|
||||
OUT_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(OUT_DIR, exist_ok=True)
|
||||
|
||||
client = genai.Client(api_key=API_KEY)
|
||||
|
||||
SHOTS = [
|
||||
{
|
||||
"name": "shot-01-door-opens",
|
||||
"prompt": (
|
||||
"Cinematic low-angle wide shot. A solid wood front door of an upstate New York home opens "
|
||||
"inward smoothly. Bright golden afternoon sunlight pours through the doorway onto a carpeted "
|
||||
"entryway floor. Camera is at floor level, looking toward the door. The door swings open "
|
||||
"fully revealing light. No people visible. Photorealistic, warm inviting light, slow motion."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "shot-02-pan-to-stains",
|
||||
"prompt": (
|
||||
"Slow cinematic camera pan from the front door entryway across a residential living room carpet "
|
||||
"in an upstate New York home. The carpet shows visible dirt tracks, pet stains, and soiling "
|
||||
"from daily use. Natural light. No people. Camera moves fluidly across the room revealing "
|
||||
"the stained carpet. Photorealistic."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "shot-03-stain-closeup",
|
||||
"prompt": (
|
||||
"Close-up shot of a stained beige carpet with visible pet stains, mud, and dark soiling. "
|
||||
"Camera slowly pushes in on the dirty area. Dramatic side lighting emphasises the stain depth "
|
||||
"and texture. Slow motion. Ultra-realistic macro photography style."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "shot-04-extraction-carpet",
|
||||
"prompt": (
|
||||
"Cinematic slow-motion wide shot: a large industrial stand-up hot water extraction machine "
|
||||
"being pushed steadily forward across a beige residential carpet. The machine is a tall "
|
||||
"professional-grade upright extractor — heavy-duty, commercial size, on wheels, with a wide "
|
||||
"cleaning head at the base and an upright handle. No steam, no spraying water, no visible "
|
||||
"liquid anywhere on the machine exterior. The carpet behind the machine transitions from dirty "
|
||||
"and matted to bright, clean, and fluffy as it passes. Warm natural room light. Photorealistic."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "shot-05-extraction-couch",
|
||||
"prompt": (
|
||||
"Close-up cinematic shot of a professional technician's gloved hand holding a small flat "
|
||||
"upholstery cleaning attachment tool, pressing it firmly against a dirty grey sofa cushion "
|
||||
"and sliding it slowly across the fabric. The fabric visibly brightens and lifts as the tool "
|
||||
"moves. No water pours out — suction draws moisture into the tool. Slow motion, natural light. "
|
||||
"Photorealistic."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "shot-06-extraction-stairs",
|
||||
"prompt": (
|
||||
"Cinematic shot of a professional technician's hands using a compact portable upright carpet "
|
||||
"cleaner on a carpeted staircase — pushing the machine up a stair tread step by step. Each "
|
||||
"tread brightens and looks freshly cleaned as the machine passes. No water pours out. Clean "
|
||||
"bright carpet revealed on each step. Slow motion, warm interior light. Photorealistic."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "shot-07-office-entryway",
|
||||
"prompt": (
|
||||
"Wide cinematic shot of a clean professional office building entryway with commercial grade "
|
||||
"carpet. Modern corporate interior, glass doors, professional lighting. No people. Camera "
|
||||
"slowly pushes forward through the entry. Photorealistic."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "shot-08-showroom",
|
||||
"prompt": (
|
||||
"Wide cinematic shot of an upscale retail showroom or winery tasting room in the Finger Lakes "
|
||||
"region. Rich carpet throughout, warm interior lighting, product displays. No people. Camera "
|
||||
"glides forward through the space. Photorealistic, luxurious atmosphere."
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "shot-09-technician-unloading",
|
||||
"prompt": (
|
||||
"Wide shot of a professional carpet cleaning technician wearing a plain black shirt with no logo, "
|
||||
"rolling a large industrial stand-up hot water extraction machine out of a white service van "
|
||||
"parked in a residential driveway in upstate New York. The machine is a heavy commercial-grade "
|
||||
"upright extractor on wheels — tall, industrial size. Autumn trees in background, bright day. "
|
||||
"Technician shown from side or behind, no face visible. Photorealistic."
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
MODELS = [
|
||||
"veo-2.0-generate-001",
|
||||
"veo-3.0-generate-001",
|
||||
]
|
||||
|
||||
def poll(operation, timeout=420):
|
||||
elapsed = 0
|
||||
while not operation.done:
|
||||
if elapsed >= timeout:
|
||||
print(" Timed out.")
|
||||
return None
|
||||
print(f" Waiting... ({elapsed}s)")
|
||||
time.sleep(15)
|
||||
elapsed += 15
|
||||
operation = client.operations.get(operation)
|
||||
return operation
|
||||
|
||||
def download_video(video, out_path):
|
||||
video_bytes = None
|
||||
try:
|
||||
video_bytes = client.files.download(file=video)
|
||||
except Exception:
|
||||
pass
|
||||
if video_bytes:
|
||||
with open(out_path, "wb") as f:
|
||||
f.write(video_bytes)
|
||||
return True
|
||||
if hasattr(video, "uri") and video.uri:
|
||||
import urllib.request
|
||||
uri = video.uri + ("&" if "?" in video.uri else "?") + f"key={API_KEY}"
|
||||
print(f" Fetching via URI...")
|
||||
urllib.request.urlretrieve(uri, out_path)
|
||||
return True
|
||||
return False
|
||||
|
||||
def generate():
|
||||
saved = []
|
||||
for item in SHOTS:
|
||||
out_path = os.path.join(OUT_DIR, f"{item['name']}.mp4")
|
||||
print(f"\n[{SHOTS.index(item)+1}/{len(SHOTS)}] Generating {item['name']}...")
|
||||
|
||||
done = False
|
||||
for model in MODELS:
|
||||
try:
|
||||
print(f" Model: {model}")
|
||||
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 is None:
|
||||
continue
|
||||
if op.response and op.response.generated_videos:
|
||||
vid = op.response.generated_videos[0].video
|
||||
if download_video(vid, out_path):
|
||||
size_kb = os.path.getsize(out_path) // 1024
|
||||
print(f" Saved {out_path} ({size_kb}KB)")
|
||||
saved.append(out_path)
|
||||
done = True
|
||||
break
|
||||
else:
|
||||
print(f" Download failed for {model}")
|
||||
else:
|
||||
print(f" No video from {model}")
|
||||
except Exception as e:
|
||||
print(f" Error with {model}: {e}")
|
||||
|
||||
if not done:
|
||||
print(f" FAILED: {item['name']}")
|
||||
|
||||
return saved
|
||||
|
||||
def concat(clips):
|
||||
if len(clips) < 2:
|
||||
print("Not enough clips to concatenate.")
|
||||
return
|
||||
list_file = os.path.join(OUT_DIR, "concat.txt")
|
||||
with open(list_file, "w") as f:
|
||||
for c in clips:
|
||||
f.write(f"file '{c}'\n")
|
||||
print(f"\nConcatenating {len(clips)} clips into hero-reel.mp4...")
|
||||
result = subprocess.run(
|
||||
["ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", list_file,
|
||||
"-c:v", "libx264", "-crf", "22", "-preset", "fast",
|
||||
"-movflags", "+faststart", REEL_OUT],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
size_kb = os.path.getsize(REEL_OUT) // 1024
|
||||
print(f" Saved {REEL_OUT} ({size_kb}KB)")
|
||||
else:
|
||||
print(f" ffmpeg error: {result.stderr[-300:]}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
clips = generate()
|
||||
if clips:
|
||||
concat(clips)
|
||||
print(f"\nDone. {len(clips)}/5 clips generated.")
|
||||
if len(clips) == 5:
|
||||
print("Hero reel ready: assets/videos/hero/hero-reel.mp4")
|
||||
Reference in New Issue
Block a user