updated images with ai images

This commit is contained in:
Concept Agent
2026-05-16 19:45:58 +02:00
parent 18193f88fc
commit 581b94eb0c
91 changed files with 960 additions and 2046 deletions
+1 -1
View File
@@ -7,7 +7,6 @@ deployment:
- /bin/cp -r commercial "$DEPLOYPATH"
- /bin/cp -r contact "$DEPLOYPATH"
- /bin/cp -r locations "$DEPLOYPATH"
- /bin/cp -r our-work "$DEPLOYPATH"
- /bin/cp -r reviews "$DEPLOYPATH"
- /bin/cp -r service-area "$DEPLOYPATH"
- /bin/cp -r services "$DEPLOYPATH"
@@ -16,3 +15,4 @@ deployment:
- /bin/cp 500.html "$DEPLOYPATH"
- /bin/cp robots.txt "$DEPLOYPATH"
- /bin/cp sitemap.xml "$DEPLOYPATH"
- /bin/cp .htaccess "$DEPLOYPATH"
+2
View File
@@ -4,3 +4,5 @@ node_modules/
build/
.DS_Store
*.log
__pycache__/
*.pyc
+14
View File
@@ -0,0 +1,14 @@
Options -Indexes
RewriteEngine On
# Deny sensitive files
<FilesMatch "\.(py|yml|yaml|md|log|sh|env|conf|dockerfile)$">
Order allow,deny
Deny from all
</FilesMatch>
# Deny tools directory
RewriteRule ^tools/ - [F,L]
ErrorDocument 404 /404.html
ErrorDocument 500 /500.html
Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-244
View File
@@ -1,244 +0,0 @@
"""
Generate unique hero images for every page via ComfyUI SDXL.
No people. No machines. Residential = warm home scenes. Commercial = professional spaces.
Run in tmux: python3 tools/gen-hero-images.py
"""
import json, time, urllib.request, os, random
COMFY = "http://localhost:8188"
OUT_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "images", "hero")
CKPT = "sd_xl_base_1.0.safetensors"
NEG = (
"people, person, human, hands, feet, boots, shoes, worker, cleaner, technician, "
"machine, vacuum cleaner, equipment, steam, city skyline, apartment building exterior, "
"urban, dirty, stain, text, watermark, logo, blurry, low quality, cartoon, anime, "
"illustration, render, CGI"
)
IMAGES = [
{
"filename": "hero-carpet-cleaning.jpg",
"positive": (
"wide shot of a bright residential living room in an upstate New York home, "
"thick plush beige carpet throughout, warm afternoon sunlight through large windows, "
"comfortable couch and coffee table, Finger Lakes farmhouse style interior, "
"no people, ultra-realistic architectural photography, 16:9 wide"
),
},
{
"filename": "hero-stairs.jpg",
"positive": (
"wide shot of a clean carpeted staircase inside a suburban American home, "
"light grey carpet runner on dark wood stairs, white painted banister, "
"bright natural light from above, no people, no equipment, "
"ultra-realistic interior photography, 16:9"
),
},
{
"filename": "hero-upholstery.jpg",
"positive": (
"bright cozy living room in a residential home, large comfortable fabric sofa "
"in warm neutral tones, clean armchair beside it, plush carpet beneath, "
"afternoon light, Finger Lakes countryside visible through window, "
"no people, ultra-realistic interior photography, 16:9"
),
},
{
"filename": "hero-floors.jpg",
"positive": (
"wide shot of a clean hardwood floor hallway in a spacious suburban American home, "
"light oak floors gleaming, white walls, natural light streaming through windows, "
"no people, no equipment, ultra-realistic interior photography, 16:9"
),
},
{
"filename": "hero-area-rugs.jpg",
"positive": (
"beautiful oriental area rug centered in a bright residential living room, "
"rich warm tones of deep red and gold, hardwood floor surrounding the rug, "
"cozy farmhouse interior, natural light, Finger Lakes region home decor, "
"no people, ultra-realistic interior photography, 16:9"
),
},
{
"filename": "hero-add-ons.jpg",
"positive": (
"bright clean residential bedroom interior, freshly cleaned light beige carpet, "
"white walls, large window with sheer curtains, simple wooden bed frame, "
"crisp natural morning light, Finger Lakes home style, "
"no people, no machines, ultra-realistic interior photography, 16:9"
),
},
{
"filename": "hero-commercial.jpg",
"positive": (
"wide shot of a modern commercial office building lobby interior, "
"clean dark grey commercial carpet throughout, professional corporate space, "
"glass entrance doors, white walls, recessed lighting, "
"no people, no equipment, architectural photography, ultra-realistic, 16:9"
),
},
{
"filename": "hero-offices.jpg",
"positive": (
"modern open-plan corporate office interior, clean grey carpet tiles, "
"rows of empty desks, glass partitions, professional overhead lighting, "
"large windows with daylight, no people, no equipment, "
"architectural photography, ultra-realistic, 16:9"
),
},
{
"filename": "hero-vacation-rentals.jpg",
"positive": (
"bright cozy vacation rental cottage living room interior, Finger Lakes region style, "
"clean beige carpet, wooden ceiling beams, stone fireplace, comfortable furniture, "
"large window with lake view in distance, warm inviting atmosphere, "
"no people, ultra-realistic interior photography, 16:9"
),
},
{
"filename": "hero-hotels.jpg",
"positive": (
"long elegant hotel corridor interior, clean patterned burgundy carpet runner, "
"warm wall sconce lighting along white walls, numbered wooden room doors, "
"soft warm glow, upscale hospitality interior, no people, no equipment, "
"professional architectural photography, ultra-realistic, 16:9"
),
},
{
"filename": "hero-retail.jpg",
"positive": (
"upscale retail showroom interior, clean light grey carpet flooring, "
"modern minimalist display fixtures, bright track lighting overhead, "
"white walls, large storefront windows with natural light, "
"no people, no equipment, architectural photography, ultra-realistic, 16:9"
),
},
{
"filename": "hero-property-management.jpg",
"positive": (
"clean move-in ready apartment unit living room, fresh neutral carpet, "
"white walls, bright windows, empty space ready for tenants, "
"no furniture, no people, no equipment, "
"real estate photography style, ultra-realistic, 16:9"
),
},
{
"filename": "hero-about.jpg",
"positive": (
"warm exterior view of a classic upstate New York suburban home, "
"green lawn, mature trees, clear blue sky, Finger Lakes region, "
"inviting residential property, no people, no vehicles, "
"professional real estate photography, ultra-realistic, 16:9"
),
},
{
"filename": "hero-service-area.jpg",
"positive": (
"scenic Finger Lakes region landscape, rolling green hills, vineyards in distance, "
"calm lake water reflecting sky, late afternoon golden hour light, "
"upstate New York countryside, no people, no vehicles, "
"professional landscape photography, ultra-realistic, 16:9"
),
},
{
"filename": "hero-living-room.jpg",
"positive": (
"spacious bright residential living room in a Finger Lakes area home, "
"plush clean light grey carpet, white walls, large sectional sofa, "
"afternoon sunlight through bay windows, warm cozy family atmosphere, "
"no people, ultra-realistic interior photography, 16:9"
),
},
{
"filename": "hero-clean-result.jpg",
"positive": (
"close-up wide angle of immaculate freshly cleaned residential carpet, "
"uniform plush beige pile, bright natural light raking across the surface, "
"showing deep clean texture, no people, no machines, "
"ultra-realistic macro photography, 16:9"
),
},
]
def build_workflow(positive, seed=None):
if seed is None:
seed = random.randint(0, 2**32)
return {
"3": {"class_type": "KSampler", "inputs": {
"cfg": 7.5, "denoise": 1.0,
"latent_image": ["5", 0], "model": ["4", 0],
"negative": ["7", 0], "positive": ["6", 0],
"sampler_name": "dpmpp_2m", "scheduler": "karras",
"seed": seed, "steps": 30,
}},
"4": {"class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": CKPT}},
"5": {"class_type": "EmptyLatentImage", "inputs": {"batch_size": 1, "height": 576, "width": 1024}},
"6": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["4", 1], "text": positive}},
"7": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["4", 1], "text": NEG}},
"8": {"class_type": "VAEDecode", "inputs": {"samples": ["3", 0], "vae": ["4", 2]}},
"9": {"class_type": "SaveImage", "inputs": {"filename_prefix": "hero_gen", "images": ["8", 0]}},
}
def queue_prompt(workflow):
data = json.dumps({"prompt": workflow}).encode()
req = urllib.request.Request(f"{COMFY}/prompt", data=data,
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())["prompt_id"]
def wait_for_result(prompt_id, timeout=900):
start = time.time()
while time.time() - start < timeout:
try:
with urllib.request.urlopen(f"{COMFY}/history/{prompt_id}") as resp:
hist = json.loads(resp.read())
if prompt_id in hist:
outputs = hist[prompt_id].get("outputs", {})
for node_out in outputs.values():
if "images" in node_out:
return node_out["images"]
except Exception:
pass
time.sleep(5)
return None
def download_image(img_info, out_path):
fname = img_info["filename"]
subfolder = img_info.get("subfolder", "")
img_type = img_info.get("type", "output")
url = f"{COMFY}/view?filename={fname}&subfolder={subfolder}&type={img_type}"
with urllib.request.urlopen(url) as resp:
data = resp.read()
try:
from PIL import Image
import io
img = Image.open(io.BytesIO(data)).convert("RGB")
img.save(out_path, "JPEG", quality=92)
print(f" OK: {os.path.basename(out_path)} ({os.path.getsize(out_path)//1024}KB)", flush=True)
except ImportError:
png_path = out_path.replace(".jpg", ".png")
with open(png_path, "wb") as f:
f.write(data)
print(f" OK (PNG): {png_path}", flush=True)
total = len(IMAGES)
for i, spec in enumerate(IMAGES):
out_path = os.path.join(OUT_DIR, spec["filename"])
print(f"\n[{i+1}/{total}] {spec['filename']}", flush=True)
workflow = build_workflow(spec["positive"])
prompt_id = queue_prompt(workflow)
print(f" queued {prompt_id[:8]}...", flush=True)
images = wait_for_result(prompt_id)
if images:
download_image(images[0], out_path)
else:
print(f" FAILED (timeout)", flush=True)
print("\nAll done.", flush=True)
-186
View File
@@ -1,186 +0,0 @@
"""Generate replacement service images via ComfyUI SDXL (local, no API key needed)."""
import json, time, urllib.request, urllib.error, os, sys
COMFY = "http://localhost:8188"
OUT_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "images", "services")
CKPT = "sd_xl_base_1.0.safetensors"
IMAGES = [
{
"filename": "vacation-rentals.jpg",
"positive": (
"bright cozy vacation rental living room interior, clean beige carpet, "
"comfortable furniture, large windows with natural light, Finger Lakes "
"style decor, warm inviting atmosphere, no people, no equipment, "
"professional interior photography, ultra-realistic"
),
"negative": (
"people, person, human, worker, machine, vacuum, equipment, dirty, stain, "
"text, watermark, blurry, low quality, cartoon, dark"
),
},
{
"filename": "office-spaces.jpg",
"positive": (
"modern corporate office interior, clean dark grey commercial carpet tiles, "
"open plan workspace, white desks, professional lighting, glass partitions, "
"no people, no equipment, architectural photography, ultra-realistic"
),
"negative": (
"people, person, human, worker, machine, vacuum, equipment, dirty, stain, "
"text, watermark, blurry, low quality, cartoon"
),
},
{
"filename": "hotels-inns.jpg",
"positive": (
"elegant hotel corridor interior, clean patterned carpet runner, warm wall "
"sconce lighting, white walls, numbered room doors along hallway, "
"hospitality interior design, no people, no equipment, "
"professional photography, ultra-realistic"
),
"negative": (
"people, person, human, worker, machine, vacuum, equipment, dirty, stain, "
"text, watermark, blurry, low quality, cartoon"
),
},
{
"filename": "retail-showrooms.jpg",
"positive": (
"upscale retail showroom interior, clean light grey carpet flooring, "
"modern display shelving, bright overhead track lighting, white walls, "
"customer-facing professional space, no people, no equipment, "
"architectural photography, ultra-realistic"
),
"negative": (
"people, person, human, worker, machine, vacuum, equipment, dirty, stain, "
"text, watermark, blurry, low quality, cartoon"
),
},
{
"filename": "property-management.jpg",
"positive": (
"clean apartment unit interior, fresh beige carpet throughout living room, "
"neutral walls, bright windows, move-in ready condition, residential "
"property management style, no people, no furniture, no equipment, "
"real estate photography, ultra-realistic"
),
"negative": (
"people, person, human, worker, machine, vacuum, equipment, dirty, stain, "
"text, watermark, blurry, low quality, cartoon"
),
},
]
def build_workflow(positive, negative, seed=None):
import random
if seed is None:
seed = random.randint(0, 2**32)
return {
"3": {
"class_type": "KSampler",
"inputs": {
"cfg": 7.0,
"denoise": 1.0,
"latent_image": ["5", 0],
"model": ["4", 0],
"negative": ["7", 0],
"positive": ["6", 0],
"sampler_name": "euler",
"scheduler": "normal",
"seed": seed,
"steps": 25,
},
},
"4": {
"class_type": "CheckpointLoaderSimple",
"inputs": {"ckpt_name": CKPT},
},
"5": {
"class_type": "EmptyLatentImage",
"inputs": {"batch_size": 1, "height": 768, "width": 1024},
},
"6": {
"class_type": "CLIPTextEncode",
"inputs": {"clip": ["4", 1], "text": positive},
},
"7": {
"class_type": "CLIPTextEncode",
"inputs": {"clip": ["4", 1], "text": negative},
},
"8": {
"class_type": "VAEDecode",
"inputs": {"samples": ["3", 0], "vae": ["4", 2]},
},
"9": {
"class_type": "SaveImage",
"inputs": {"filename_prefix": "lahr_gen", "images": ["8", 0]},
},
}
def queue_prompt(workflow):
data = json.dumps({"prompt": workflow}).encode()
req = urllib.request.Request(
f"{COMFY}/prompt",
data=data,
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())["prompt_id"]
def wait_for_result(prompt_id, timeout=600):
start = time.time()
while time.time() - start < timeout:
try:
with urllib.request.urlopen(f"{COMFY}/history/{prompt_id}") as resp:
hist = json.loads(resp.read())
if prompt_id in hist:
outputs = hist[prompt_id].get("outputs", {})
for node_id, node_out in outputs.items():
if "images" in node_out:
return node_out["images"]
except Exception:
pass
print(" waiting...", flush=True)
time.sleep(5)
return None
def download_image(img_info, out_path):
fname = img_info["filename"]
subfolder = img_info.get("subfolder", "")
img_type = img_info.get("type", "output")
params = f"filename={fname}&subfolder={subfolder}&type={img_type}"
url = f"{COMFY}/view?{params}"
with urllib.request.urlopen(url) as resp:
data = resp.read()
# Convert PNG to JPEG via PIL if available
try:
from PIL import Image
import io
img = Image.open(io.BytesIO(data)).convert("RGB")
img.save(out_path, "JPEG", quality=90)
print(f" Saved JPEG ({len(data)//1024}KB raw -> {os.path.getsize(out_path)//1024}KB)")
except ImportError:
# Save as-is (PNG), rename accordingly
png_path = out_path.replace(".jpg", ".png")
with open(png_path, "wb") as f:
f.write(data)
print(f" Saved PNG (PIL not available): {png_path}")
for spec in IMAGES:
out_path = os.path.join(OUT_DIR, spec["filename"])
print(f"\nGenerating: {spec['filename']}")
workflow = build_workflow(spec["positive"], spec["negative"])
prompt_id = queue_prompt(workflow)
print(f" Queued: {prompt_id}")
images = wait_for_result(prompt_id)
if images:
download_image(images[0], out_path)
else:
print(" FAILED: no output after timeout")
print("\nDone.")
+292
View File
@@ -0,0 +1,292 @@
"""
Generate all site images via FLUX.1 Schnell GGUF through ComfyUI.
FLUX Schnell: 4 steps, cfg=1.0, no negative prompt, photorealistic.
Run after ComfyUI restart: python3 tools/gen-images-flux.py
"""
import json, time, urllib.request, os, random, io
COMFY = "http://localhost:8188"
HERO_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "images", "hero")
SVC_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "images", "services")
IMAGES = [
# --- HERO IMAGES ---
{"filename": "hero-carpet-cleaning.jpg", "dir": HERO_DIR, "prompt": (
"low-angle 35mm lens perspective looking across thick plush cream carpet in an upstate New York living room, "
"carpet fibers razor sharp in foreground, couch and coffee table receding into shallow bokeh background, "
"warm afternoon window light raking across carpet texture, Finger Lakes farmhouse interior, "
"no people, ultra-realistic architectural photography, 16:9"
)},
{"filename": "hero-stairs.jpg", "dir": HERO_DIR, "prompt": (
"dramatic low 35mm angle looking up a clean carpeted staircase from floor level, "
"light grey carpet runner sharp and textured in foreground steps, wood banister receding diagonally, "
"bright daylight flooding from above, shallow depth of field, "
"no people, ultra-realistic interior photography, 16:9"
)},
{"filename": "hero-upholstery.jpg", "dir": HERO_DIR, "prompt": (
"50mm lens low corner angle across a bright residential living room, "
"plush linen fabric sofa arm sharp in near foreground, clean armchair and window receding with bokeh, "
"afternoon countryside light through window, shallow depth of field, "
"no people, ultra-realistic interior photography, 16:9"
)},
{"filename": "hero-floors.jpg", "dir": HERO_DIR, "prompt": (
"low 24mm angle pressed to gleaming light oak hardwood floor, "
"floor grain razor sharp in extreme foreground receding to hallway vanishing point, "
"white walls, natural light streaming in, shallow depth of field, "
"no people, ultra-realistic interior photography, 16:9"
)},
{"filename": "hero-area-rugs.jpg", "dir": HERO_DIR, "prompt": (
"low 35mm angle looking across a hand-knotted oriental rug from floor level, "
"rich red and gold rug fibers sharp in foreground, hardwood floor and room receding into bokeh, "
"cozy farmhouse living room, warm natural light, shallow depth of field, "
"no people, ultra-realistic interior photography, 16:9"
)},
{"filename": "hero-add-ons.jpg", "dir": HERO_DIR, "prompt": (
"low 35mm angle across a clean beige bedroom carpet, "
"carpet pile sharp and detailed in near foreground, wooden bed frame and sheer curtained window receding, "
"crisp morning light, shallow depth of field, "
"no people, no machines, ultra-realistic interior photography, 16:9"
)},
{"filename": "hero-commercial.jpg", "dir": HERO_DIR, "prompt": (
"low 24mm wide-angle lens across a modern corporate lobby floor, "
"dark charcoal commercial carpet sharp in extreme foreground receding to glass entrance doors, "
"recessed ceiling lights creating depth, strong vanishing point perspective, "
"no people, ultra-realistic architectural photography, 16:9"
)},
{"filename": "hero-offices.jpg", "dir": HERO_DIR, "prompt": (
"low 24mm angle across clean grey carpet tiles in a modern open-plan office, "
"carpet tile seams sharp in foreground receding to rows of empty desks and glass partitions, "
"professional overhead lighting, strong linear perspective, "
"no people, ultra-realistic architectural photography, 16:9"
)},
{"filename": "hero-vacation-rentals.jpg", "dir": HERO_DIR, "prompt": (
"low 35mm angle across clean beige carpet in a Finger Lakes cottage living room, "
"carpet fibers sharp in foreground, stone fireplace and lake-view window receding with bokeh, "
"wooden ceiling beams, warm inviting light, shallow depth of field, "
"no people, ultra-realistic interior photography, 16:9"
)},
{"filename": "hero-hotels.jpg", "dir": HERO_DIR, "prompt": (
"low 24mm lens looking down a long hotel corridor from floor level, "
"patterned burgundy carpet runner sharp in extreme foreground receding to vanishing point, "
"warm wall sconces lining white walls, numbered doors converging in perspective, "
"no people, ultra-realistic hospitality photography, 16:9"
)},
{"filename": "hero-retail.jpg", "dir": HERO_DIR, "prompt": (
"low 35mm diagonal angle across clean light grey carpet in an upscale retail showroom, "
"carpet surface sharp in foreground, minimalist display fixtures and storefront windows receding with bokeh, "
"bright track lighting overhead, shallow depth of field, "
"no people, ultra-realistic architectural photography, 16:9"
)},
{"filename": "hero-property-management.jpg", "dir": HERO_DIR, "prompt": (
"low 35mm angle across fresh neutral carpet in an empty move-in ready apartment, "
"carpet texture sharp in foreground, bare white walls and bright windows receding, "
"clean real estate photography perspective, shallow depth of field, "
"no people, ultra-realistic real estate photography, 16:9"
)},
{"filename": "hero-about.jpg", "dir": HERO_DIR, "prompt": (
"low 35mm angle from lawn level looking up at a classic upstate New York suburban home, "
"green grass blades sharp in extreme foreground, inviting house facade receding upward, "
"mature trees and clear blue sky, warm summer afternoon, "
"no people, ultra-realistic real estate photography, 16:9"
)},
{"filename": "hero-service-area.jpg", "dir": HERO_DIR, "prompt": (
"low horizon 24mm wide-angle Finger Lakes landscape, "
"green vineyard vines sharp in foreground receding to rolling hills and calm lake, "
"golden hour light casting long shadows, strong depth and distance, "
"no people, ultra-realistic landscape photography, 16:9"
)},
{"filename": "hero-living-room.jpg", "dir": HERO_DIR, "prompt": (
"low 35mm corner angle across a spacious residential living room, "
"plush light grey carpet sharp and textured in foreground, large sectional sofa and bay windows receding with bokeh, "
"warm afternoon sunlight, shallow depth of field, "
"no people, ultra-realistic interior photography, 16:9"
)},
{"filename": "hero-clean-result.jpg", "dir": HERO_DIR, "prompt": (
"extreme low 50mm macro angle pressed to immaculate freshly cleaned residential carpet, "
"individual carpet fibers razor sharp in foreground, pile receding into soft bokeh, "
"raking natural light revealing deep clean texture and uniform pile height, "
"no people, ultra-realistic macro carpet photography, 16:9"
)},
# --- SERVICE CARD IMAGES ---
{"filename": "carpet-cleaning.jpg", "dir": SVC_DIR, "prompt": (
"low 35mm angle looking across plush clean beige carpet in a residential living room, "
"carpet fibers sharp in foreground, couch and window receding into bokeh, "
"warm afternoon light, shallow depth of field, no people, ultra-realistic interior photography"
)},
{"filename": "stairs-cleaning.jpg", "dir": SVC_DIR, "prompt": (
"low 35mm angle looking up clean grey carpeted stairs from bottom step, "
"carpet texture sharp on nearest step, stairs receding diagonally upward, "
"wood banister, bright light from above, no people, ultra-realistic interior photography"
)},
{"filename": "upholstery-cleaning.jpg", "dir": SVC_DIR, "prompt": (
"low 50mm angle across a clean plush linen fabric sofa arm, "
"fabric weave sharp in foreground, living room receding with bokeh, "
"warm light, shallow depth of field, no people, ultra-realistic interior photography"
)},
{"filename": "floor-cleaning.jpg", "dir": SVC_DIR, "prompt": (
"low 24mm angle pressed to gleaming light oak hardwood floor, "
"wood grain razor sharp in extreme foreground receding down hallway, "
"natural light, no people, ultra-realistic interior photography"
)},
{"filename": "area-rug-cleaning.jpg", "dir": SVC_DIR, "prompt": (
"low 35mm angle across a vibrant clean oriental rug from floor level, "
"rug fibers and pattern sharp in foreground, hardwood floor and room receding, "
"warm light, shallow depth of field, no people, ultra-realistic interior photography"
)},
{"filename": "add-ons.jpg", "dir": SVC_DIR, "prompt": (
"low 35mm angle across clean beige bedroom carpet, "
"carpet pile sharp in foreground, bed frame and curtained window receding with bokeh, "
"morning light, no people, ultra-realistic interior photography"
)},
{"filename": "commercial-overview.jpg", "dir": SVC_DIR, "prompt": (
"low 24mm angle across dark commercial carpet in a corporate lobby, "
"carpet surface sharp in foreground receding to glass entrance, "
"strong vanishing point, no people, ultra-realistic architectural photography"
)},
{"filename": "vacation-rentals.jpg", "dir": SVC_DIR, "prompt": (
"low 35mm angle across clean carpet in a Finger Lakes cottage living room, "
"carpet sharp in foreground, stone fireplace and window receding with bokeh, "
"rustic warm decor, no people, ultra-realistic interior photography"
)},
{"filename": "office-spaces.jpg", "dir": SVC_DIR, "prompt": (
"low 24mm angle across grey carpet tiles in a modern open office, "
"tile seams sharp in foreground, empty desks receding with linear perspective, "
"professional lighting, no people, ultra-realistic architectural photography"
)},
{"filename": "hotels-inns.jpg", "dir": SVC_DIR, "prompt": (
"low 24mm angle down a hotel corridor, patterned carpet runner sharp in foreground, "
"corridor receding to vanishing point, warm wall sconces, "
"no people, ultra-realistic hospitality photography"
)},
{"filename": "retail-showrooms.jpg", "dir": SVC_DIR, "prompt": (
"low 35mm diagonal angle across light grey carpet in an upscale retail showroom, "
"carpet sharp in foreground, display fixtures and track lighting receding with bokeh, "
"no people, ultra-realistic architectural photography"
)},
{"filename": "property-management.jpg", "dir": SVC_DIR, "prompt": (
"low 35mm angle across fresh neutral carpet in an empty clean apartment, "
"carpet texture sharp in foreground, white walls and windows receding, "
"no people, ultra-realistic real estate photography"
)},
]
def build_workflow(prompt, seed=None):
if seed is None:
seed = random.randint(0, 2**32)
return {
"1": {
"class_type": "UnetLoaderGGUF",
"inputs": {"unet_name": "flux1-schnell-Q8_0.gguf"},
},
"2": {
"class_type": "DualCLIPLoader",
"inputs": {
"clip_name1": "t5xxl_fp8_e4m3fn.safetensors",
"clip_name2": "clip_l.safetensors",
"type": "flux",
},
},
"3": {
"class_type": "VAELoader",
"inputs": {"vae_name": "ae.safetensors"},
},
"4": {
"class_type": "CLIPTextEncode",
"inputs": {"clip": ["2", 0], "text": prompt},
},
"5": {
"class_type": "EmptyLatentImage",
"inputs": {"batch_size": 1, "height": 576, "width": 1024},
},
"6": {
"class_type": "KSampler",
"inputs": {
"cfg": 1.0,
"denoise": 1.0,
"latent_image": ["5", 0],
"model": ["1", 0],
"negative": ["4", 0],
"positive": ["4", 0],
"sampler_name": "euler",
"scheduler": "simple",
"seed": seed,
"steps": 4,
},
},
"7": {
"class_type": "VAEDecode",
"inputs": {"samples": ["6", 0], "vae": ["3", 0]},
},
"8": {
"class_type": "SaveImage",
"inputs": {"filename_prefix": "flux_lahr", "images": ["7", 0]},
},
}
def queue_prompt(workflow):
data = json.dumps({"prompt": workflow}).encode()
req = urllib.request.Request(
f"{COMFY}/prompt", data=data,
headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())["prompt_id"]
def wait_for_result(prompt_id, timeout=600):
start = time.time()
while time.time() - start < timeout:
try:
with urllib.request.urlopen(f"{COMFY}/history/{prompt_id}") as resp:
hist = json.loads(resp.read())
if prompt_id in hist:
entry = hist[prompt_id]
status = entry.get("status", {}).get("status_str", "")
if status == "error":
msgs = entry.get("status", {}).get("messages", [])
print(f" COMFYUI ERROR: {msgs}", flush=True)
return None
for node_out in entry.get("outputs", {}).values():
if "images" in node_out:
return node_out["images"]
except Exception:
pass
time.sleep(5)
return None
def download_image(img_info, out_path):
fname = img_info["filename"]
subfolder = img_info.get("subfolder", "")
img_type = img_info.get("type", "output")
url = f"{COMFY}/view?filename={fname}&subfolder={subfolder}&type={img_type}"
with urllib.request.urlopen(url) as resp:
data = resp.read()
try:
from PIL import Image
img = Image.open(io.BytesIO(data)).convert("RGB")
img.save(out_path, "JPEG", quality=92)
print(f" OK: {os.path.basename(out_path)} ({os.path.getsize(out_path)//1024}KB)", flush=True)
except ImportError:
png_path = out_path.replace(".jpg", ".png")
with open(png_path, "wb") as f:
f.write(data)
print(f" OK (PNG): {png_path}", flush=True)
total = len(IMAGES)
for i, spec in enumerate(IMAGES):
out_path = os.path.join(spec["dir"], spec["filename"])
print(f"\n[{i+1}/{total}] {spec['filename']}", flush=True)
workflow = build_workflow(spec["prompt"])
prompt_id = queue_prompt(workflow)
print(f" queued {prompt_id[:8]}...", flush=True)
images = wait_for_result(prompt_id)
if images:
download_image(images[0], out_path)
else:
print(f" FAILED (timeout)", flush=True)
print("\nAll done.", flush=True)
-106
View File
@@ -1,106 +0,0 @@
"""
Lahr Carpet Cleaning — Gemini Imagen hero image generator.
Generates: hero living room, cleaning in progress, clean result.
Saves to: assets/images/hero/
Run: python3 tools/gen-images.py
"""
import os
import sys
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")
OUT_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "images", "hero")
os.makedirs(OUT_DIR, exist_ok=True)
client = genai.Client(api_key=API_KEY)
IMAGES = [
{
"name": "hero-living-room",
"prompt": (
"Luxurious modern living room with spotlessly clean cream carpet, "
"natural light streaming through large windows, contemporary furniture, "
"professional interior photography, warm inviting atmosphere, no people, "
"ultra-realistic, 8K quality"
),
"aspect": "16:9",
},
{
"name": "hero-clean-result",
"prompt": (
"Pristine white plush carpet in a beautiful upscale residential home, "
"dramatic before-after transformation, deeply cleaned carpet with "
"vacuum lines visible, natural light, professional photography, no people"
),
"aspect": "16:9",
},
{
"name": "hero-technician",
"prompt": (
"Professional carpet cleaning technician pushing a large upright hot water "
"extraction machine across residential carpet in a bright modern home interior. "
"Machine resembles an oversized upright vacuum cleaner with a cylindrical body. "
"No steam visible anywhere, no water spraying, no hoses visible, completely dry. "
"Technician shown from behind or side, no face, plain black shirt, no logo. "
"High-end professional photography."
),
"aspect": "16:9",
},
{
"name": "hero-before-after",
"prompt": (
"Side-by-side residential living room carpet: left half heavily soiled with mud "
"and dark stains, right half same carpet after professional hot water extraction "
"cleaning, bright and pristine. No steam, no water, no machines visible anywhere. "
"Dramatic before-after contrast. Professional photography, no people."
),
"aspect": "16:9",
},
{
"name": "hero-stairs",
"prompt": (
"Beautiful staircase with freshly cleaned plush carpet on stairs, "
"modern home interior, natural light from above, professional result, "
"no people, architectural photography, high-end residential"
),
"aspect": "3:4",
},
]
def generate():
for item in IMAGES:
out_path = os.path.join(OUT_DIR, f"{item['name']}.jpg")
print(f"Generating {item['name']}...")
try:
resp = client.models.generate_images(
model="imagen-4.0-generate-001",
prompt=item["prompt"],
config=types.GenerateImagesConfig(
number_of_images=1,
aspect_ratio=item["aspect"],
output_mime_type="image/jpeg",
safety_filter_level="block_low_and_above",
),
)
if resp.generated_images:
image_bytes = resp.generated_images[0].image.image_bytes
with open(out_path, "wb") as f:
f.write(image_bytes)
size_kb = len(image_bytes) // 1024
print(f" Saved {out_path} ({size_kb}KB)")
else:
print(f" No image returned for {item['name']}")
except Exception as e:
print(f" Error on {item['name']}: {e}")
if __name__ == "__main__":
generate()
print("\nDone. Images in assets/images/hero/")
-60
View File
@@ -1,60 +0,0 @@
"""Generate the 2 missing service images."""
import os, sys
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")
OUT_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "images", "services")
client = genai.Client(api_key=API_KEY)
TARGETS = [
{
"name": "property-management",
"prompt": (
"View across three clean empty apartment living rooms, each with spotlessly clean "
"beige carpet showing fresh extraction lines after professional hot water extraction cleaning. "
"Bright neutral interiors ready for new tenants. Natural light, no furniture, "
"no people, no equipment. Professional real estate photography, ultra-realistic."
),
},
{
"name": "commercial-overview",
"prompt": (
"Professional carpet cleaning technician in a plain black shirt, shown from the side, "
"pushing a large upright extraction machine through a bright commercial building lobby. "
"Clean bright carpet behind the machine. No steam, no water spraying, no face visible. "
"Professional editorial photography, ultra-realistic."
),
},
]
for item in TARGETS:
out_path = os.path.join(OUT_DIR, f"{item['name']}.jpg")
print(f"Generating {item['name']}...")
try:
resp = client.models.generate_images(
model="imagen-4.0-generate-001",
prompt=item["prompt"],
config=types.GenerateImagesConfig(
number_of_images=1,
aspect_ratio="4:3",
output_mime_type="image/jpeg",
safety_filter_level="block_low_and_above",
),
)
if resp.generated_images:
b = resp.generated_images[0].image.image_bytes
with open(out_path, "wb") as f:
f.write(b)
print(f" Saved ({len(b)//1024}KB)")
else:
print(f" No image returned")
except Exception as e:
print(f" Error: {e}")
print("Done.")
+223
View File
@@ -0,0 +1,223 @@
"""
Generate carpet cleaning reel clips via Wan 2.2 TI2V (text+image to video) through ComfyUI.
Uses FLUX-generated hero stills as input frames, animates each into a 3-5 second clip.
Run after gen-images-flux.py completes and images are converted to webp.
Usage:
python3 tools/gen-video-wan.py
Output: assets/videos/clips/*.mp4 — stitch with ffmpeg into final reel.
"""
import json, time, urllib.request, os, random, io
COMFY = "http://localhost:8188"
HERO_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "images", "hero")
OUT_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "videos", "clips")
os.makedirs(OUT_DIR, exist_ok=True)
WAN_MODEL = "Wan2.2-TI2V-5B-Q4_K_M.gguf"
# Each clip: input still + motion prompt → 3-5 sec animated clip
# Order matches the reel sequence
CLIPS = [
{
"filename": "clip-01-carpet.mp4",
"image": "hero-carpet-cleaning.webp",
"prompt": "slow dolly forward across clean plush carpet, gentle camera push toward the far wall, warm afternoon light, cinematic, smooth motion",
"frames": 49, # ~4 seconds at ~12fps
},
{
"filename": "clip-02-stairs.mp4",
"image": "hero-stairs.webp",
"prompt": "slow pan upward along clean carpeted staircase, camera tilts up following the banister, soft natural light, cinematic motion",
"frames": 49,
},
{
"filename": "clip-03-upholstery.mp4",
"image": "hero-upholstery.webp",
"prompt": "gentle push in toward clean linen sofa, shallow depth of field, warm light, slow cinematic camera movement",
"frames": 49,
},
{
"filename": "clip-04-commercial.mp4",
"image": "hero-commercial.webp",
"prompt": "slow tracking shot moving forward down a clean corporate lobby, receding vanishing point, professional lighting, cinematic",
"frames": 49,
},
{
"filename": "clip-05-floors.mp4",
"image": "hero-floors.webp",
"prompt": "floor-level drift forward along gleaming hardwood, camera slides smoothly down the hallway, natural light",
"frames": 49,
},
{
"filename": "clip-06-clean-result.mp4",
"image": "hero-clean-result.webp",
"prompt": "slow rack focus across clean carpet fibers, foreground to background, raking natural light, macro detail, cinematic",
"frames": 49,
},
]
def load_image_as_base64(image_path):
import base64
with open(image_path, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")
def upload_image(image_path):
"""Upload image to ComfyUI and return the filename it assigned."""
import base64
fname = os.path.basename(image_path)
with open(image_path, "rb") as f:
img_data = f.read()
boundary = "----FormBoundary" + str(random.randint(100000, 999999))
body = (
f"--{boundary}\r\n"
f'Content-Disposition: form-data; name="image"; filename="{fname}"\r\n'
f"Content-Type: image/webp\r\n\r\n"
).encode() + img_data + f"\r\n--{boundary}--\r\n".encode()
req = urllib.request.Request(
f"{COMFY}/upload/image",
data=body,
headers={"Content-Type": f"multipart/form-data; boundary={boundary}"},
)
with urllib.request.urlopen(req) as resp:
result = json.loads(resp.read())
return result["name"]
def build_workflow(image_name, prompt, frames, seed=None):
if seed is None:
seed = random.randint(0, 2**32)
return {
"1": {
"class_type": "UnetLoaderGGUF",
"inputs": {"unet_name": WAN_MODEL},
},
"2": {
"class_type": "CLIPLoader",
"inputs": {
"clip_name": "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"type": "wan",
},
},
"3": {
"class_type": "VAELoader",
"inputs": {"vae_name": "wan_2.1_vae.safetensors"},
},
"4": {
"class_type": "LoadImage",
"inputs": {"image": image_name},
},
"5": {
"class_type": "CLIPTextEncode",
"inputs": {"clip": ["2", 0], "text": prompt},
},
"6": {
"class_type": "CLIPTextEncode",
"inputs": {"clip": ["2", 0], "text": "blur, low quality, distortion, text, watermark, people, faces"},
},
"7": {
"class_type": "WanImageToVideo",
"inputs": {
"model": ["1", 0],
"clip": ["2", 0],
"vae": ["3", 0],
"image": ["4", 0],
"positive": ["5", 0],
"negative": ["6", 0],
"width": 832,
"height": 480,
"length": frames,
"batch_size": 1,
"seed": seed,
"steps": 20,
"cfg": 6.0,
"sampler_name": "uni_pc",
"scheduler": "simple",
"denoise": 1.0,
},
},
"8": {
"class_type": "SaveAnimatedWEBP",
"inputs": {
"images": ["7", 0],
"filename_prefix": "wan_lahr",
"fps": 16,
"lossless": False,
"quality": 85,
"method": "default",
},
},
}
def queue_prompt(workflow):
data = json.dumps({"prompt": workflow}).encode()
req = urllib.request.Request(
f"{COMFY}/prompt", data=data,
headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())["prompt_id"]
def wait_for_result(prompt_id, timeout=1800):
start = time.time()
while time.time() - start < timeout:
try:
with urllib.request.urlopen(f"{COMFY}/history/{prompt_id}") as resp:
hist = json.loads(resp.read())
if prompt_id in hist:
entry = hist[prompt_id]
if entry.get("status", {}).get("status_str") == "error":
print(f" ERROR: {entry['status'].get('messages', '')}", flush=True)
return None
for node_out in entry.get("outputs", {}).values():
if "images" in node_out:
return node_out["images"]
if "gifs" in node_out:
return node_out["gifs"]
except Exception:
pass
time.sleep(8)
print(" waiting...", flush=True)
return None
def download_video(vid_info, out_path):
fname = vid_info["filename"]
subfolder = vid_info.get("subfolder", "")
img_type = vid_info.get("type", "output")
url = f"{COMFY}/view?filename={fname}&subfolder={subfolder}&type={img_type}"
with urllib.request.urlopen(url) as resp:
data = resp.read()
with open(out_path, "wb") as f:
f.write(data)
print(f" saved: {os.path.basename(out_path)} ({len(data)//1024}KB)", flush=True)
total = len(CLIPS)
for i, clip in enumerate(CLIPS):
print(f"\n[{i+1}/{total}] {clip['filename']}", flush=True)
image_path = os.path.join(HERO_DIR, clip["image"])
if not os.path.exists(image_path):
print(f" SKIP: {image_path} not found", flush=True)
continue
print(f" uploading {clip['image']}...", flush=True)
image_name = upload_image(image_path)
workflow = build_workflow(image_name, clip["prompt"], clip["frames"])
prompt_id = queue_prompt(workflow)
print(f" queued {prompt_id[:8]}...", flush=True)
results = wait_for_result(prompt_id)
if results:
out_path = os.path.join(OUT_DIR, clip["filename"])
# rename .webp output to .mp4 for compatibility — or save as webp animation
out_path_webp = out_path.replace(".mp4", ".webp")
download_video(results[0], out_path_webp)
else:
print(f" FAILED", flush=True)
print("\nAll clips done. Stitch with:")
print(f" ffmpeg -f concat -safe 0 -i tools/clip-list.txt -c copy assets/videos/hero-reel-flux.mp4")
+151
View File
@@ -0,0 +1,151 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Image Gen Pipeline</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: #0d0d0d; color: #e0e0e0; font-family: monospace; padding: 32px; }
h1 { font-size: 14px; color: #aaa; margin-bottom: 28px; letter-spacing: 1px; text-transform: uppercase; }
h2 { font-size: 11px; color: #666; letter-spacing: 1px; text-transform: uppercase; margin-bottom: 16px; }
.pipeline {
display: flex;
align-items: center;
gap: 0;
margin-bottom: 40px;
flex-wrap: wrap;
gap: 0;
}
.node {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 6px;
padding: 14px 18px;
min-width: 160px;
text-align: center;
}
.node .label { font-size: 10px; color: #666; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px; }
.node .name { font-size: 13px; color: #e0e0e0; font-weight: bold; }
.node .sub { font-size: 10px; color: #888; margin-top: 4px; }
.node.highlight { border-color: #4a9eff; background: #0f1f33; }
.node.highlight .name { color: #4a9eff; }
.arrow { color: #444; font-size: 20px; padding: 0 8px; }
.grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 40px; }
.card { background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 6px; padding: 14px; }
.card .k { font-size: 10px; color: #555; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px; }
.card .v { font-size: 13px; color: #ddd; }
.card .v.ok { color: #4caf50; }
.card .v.warn { color: #ff9800; }
.progress { margin-bottom: 40px; }
.bar-wrap { background: #1a1a1a; border-radius: 4px; height: 24px; margin-bottom: 8px; overflow: hidden; }
.bar { height: 100%; background: #4a9eff; border-radius: 4px; display: flex; align-items: center; padding-left: 10px; font-size: 11px; color: #fff; transition: width 0.3s; }
.pct { font-size: 11px; color: #666; }
.log { background: #0a0a0a; border: 1px solid #222; border-radius: 6px; padding: 16px; font-size: 11px; line-height: 1.8; color: #888; }
.log .ok { color: #4caf50; }
.log .done { color: #aaa; }
</style>
</head>
<body>
<h1>Lahr Carpet Cleaning — Image Generation Pipeline</h1>
<h2>Model Stack</h2>
<div class="pipeline">
<div class="node">
<div class="label">Prompt</div>
<div class="name">gen-images-flux.py</div>
<div class="sub">28 images (16 hero + 12 svc)</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="label">API</div>
<div class="name">ComfyUI</div>
<div class="sub">localhost:8188</div>
</div>
<div class="arrow"></div>
<div class="node highlight">
<div class="label">UNet (model)</div>
<div class="name">FLUX.1 Schnell</div>
<div class="sub">Q8_0 GGUF · 12GB · 12B params</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="label">Sampler</div>
<div class="name">KSampler</div>
<div class="sub">4 steps · euler · cfg=1.0</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="label">Decode</div>
<div class="name">FLUX AE</div>
<div class="sub">ae.safetensors · 108MB</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="label">Output</div>
<div class="name">JPEG → WebP</div>
<div class="sub">1024×576 · q92 → q80</div>
</div>
</div>
<h2>Text Encoders</h2>
<div class="pipeline" style="margin-bottom:40px">
<div class="node">
<div class="label">CLIP-L</div>
<div class="name">clip_l.safetensors</div>
<div class="sub">235MB · short prompts</div>
</div>
<div class="arrow">+</div>
<div class="node highlight">
<div class="label">T5-XXL fp8</div>
<div class="name">t5xxl_fp8_e4m3fn</div>
<div class="sub">4.6GB · long prompt understanding</div>
</div>
<div class="arrow"></div>
<div class="node">
<div class="label">Node</div>
<div class="name">DualCLIPLoader</div>
<div class="sub">type: flux</div>
</div>
</div>
<h2>Hardware</h2>
<div class="grid">
<div class="card"><div class="k">GPU</div><div class="v warn">AMD Radeon (2GB VRAM)</div></div>
<div class="card"><div class="k">Execution</div><div class="v warn">CPU only (VRAM too small)</div></div>
<div class="card"><div class="k">Speed</div><div class="v">~4 min / image</div></div>
<div class="card"><div class="k">Total ETA</div><div class="v">~1h50m for 28 images</div></div>
</div>
<h2>Model Files on Disk</h2>
<div class="grid">
<div class="card"><div class="k">UNet</div><div class="v ok">flux1-schnell-Q8_0.gguf · 12GB</div></div>
<div class="card"><div class="k">T5-XXL</div><div class="v ok">t5xxl_fp8_e4m3fn.safetensors · 4.6GB</div></div>
<div class="card"><div class="k">CLIP-L</div><div class="v ok">clip_l.safetensors · 235MB</div></div>
<div class="card"><div class="k">VAE</div><div class="v ok">ae.safetensors · 108MB (official BFL)</div></div>
</div>
<h2>Generation Progress</h2>
<div class="progress">
<div class="bar-wrap"><div class="bar" style="width:14%">4 / 28</div></div>
<div class="pct">14% — reload page to update</div>
</div>
<h2>Prompt Strategy</h2>
<div class="log">
<div class="ok">Low-angle perspective (35mm / 24mm lens specified in prompt)</div>
<div class="ok">Carpet/floor texture sharp in foreground — subject recedes into bokeh</div>
<div class="ok">Shallow depth of field + vanishing point for depth cues</div>
<div class="ok">No people, no machines, no equipment</div>
<div class="ok">Finger Lakes / upstate NY context for residential scenes</div>
<br>
<div class="done">Previous model: RealVisXL V5.0 fp16 (SDXL 3.5B) — rejected: flat angles, poor depth</div>
<div class="done">Current model: FLUX.1 Schnell (12B transformer) — better spatial understanding</div>
</div>
</body>
</html>
-142
View File
@@ -1,142 +0,0 @@
"""
Regen shot-04, shot-06, shot-07 with corrected scenes.
shot-04: carpet before/after reveal, no machine
shot-06: clean bright staircase, no machine
shot-07: bright modern office, no dark tones
"""
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")
client = genai.Client(api_key=API_KEY)
SHOTS = [
{
"name": "shot-04-extraction-carpet",
"prompt": (
"Cinematic slow-motion wide shot. Camera glides low across a residential living room carpet. "
"The left half of the carpet is visibly dirty, stained, and matted. "
"The right half is bright, clean, fluffy, and freshly extracted. "
"The boundary between dirty and clean is sharp and dramatic. "
"Warm natural afternoon light. No people. No machines. No equipment. Photorealistic."
),
},
{
"name": "shot-06-extraction-stairs",
"prompt": (
"Cinematic slow-motion shot looking up a bright residential carpeted staircase. "
"Each step has clean, bright, plush beige carpet with fresh extraction lines. "
"Warm natural light from above illuminates the stairs. Wood banisters on each side. "
"No people. No machines. No equipment anywhere in frame. Photorealistic."
),
},
{
"name": "shot-07-office-entryway",
"prompt": (
"Wide cinematic shot of a bright modern commercial office building lobby. "
"Large windows let in abundant natural daylight. Clean beige or grey commercial carpet throughout. "
"White walls, professional lighting, glass doors, contemporary furniture. "
"The carpet looks spotlessly clean with neat vacuum lines. "
"No people. No machines. No dark tones — the space is bright and well-lit. Photorealistic."
),
},
]
MODEL = "veo-3.1-generate-preview"
def poll(op, timeout=600):
elapsed = 0
while not op.done:
if elapsed >= timeout:
print(f" Timed out after {timeout}s.")
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[VID] Generating {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 is None:
print(f" FAILED (timeout)")
continue
if 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(item["name"])
else:
try:
vid.save(out_path)
print(f" Saved via .save() ({os.path.getsize(out_path)//1024}KB)")
saved.append(item["name"])
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: {saved}")
ORDER = [
"shot-01-door-opens-trimmed",
"shot-02-pan-to-stains",
"shot-03-stain-closeup",
"shot-04-extraction-carpet",
"shot-05-extraction-couch",
"shot-06-extraction-stairs",
"shot-07-office-entryway",
"shot-08-showroom",
"shot-09-technician-unloading",
]
missing = [n for n in ORDER if not os.path.exists(os.path.join(VID_DIR, f"{n}.mp4"))]
if missing:
print(f"\nSkipping reconcat — missing: {missing}")
else:
print("\nReconcatenating hero-reel.mp4...")
concat_file = os.path.join(VID_DIR, "concat.txt")
with open(concat_file, "w") as f:
for name in ORDER:
f.write(f"file '{os.path.join(VID_DIR, name)}.mp4'\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[-400:]}")
print("\nDone.")
-55
View File
@@ -1,55 +0,0 @@
"""Regenerate commercial-overview.jpg with a clear commercial carpet scene."""
import os, sys
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")
OUT_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "images", "services")
client = genai.Client(api_key=API_KEY)
PROMPTS = [
(
"Wide shot of a modern commercial office building lobby with clean grey carpet throughout. "
"Professional corporate interior, glass doors, white walls, overhead lighting. "
"The carpet is spotless and freshly cleaned — uniform, well-maintained. "
"No people. No machines. No equipment. Professional architectural photography, ultra-realistic."
),
(
"Wide interior shot of a bright commercial building corridor with clean, dark grey commercial carpet. "
"Modern office environment, glass partitions, professional lighting. "
"The carpet looks freshly cleaned and spotless. "
"No people, no equipment. Professional photography, ultra-realistic."
),
]
out_path = os.path.join(OUT_DIR, "commercial-overview.jpg")
for i, prompt in enumerate(PROMPTS):
print(f"Attempt {i+1}...")
try:
resp = client.models.generate_images(
model="imagen-4.0-generate-001",
prompt=prompt,
config=types.GenerateImagesConfig(
number_of_images=1, aspect_ratio="4:3",
output_mime_type="image/jpeg",
safety_filter_level="block_low_and_above",
),
)
if resp.generated_images:
b = resp.generated_images[0].image.image_bytes
with open(out_path, "wb") as f:
f.write(b)
print(f"Saved ({len(b)//1024}KB)")
break
else:
print("No image returned")
except Exception as e:
print(f"Error: {e}")
print("Done.")
-163
View File
@@ -1,163 +0,0 @@
"""
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.")
-64
View File
@@ -1,64 +0,0 @@
"""Regenerate only hero-technician and hero-before-after images."""
import os, sys
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")
OUT_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "images", "hero")
client = genai.Client(api_key=API_KEY)
TARGETS = [
{
"name": "hero-technician",
"aspect": "16:9",
"prompt": (
"Professional carpet cleaning technician pushing a large upright hot water "
"extraction machine across residential carpet in a bright modern home interior. "
"Machine resembles an oversized upright vacuum cleaner with a cylindrical body. "
"No steam visible anywhere, no water spraying, no hoses visible, completely dry. "
"Technician shown from behind or side, no face, plain black shirt, no logo. "
"High-end professional photography."
),
},
{
"name": "hero-before-after",
"aspect": "16:9",
"prompt": (
"Side-by-side residential living room carpet: left half heavily soiled with mud "
"and dark stains, right half same carpet after professional hot water extraction "
"cleaning, bright and pristine. No steam, no water, no machines visible anywhere. "
"Dramatic before-after contrast. Professional photography, no people."
),
},
]
for item in TARGETS:
out_path = os.path.join(OUT_DIR, f"{item['name']}.jpg")
print(f"Generating {item['name']}...")
try:
resp = client.models.generate_images(
model="imagen-4.0-generate-001",
prompt=item["prompt"],
config=types.GenerateImagesConfig(
number_of_images=1,
aspect_ratio=item["aspect"],
output_mime_type="image/jpeg",
safety_filter_level="block_low_and_above",
),
)
if resp.generated_images:
img_bytes = resp.generated_images[0].image.image_bytes
with open(out_path, "wb") as f:
f.write(img_bytes)
print(f" Saved {out_path} ({len(img_bytes)//1024}KB)")
else:
print(f" No image returned for {item['name']}")
except Exception as e:
print(f" Error: {e}")
print("\nDone.")
-169
View File
@@ -1,169 +0,0 @@
"""Regenerate carpet-cleaning and commercial-overview service images with industrial extractor prompts."""
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__))
IMG_DIR = os.path.join(BASE_DIR, "assets", "images", "services")
VID_DIR = os.path.join(BASE_DIR, "assets", "videos", "hero", "clips")
REEL_OUT = os.path.join(BASE_DIR, "assets", "videos", "hero", "hero-reel.mp4")
client = genai.Client(api_key=API_KEY)
# ── Service images ────────────────────────────────────────────────────────────
IMAGES = [
{
"name": "carpet-cleaning",
"prompt": (
"Wide shot of a large industrial stand-up hot water extraction machine being pushed across "
"a plush beige residential carpet. The machine is a heavy commercial-grade upright extractor "
"on wheels — tall, wide cleaning head at the base, long upright handle. "
"The carpet behind it transitions from dirty and matted to clean, bright, and fluffy. "
"Completely dry machine exterior, no steam, no water spraying anywhere. "
"Warm natural interior light. Ultra-realistic professional photography."
),
},
{
"name": "commercial-overview",
"prompt": (
"Professional carpet cleaning technician in a plain black shirt, shown from the side, "
"pushing a large industrial stand-up hot water extraction machine through a bright commercial "
"building lobby. The machine is a heavy commercial-grade upright extractor on wheels — "
"tall, wide cleaning head, long handle. Clean carpet visible. No steam, no water spraying, "
"no face visible. Professional editorial photography, ultra-realistic."
),
},
]
for item in IMAGES:
out_path = os.path.join(IMG_DIR, f"{item['name']}.jpg")
print(f"[IMG] Generating {item['name']}...")
try:
resp = client.models.generate_images(
model="imagen-4.0-generate-001",
prompt=item["prompt"],
config=types.GenerateImagesConfig(
number_of_images=1, aspect_ratio="4:3",
output_mime_type="image/jpeg",
safety_filter_level="block_low_and_above",
),
)
if resp.generated_images:
b = resp.generated_images[0].image.image_bytes
with open(out_path, "wb") as f:
f.write(b)
print(f" Saved {out_path} ({len(b)//1024}KB)")
else:
print(f" No image returned")
except Exception as e:
print(f" Error: {e}")
# ── Video shots ───────────────────────────────────────────────────────────────
SHOTS = [
{
"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-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(op, timeout=420):
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_clips = []
for item in SHOTS:
out_path = os.path.join(VID_DIR, f"{item['name']}.mp4")
print(f"\n[VID] 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 and op.response and op.response.generated_videos:
vid = op.response.generated_videos[0].video
try:
b = client.files.download(file=vid)
except Exception:
b = None
if b:
with open(out_path, "wb") as f:
f.write(b)
print(f" Saved ({os.path.getsize(out_path)//1024}KB)")
saved_clips.append(item["name"])
done = True
break
except Exception as e:
print(f" Error with {model}: {e}")
if not done:
print(f" FAILED: {item['name']}")
# ── Reconcat reel if both shots regenerated ───────────────────────────────────
if len(saved_clips) == 2:
print("\nReconcatenating reel...")
concat_file = os.path.join(VID_DIR, "concat.txt")
order = [
"shot-01-door-opens-trimmed",
"shot-02-pan-to-stains",
"shot-03-stain-closeup",
"shot-04-extraction-carpet",
"shot-05-extraction-couch",
"shot-06-extraction-stairs",
"shot-07-office-entryway",
"shot-08-showroom",
"shot-09-technician-unloading",
]
with open(concat_file, "w") as f:
for name in order:
f.write(f"file '{os.path.join(VID_DIR, name)}.mp4'\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:]}")
else:
print(f"\nOnly {len(saved_clips)}/2 video shots regenerated — skipping reconcat.")
print("\nDone.")
-87
View File
@@ -1,87 +0,0 @@
"""
Regenerate one specific shot and re-concatenate the hero reel.
Usage: python3 tools/regen-shot.py shot-02-staircase
"""
import os, sys, time, subprocess
from google import genai
from google.genai import types
import urllib.request
API_KEY = os.environ.get("GEMINI_API_KEY", "")
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
CLIPS_DIR = os.path.join(BASE_DIR, "assets", "videos", "hero", "clips")
REEL_OUT = os.path.join(BASE_DIR, "assets", "videos", "hero", "hero-reel.mp4")
if not API_KEY:
print("Set GEMINI_API_KEY env var"); sys.exit(1)
# Import SHOTS from gen-video.py (dash in name requires importlib)
import importlib.util
spec = importlib.util.spec_from_file_location(
"gen_video", os.path.join(os.path.dirname(__file__), "gen-video.py")
)
_mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(_mod)
SHOTS = _mod.SHOTS
shot_name = sys.argv[1] if len(sys.argv) > 1 else None
target = next((s for s in SHOTS if s["name"] == shot_name), None)
if not target:
print(f"Shot '{shot_name}' not found. Available: {[s['name'] for s in SHOTS]}")
sys.exit(1)
client = genai.Client(api_key=API_KEY)
out_path = os.path.join(CLIPS_DIR, f"{target['name']}.mp4")
print(f"Regenerating {target['name']}...")
op = client.models.generate_videos(
model="veo-3.0-generate-001",
prompt=target["prompt"],
config=types.GenerateVideosConfig(
aspect_ratio="16:9", resolution="720p",
duration_seconds=6, number_of_videos=1,
),
)
elapsed = 0
while not op.done:
print(f" Waiting... ({elapsed}s)")
time.sleep(15); elapsed += 15
op = client.operations.get(op)
if not (op.response and op.response.generated_videos):
print("No video returned"); sys.exit(1)
vid = op.response.generated_videos[0].video
video_bytes = None
try:
video_bytes = client.files.download(file=vid)
except Exception:
pass
if video_bytes:
with open(out_path, "wb") as f: f.write(video_bytes)
elif hasattr(vid, "uri") and vid.uri:
uri = vid.uri + ("&" if "?" in vid.uri else "?") + f"key={API_KEY}"
urllib.request.urlretrieve(uri, out_path)
else:
print("Download failed"); sys.exit(1)
print(f"Saved {out_path} ({os.path.getsize(out_path)//1024}KB)")
# Re-concat in shot order
clips = [os.path.join(CLIPS_DIR, f"{s['name']}.mp4") for s in SHOTS
if os.path.exists(os.path.join(CLIPS_DIR, f"{s['name']}.mp4"))]
list_file = os.path.join(CLIPS_DIR, "concat.txt")
with open(list_file, "w") as f:
for c in clips: f.write(f"file '{c}'\n")
print(f"Concatenating {len(clips)} clips...")
r = 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 r.returncode == 0:
print(f"Reel updated: {REEL_OUT} ({os.path.getsize(REEL_OUT)//1024}KB)")
else:
print(f"ffmpeg error: {r.stderr[-200:]}")
-102
View File
@@ -1,102 +0,0 @@
"""Generate shot-02 replacement: kid runs in with muddy shoes, focus on feet and mud tracks."""
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")
client = genai.Client(api_key=API_KEY)
PROMPTS = [
(
"Slow cinematic shot at floor level, looking across a beige residential carpet. "
"A child's feet in muddy sneakers run into frame from the front door and across the carpet, "
"leaving clear muddy footprints with each step. Then adult feet in boots walk in behind, "
"adding more dirt and mud tracks. Camera stays low, focused on the feet and the mud on the carpet. "
"Warm indoor light. Photorealistic, slow motion."
),
(
"Low cinematic camera angle at carpet level inside a home entryway. "
"A child runs in wearing muddy shoes, close-up on their feet stomping mud into the beige carpet. "
"Adult legs follow behind, tracking in more dirt. "
"The carpet shows fresh mud and dirty footprints after each step. "
"Camera stays at floor level throughout. Warm natural light. Slow motion. Photorealistic."
),
]
MODEL = "veo-3.1-generate-preview"
out_path = os.path.join(VID_DIR, "shot-02-pan-to-stains.mp4")
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 = False
for i, prompt in enumerate(PROMPTS):
print(f"\n[VID] shot-02 attempt {i+1}...")
try:
op = client.models.generate_videos(
model=MODEL,
prompt=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 = True
break
else:
print(f" No video returned, trying next prompt...")
except Exception as e:
print(f" Error: {e}")
if not saved:
print("All attempts failed — original shot-02 kept.")
else:
ORDER = [
"shot-01-door-opens-trimmed", "shot-02-pan-to-stains", "shot-03-stain-closeup",
"shot-04-extraction-carpet", "shot-05-extraction-couch", "shot-06-extraction-stairs",
"shot-07-office-entryway", "shot-08-showroom", "shot-09-technician-unloading",
]
missing = [n for n in ORDER if not os.path.exists(os.path.join(VID_DIR, f"{n}.mp4"))]
if missing:
print(f"Skipping reconcat — missing: {missing}")
else:
concat_file = os.path.join(VID_DIR, "concat.txt")
with open(concat_file, "w") as f:
for name in ORDER:
f.write(f"file '{os.path.join(VID_DIR, name)}.mp4'\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.")
-95
View File
@@ -1,95 +0,0 @@
"""Retry shot-04 with a simple clean carpet scene."""
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")
client = genai.Client(api_key=API_KEY)
PROMPTS = [
(
"Cinematic slow dolly shot moving forward through a bright residential living room. "
"Clean, plush beige carpet fills the floor. Warm afternoon sunlight comes through large windows. "
"Contemporary furniture, neutral walls. The carpet looks freshly cleaned — bright, fluffy, spotless. "
"No people. No machines. Photorealistic."
),
(
"Slow cinematic camera pan across a clean beige residential carpet in a bright living room. "
"The carpet fibers are lifted and bright after professional cleaning. "
"Warm natural light. Modern home interior. No people, no equipment. Photorealistic."
),
]
MODEL = "veo-3.1-generate-preview"
out_path = os.path.join(VID_DIR, "shot-04-extraction-carpet.mp4")
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 = False
for i, prompt in enumerate(PROMPTS):
print(f"\n[VID] shot-04 attempt {i+1}...")
try:
op = client.models.generate_videos(
model=MODEL,
prompt=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 = True
break
else:
print(f" No video returned, trying next prompt...")
except Exception as e:
print(f" Error: {e}")
if not saved:
print("All attempts failed.")
else:
ORDER = [
"shot-01-door-opens-trimmed", "shot-02-pan-to-stains", "shot-03-stain-closeup",
"shot-04-extraction-carpet", "shot-05-extraction-couch", "shot-06-extraction-stairs",
"shot-07-office-entryway", "shot-08-showroom", "shot-09-technician-unloading",
]
concat_file = os.path.join(VID_DIR, "concat.txt")
with open(concat_file, "w") as f:
for name in ORDER:
f.write(f"file '{os.path.join(VID_DIR, name)}.mp4'\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.")
-98
View File
@@ -1,98 +0,0 @@
"""Replace v3-shot-04 with a clean sofa result — no cleaning action, no equipment, no steam."""
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")
client = genai.Client(api_key=API_KEY)
PROMPTS = [
(
"Close-up cinematic shot slowly pulling back from a clean, bright grey upholstered sofa cushion. "
"The fabric is fresh, fluffy, and spotless. Warm natural window light. "
"No people. No machines. No equipment of any kind. No steam. No water. "
"Just a beautiful clean sofa in a bright living room. Photorealistic."
),
(
"Slow cinematic pan across a clean living room. A light grey sofa with pristine cushions. "
"Bright clean beige carpet on the floor. Warm afternoon light. "
"No people. No machines. No equipment. Photorealistic."
),
]
MODEL = "veo-3.1-generate-preview"
out_path = os.path.join(VID_DIR, "v3-shot-04.mp4")
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 = False
for i, prompt in enumerate(PROMPTS):
print(f"\n[v3-shot-04] attempt {i+1}...")
try:
op = client.models.generate_videos(
model=MODEL,
prompt=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 = True
break
else:
print(f" No video returned")
except Exception as e:
print(f" Error: {e}")
if saved:
clips = [
"v3-shot-01", "v3-shot-02", "v3-shot-03", "v3-shot-04",
"v3-shot-05", "v3-shot-06", "v3-shot-07",
]
missing = [n for n in clips if not os.path.exists(os.path.join(VID_DIR, f"{n}.mp4"))]
if missing:
print(f"Skipping reconcat — missing: {missing}")
else:
concat_file = os.path.join(VID_DIR, "concat-v3.txt")
with open(concat_file, "w") as f:
for n in clips:
f.write(f"file '{os.path.join(VID_DIR, n)}.mp4'\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:]}")
else:
print("Failed — shot-04 unchanged.")
print("\nDone.")
-153
View File
@@ -1,153 +0,0 @@
"""
Hero reel v3 7 shots with corrected prompts.
"""
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": "v3-shot-01",
"prompt": (
"Medium shot. A family of four — two adults and two children — walks through the front door "
"of a warm residential home. Camera is inside the home facing them as they enter. "
"After they walk in, the camera slowly pans down to the carpet, revealing dirty footprints "
"and mud tracked onto the beige carpet. Warm natural light. Photorealistic."
),
},
{
"name": "v3-shot-02",
"prompt": (
"Medium shot, slow zoom in. A glass of red wine has spilled onto a light grey upholstered sofa. "
"The camera starts wide on the sofa then slowly zooms in on the dark wine stain spreading "
"across the fabric. The stain is clearly visible, soaked into the cushion. "
"Warm living room light. No people. Photorealistic."
),
},
{
"name": "v3-shot-03",
"prompt": (
"Medium shot at low angle, camera near floor level. Inside a bright commercial office entryway. "
"A person wearing work boots walks through the entry door toward the camera and past it. "
"The camera is low, showing the boots and lower legs as they walk across the carpet past the lens. "
"Office carpet visible, natural light from glass doors behind. Photorealistic."
),
},
{
"name": "v3-shot-04",
"prompt": (
"Close-up cinematic shot. A technician's gloved hand holds a small upholstery extraction wand — "
"a flat rectangular handheld tool. The technician presses the wand firmly onto a wine-stained "
"sofa cushion and draws it slowly across the fabric. The stain visibly lifts as the wand moves. "
"Suction only — no water sprays out. Slow motion. Natural light. Photorealistic."
),
},
{
"name": "v3-shot-05",
"prompt": (
"Wide cinematic shot. Camera slowly pans across the entrance of a modern commercial office building. "
"Clean grey commercial carpet stretches across the lobby floor. Large windows, glass doors, "
"professional lighting. The carpet looks freshly cleaned and spotless. "
"No people. No machines. Photorealistic."
),
},
{
"name": "v3-shot-06",
"prompt": (
"Wide cinematic shot. Camera slowly pans across a bright, clean residential living room. "
"Plush clean beige carpet throughout. Comfortable furniture — sofa, armchairs, coffee table. "
"Warm natural afternoon light through large windows. The room looks fresh and inviting. "
"No people. No cleaning equipment. Photorealistic."
),
},
{
"name": "v3-shot-07",
"prompt": (
"Wide cinematic shot. Camera moves slowly forward through an upscale restaurant dining room. "
"Rich carpet covers the floor. White tablecloths, warm ambient lighting, wood accents. "
"The carpet looks clean, deep, and well-maintained as the camera glides through the space. "
"No people. 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 i, item in enumerate(SHOTS):
out_path = os.path.join(VID_DIR, f"{item['name']}.mp4")
print(f"\n[{i+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:
concat_file = os.path.join(VID_DIR, "concat-v3.txt")
clips = [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"))]
with open(concat_file, "w") as f:
for p in clips:
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.")
-154
View File
@@ -1,154 +0,0 @@
"""Hero reel v4 — 6 precise shots."""
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": "v4-shot-01",
"prompt": (
"Medium cinematic shot. A family — two adults and two children — walks through a front door "
"into a residential home. The camera follows their feet as they step onto the beige carpet "
"in the entryway, then slowly pans down to show their shoes leaving dirty tracks on the carpet. "
"Warm afternoon light. Photorealistic, slow motion."
),
},
{
"name": "v4-shot-02",
"prompt": (
"Slow-motion close-up cinematic shot. A wine glass tips over on a light grey fabric sofa cushion. "
"Red wine pours out of the glass and spreads across the sofa cushion, soaking into the fabric. "
"The dark red stain expands slowly across the grey upholstery. "
"Warm living room light. No people visible. Photorealistic, dramatic slow motion."
),
},
{
"name": "v4-shot-03",
"prompt": (
"Cinematic close-up shot slowly pushing in on a section of heavily soiled residential carpet. "
"The beige carpet has multiple visible stains — dark spots, discoloration, pet stains, "
"and general dirt buildup embedded in the fibers. "
"Dramatic side lighting emphasizes the depth of the stains. No people. No equipment. Photorealistic."
),
},
{
"name": "v4-shot-04",
"prompt": (
"Close-up cinematic shot. A carpet cleaning technician in a plain black shirt pushes "
"a large upright carpet cleaning machine — like a Rug Doctor — across a dirty beige carpet. "
"Tight shot focused on the wide flat cleaning head at the base of the machine pressing against "
"the carpet and moving forward. The carpet behind the machine looks visibly cleaner and brighter. "
"The machine only pulls dirt and moisture INTO itself — nothing comes out. "
"No steam. No liquid leaving the machine. Photorealistic slow motion."
),
},
{
"name": "v4-shot-05",
"prompt": (
"Wide cinematic shot slowly pushing forward through the main entrance of a modern commercial "
"office building. Clean grey carpet covers the entire lobby floor. Glass doors, white walls, "
"professional overhead lighting. The carpet is the visual centerpiece — clean, uniform, well-maintained. "
"No people. No machines. Photorealistic."
),
},
{
"name": "v4-shot-06",
"prompt": (
"Cinematic wide shot inside a bright clean residential living room. "
"The camera slowly pans upward from the clean plush beige carpet to reveal the whole room. "
"A family — two adults and a child — walks in at different moments and relaxes on the sofa. "
"Everyone is wearing socks. The carpet is spotless and fluffy. Warm natural light. "
"Comfortable, inviting atmosphere. Photorealistic."
),
},
]
MODELS = ["veo-3.1-generate-preview", "veo-2.0-generate-001"]
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 i, item in enumerate(SHOTS):
out_path = os.path.join(VID_DIR, f"{item['name']}.mp4")
print(f"\n[{i+1}/{len(SHOTS)}] {item['name']}...")
done = False
for model in MODELS:
try:
print(f" Trying {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 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)
done = True
break
else:
try:
vid.save(out_path)
print(f" Saved via .save()")
saved.append(out_path)
done = True
break
except Exception as e2:
print(f" Download failed: {e2}")
else:
print(f" No video returned from {model}")
except Exception as e:
print(f" Error with {model}: {e}")
if not done:
print(f" FAILED: {item['name']}")
print(f"\n{len(saved)}/{len(SHOTS)} shots saved")
if len(saved) >= 2:
clips = [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-v4.txt")
with open(concat_file, "w") as f:
for p in clips:
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.")
-167
View File
@@ -1,167 +0,0 @@
"""
Regenerate carpet extraction shots using Veo 3.1.
Fixes: buffer/rotary/steamer machines replaced with explicit stand-up extractor descriptions.
Reconcats hero-reel.mp4 when done.
Run: python3 tools/regen-veo31.py
"""
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")
client = genai.Client(api_key=API_KEY)
# Machine description block used across all shots
MACHINE = (
"The carpet cleaning machine looks exactly like a Rug Doctor or Bissell Big Green carpet cleaner — "
"the large upright machines you rent at grocery stores. Tall rectangular body, upright handle, "
"wide flat cleaning foot at the base. It is pushed forward in straight lines like a vacuum cleaner. "
"NOT a floor buffer. NOT a rotary disc scrubber. NOT a steam cleaner. NOT circular. "
"NO spinning parts. NO steam. NO water spraying out. The carpet is cleaned by suction alone."
)
SHOTS = [
{
"name": "shot-04-extraction-carpet",
"prompt": (
f"Cinematic slow-motion wide shot. A man pushes a Rug Doctor style carpet cleaning machine "
f"steadily forward across a beige residential carpet in a living room. {MACHINE} "
f"The carpet behind the machine transitions from dirty and matted to bright, clean, and fluffy. "
f"Warm natural room light. Photorealistic professional video."
),
},
{
"name": "shot-05-extraction-couch",
"prompt": (
"Close-up cinematic slow-motion shot. A professional technician's gloved hand holds a small "
"flat handheld upholstery cleaning attachment — a rectangular suction wand, flat on the bottom, "
"no moving parts, no spinning disc. "
"The technician presses it firmly against a dirty grey sofa cushion and slides it slowly across. "
"The fabric visibly brightens as the wand moves across it. "
"NO rotary machine anywhere in frame. NO spinning. NO steam. NO water pouring out. "
"Natural light. Photorealistic."
),
},
{
"name": "shot-06-extraction-stairs",
"prompt": (
"Cinematic slow-motion shot. A professional technician pushes a Rug Doctor style upright carpet "
"cleaning machine up a carpeted residential staircase, one stair tread at a time. "
"The machine is a tall rectangular upright unit with a handle and flat cleaning head — "
"NOT a buffer, NOT circular, NOT rotating. "
"Each stair tread brightens and looks freshly cleaned as the machine passes over it. "
"NO steam, NO water visible. Warm interior light. Photorealistic."
),
},
{
"name": "shot-09-technician-unloading",
"prompt": (
f"Wide cinematic shot. A professional carpet cleaning technician in a plain black shirt "
f"rolls a Rug Doctor style carpet cleaning machine down a ramp out of a white service van "
f"parked in a residential driveway in upstate New York. {MACHINE} "
f"Autumn trees in background, bright daylight. "
f"Technician shown from the side or behind, no face visible. Photorealistic."
),
},
]
MODEL = "veo-3.1-generate-preview"
def poll(op, timeout=600):
elapsed = 0
while not op.done:
if elapsed >= timeout:
print(f" Timed out after {timeout}s.")
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[VID] Generating {item['name']} with {MODEL}...")
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 is None:
print(f" FAILED (timeout): {item['name']}")
continue
if op.response and op.response.generated_videos:
vid = op.response.generated_videos[0].video
# Veo 3.1 download pattern
video_bytes = client.files.download(file=vid)
if video_bytes:
with open(out_path, "wb") as f:
f.write(video_bytes)
print(f" Saved {out_path} ({os.path.getsize(out_path)//1024}KB)")
saved.append(item["name"])
else:
# fallback: try .save() method
try:
vid.save(out_path)
print(f" Saved via .save() ({os.path.getsize(out_path)//1024}KB)")
saved.append(item["name"])
except Exception as e2:
print(f" Download failed: {e2}")
else:
print(f" No video returned for {item['name']}")
except Exception as e:
print(f" Error: {e}")
print(f"\n{len(saved)}/{len(SHOTS)} shots saved: {saved}")
# Reconcat full reel
ORDER = [
"shot-01-door-opens-trimmed",
"shot-02-pan-to-stains",
"shot-03-stain-closeup",
"shot-04-extraction-carpet",
"shot-05-extraction-couch",
"shot-06-extraction-stairs",
"shot-07-office-entryway",
"shot-08-showroom",
"shot-09-technician-unloading",
]
missing = [n for n in ORDER if not os.path.exists(os.path.join(VID_DIR, f"{n}.mp4"))]
if missing:
print(f"\nSkipping reconcat — missing clips: {missing}")
else:
print("\nReconcatenating hero-reel.mp4...")
concat_file = os.path.join(VID_DIR, "concat.txt")
with open(concat_file, "w") as f:
for name in ORDER:
f.write(f"file '{os.path.join(VID_DIR, name)}.mp4'\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: {REEL_OUT} ({os.path.getsize(REEL_OUT)//1024}KB)")
else:
print(f" ffmpeg error: {result.stderr[-400:]}")
print("\nDone.")
+117
View File
@@ -0,0 +1,117 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Image Review — All 28</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: #0d0d0d; color: #ddd; font-family: monospace; padding: 24px; }
.header { margin-bottom: 24px; }
.header h1 { font-size: 13px; color: #888; text-transform: uppercase; letter-spacing: 1px; }
.header .meta { font-size: 11px; color: #444; margin-top: 6px; }
.section-label { font-size: 10px; color: #555; text-transform: uppercase; letter-spacing: 2px; margin: 28px 0 12px; border-bottom: 1px solid #1a1a1a; padding-bottom: 8px; }
.grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 8px; }
.card { background: #141414; border: 1px solid #222; border-radius: 6px; overflow: hidden; cursor: pointer; transition: border-color 0.15s; }
.card:hover { border-color: #4a9eff; }
.card img { width: 100%; height: 160px; object-fit: cover; display: block; }
.card .info { padding: 10px; }
.card .name { font-size: 11px; color: #aaa; margin-bottom: 6px; font-weight: bold; }
.card .prompt { font-size: 10px; color: #555; line-height: 1.6; }
.card .tag { display: inline-block; font-size: 9px; background: #1a2a1a; color: #4a9eff; padding: 2px 6px; border-radius: 3px; margin-bottom: 6px; }
/* lightbox */
.lb { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.93); z-index: 100; padding: 32px; }
.lb.open { display: flex; gap: 32px; align-items: flex-start; }
.lb img { max-height: 80vh; max-width: 60vw; object-fit: contain; border-radius: 6px; flex-shrink: 0; }
.lb .lb-info { flex: 1; }
.lb .lb-name { font-size: 13px; color: #eee; margin-bottom: 12px; }
.lb .lb-model { font-size: 10px; color: #4a9eff; margin-bottom: 16px; }
.lb .lb-prompt { font-size: 12px; color: #aaa; line-height: 1.8; }
.lb .lb-close { position: absolute; top: 16px; right: 24px; font-size: 24px; color: #555; cursor: pointer; }
.lb .lb-close:hover { color: #eee; }
</style>
</head>
<body>
<div class="header">
<h1>Lahr Carpet Cleaning — Image Review</h1>
<div class="meta">Model: FLUX.1 Schnell Q8_0 GGUF &nbsp;·&nbsp; 4 steps, cfg=1.0, euler/simple &nbsp;·&nbsp; 1024×576 → WebP &nbsp;·&nbsp; 28 images total</div>
</div>
<div class="section-label">Hero Images (16)</div>
<div class="grid" id="heroes"></div>
<div class="section-label">Service Cards (12)</div>
<div class="grid" id="services"></div>
<div class="lb" id="lb" onclick="closeLB()">
<img id="lb-img" src="">
<div class="lb-info">
<div class="lb-name" id="lb-name"></div>
<div class="lb-model">FLUX.1 Schnell · Q8_0 GGUF · 4 steps · euler · cfg=1.0</div>
<div class="lb-prompt" id="lb-prompt"></div>
</div>
<div class="lb-close"></div>
</div>
<script>
const HEROES = [
{ file: "hero-carpet-cleaning.webp", prompt: "low-angle 35mm lens perspective looking across thick plush cream carpet in an upstate New York living room, carpet fibers razor sharp in foreground, couch and coffee table receding into shallow bokeh background, warm afternoon window light raking across carpet texture, Finger Lakes farmhouse interior, no people, ultra-realistic architectural photography, 16:9" },
{ file: "hero-stairs.webp", prompt: "dramatic low 35mm angle looking up a clean carpeted staircase from floor level, light grey carpet runner sharp and textured in foreground steps, wood banister receding diagonally, bright daylight flooding from above, shallow depth of field, no people, ultra-realistic interior photography, 16:9" },
{ file: "hero-upholstery.webp", prompt: "50mm lens low corner angle across a bright residential living room, plush linen fabric sofa arm sharp in near foreground, clean armchair and window receding with bokeh, afternoon countryside light through window, shallow depth of field, no people, ultra-realistic interior photography, 16:9" },
{ file: "hero-floors.webp", prompt: "low 24mm angle pressed to gleaming light oak hardwood floor, floor grain razor sharp in extreme foreground receding to hallway vanishing point, white walls, natural light streaming in, shallow depth of field, no people, ultra-realistic interior photography, 16:9" },
{ file: "hero-area-rugs.webp", prompt: "low 35mm angle looking across a hand-knotted oriental rug from floor level, rich red and gold rug fibers sharp in foreground, hardwood floor and room receding into bokeh, cozy farmhouse living room, warm natural light, shallow depth of field, no people, ultra-realistic interior photography, 16:9" },
{ file: "hero-add-ons.webp", prompt: "low 35mm angle across a clean beige bedroom carpet, carpet pile sharp and detailed in near foreground, wooden bed frame and sheer curtained window receding, crisp morning light, shallow depth of field, no people, no machines, ultra-realistic interior photography, 16:9" },
{ file: "hero-commercial.webp", prompt: "low 24mm wide-angle lens across a modern corporate lobby floor, dark charcoal commercial carpet sharp in extreme foreground receding to glass entrance doors, recessed ceiling lights creating depth, strong vanishing point perspective, no people, ultra-realistic architectural photography, 16:9" },
{ file: "hero-offices.webp", prompt: "low 24mm angle across clean grey carpet tiles in a modern open-plan office, carpet tile seams sharp in foreground receding to rows of empty desks and glass partitions, professional overhead lighting, strong linear perspective, no people, ultra-realistic architectural photography, 16:9" },
{ file: "hero-vacation-rentals.webp", prompt: "low 35mm angle across clean beige carpet in a Finger Lakes cottage living room, carpet fibers sharp in foreground, stone fireplace and lake-view window receding with bokeh, wooden ceiling beams, warm inviting light, shallow depth of field, no people, ultra-realistic interior photography, 16:9" },
{ file: "hero-hotels.webp", prompt: "low 24mm lens looking down a long hotel corridor from floor level, patterned burgundy carpet runner sharp in extreme foreground receding to vanishing point, warm wall sconces lining white walls, numbered doors converging in perspective, no people, ultra-realistic hospitality photography, 16:9" },
{ file: "hero-retail.webp", prompt: "low 35mm diagonal angle across clean light grey carpet in an upscale retail showroom, carpet surface sharp in foreground, minimalist display fixtures and storefront windows receding with bokeh, bright track lighting overhead, shallow depth of field, no people, ultra-realistic architectural photography, 16:9" },
{ file: "hero-property-management.webp", prompt: "low 35mm angle across fresh neutral carpet in an empty move-in ready apartment, carpet texture sharp in foreground, bare white walls and bright windows receding, clean real estate photography perspective, shallow depth of field, no people, ultra-realistic real estate photography, 16:9" },
{ file: "hero-about.webp", prompt: "low 35mm angle from lawn level looking up at a classic upstate New York suburban home, green grass blades sharp in extreme foreground, inviting house facade receding upward, mature trees and clear blue sky, warm summer afternoon, no people, ultra-realistic real estate photography, 16:9" },
{ file: "hero-service-area.webp", prompt: "low horizon 24mm wide-angle Finger Lakes landscape, green vineyard vines sharp in foreground receding to rolling hills and calm lake, golden hour light casting long shadows, strong depth and distance, no people, ultra-realistic landscape photography, 16:9" },
{ file: "hero-living-room.webp", prompt: "low 35mm corner angle across a spacious residential living room, plush light grey carpet sharp and textured in foreground, large sectional sofa and bay windows receding with bokeh, warm afternoon sunlight, shallow depth of field, no people, ultra-realistic interior photography, 16:9" },
{ file: "hero-clean-result.webp", prompt: "extreme low 50mm macro angle pressed to immaculate freshly cleaned residential carpet, individual carpet fibers razor sharp in foreground, pile receding into soft bokeh, raking natural light revealing deep clean texture and uniform pile height, no people, ultra-realistic macro carpet photography, 16:9" },
];
const SERVICES = [
{ file: "carpet-cleaning.webp", prompt: "low 35mm angle looking across plush clean beige carpet in a residential living room, carpet fibers sharp in foreground, couch and window receding into bokeh, warm afternoon light, shallow depth of field, no people, ultra-realistic interior photography" },
{ file: "stairs-cleaning.webp", prompt: "low 35mm angle looking up clean grey carpeted stairs from bottom step, carpet texture sharp on nearest step, stairs receding diagonally upward, wood banister, bright light from above, no people, ultra-realistic interior photography" },
{ file: "upholstery-cleaning.webp", prompt: "low 50mm angle across a clean plush linen fabric sofa arm, fabric weave sharp in foreground, living room receding with bokeh, warm light, shallow depth of field, no people, ultra-realistic interior photography" },
{ file: "floor-cleaning.webp", prompt: "low 24mm angle pressed to gleaming light oak hardwood floor, wood grain razor sharp in extreme foreground receding down hallway, natural light, no people, ultra-realistic interior photography" },
{ file: "area-rug-cleaning.webp", prompt: "low 35mm angle across a vibrant clean oriental rug from floor level, rug fibers and pattern sharp in foreground, hardwood floor and room receding, warm light, shallow depth of field, no people, ultra-realistic interior photography" },
{ file: "add-ons.webp", prompt: "low 35mm angle across clean beige bedroom carpet, carpet pile sharp in foreground, bed frame and curtained window receding with bokeh, morning light, no people, ultra-realistic interior photography" },
{ file: "commercial-overview.webp", prompt: "low 24mm angle across dark commercial carpet in a corporate lobby, carpet surface sharp in foreground receding to glass entrance, strong vanishing point, no people, ultra-realistic architectural photography" },
{ file: "vacation-rentals.webp", prompt: "low 35mm angle across clean carpet in a Finger Lakes cottage living room, carpet sharp in foreground, stone fireplace and window receding with bokeh, rustic warm decor, no people, ultra-realistic interior photography" },
{ file: "office-spaces.webp", prompt: "low 24mm angle across grey carpet tiles in a modern open office, tile seams sharp in foreground, empty desks receding with linear perspective, professional lighting, no people, ultra-realistic architectural photography" },
{ file: "hotels-inns.webp", prompt: "low 24mm angle down a hotel corridor, patterned carpet runner sharp in foreground, corridor receding to vanishing point, warm wall sconces, no people, ultra-realistic hospitality photography" },
{ file: "retail-showrooms.webp", prompt: "low 35mm diagonal angle across light grey carpet in an upscale retail showroom, carpet sharp in foreground, display fixtures and track lighting receding with bokeh, no people, ultra-realistic architectural photography" },
{ file: "property-management.webp", prompt: "low 35mm angle across fresh neutral carpet in an empty clean apartment, carpet texture sharp in foreground, white walls and windows receding, no people, ultra-realistic real estate photography" },
];
function openLB(src, name, prompt) {
document.getElementById('lb-img').src = src;
document.getElementById('lb-name').textContent = name;
document.getElementById('lb-prompt').textContent = prompt;
document.getElementById('lb').classList.add('open');
}
function closeLB() { document.getElementById('lb').classList.remove('open'); }
function buildCards(items, containerId, basePath) {
const el = document.getElementById(containerId);
items.forEach(item => {
const src = basePath + item.file;
const name = item.file.replace('.webp','');
const card = document.createElement('div');
card.className = 'card';
card.innerHTML = `<img src="${src}" loading="lazy"><div class="info"><div class="tag">FLUX.1 Schnell</div><div class="name">${name}</div><div class="prompt">${item.prompt.substring(0,120)}…</div></div>`;
card.onclick = () => openLB(src, name, item.prompt);
el.appendChild(card);
});
}
buildCards(HEROES, 'heroes', '../assets/images/hero/');
buildCards(SERVICES, 'services', '../assets/images/services/');
</script>
</body>
</html>
+35
View File
@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hero Image Review</title>
<style>
body { background: #111; color: #eee; font-family: sans-serif; padding: 20px; }
h1 { font-size: 16px; margin-bottom: 20px; }
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.item { }
.item img { width: 100%; height: 200px; object-fit: cover; border-radius: 4px; }
.item p { font-size: 12px; margin: 4px 0; color: #aaa; }
</style>
</head>
<body>
<h1>Hero Images — RealVisXL V5.0 (15 of 16)</h1>
<div class="grid">
<div class="item"><img src="../assets/images/hero/hero-carpet-cleaning.jpg"><p>hero-carpet-cleaning</p></div>
<div class="item"><img src="../assets/images/hero/hero-upholstery.jpg"><p>hero-upholstery</p></div>
<div class="item"><img src="../assets/images/hero/hero-floors.jpg"><p>hero-floors</p></div>
<div class="item"><img src="../assets/images/hero/hero-area-rugs.jpg"><p>hero-area-rugs</p></div>
<div class="item"><img src="../assets/images/hero/hero-add-ons.jpg"><p>hero-add-ons</p></div>
<div class="item"><img src="../assets/images/hero/hero-commercial.jpg"><p>hero-commercial</p></div>
<div class="item"><img src="../assets/images/hero/hero-offices.jpg"><p>hero-offices</p></div>
<div class="item"><img src="../assets/images/hero/hero-vacation-rentals.jpg"><p>hero-vacation-rentals</p></div>
<div class="item"><img src="../assets/images/hero/hero-hotels.jpg"><p>hero-hotels</p></div>
<div class="item"><img src="../assets/images/hero/hero-retail.jpg"><p>hero-retail</p></div>
<div class="item"><img src="../assets/images/hero/hero-property-management.jpg"><p>hero-property-management</p></div>
<div class="item"><img src="../assets/images/hero/hero-about.jpg"><p>hero-about</p></div>
<div class="item"><img src="../assets/images/hero/hero-service-area.jpg"><p>hero-service-area</p></div>
<div class="item"><img src="../assets/images/hero/hero-living-room.jpg"><p>hero-living-room</p></div>
<div class="item"><img src="../assets/images/hero/hero-clean-result.jpg"><p>hero-clean-result</p></div>
</div>
</body>
</html>
+125
View File
@@ -0,0 +1,125 @@
"""Single test clip — corrected WanImageToVideo workflow."""
import json, time, urllib.request, os, random
COMFY = "http://localhost:8188"
IMAGE_PATH = "assets/images/hero/hero-carpet-cleaning.webp"
OUT_DIR = "assets/videos/clips"
os.makedirs(OUT_DIR, exist_ok=True)
def upload_image(image_path):
fname = os.path.basename(image_path)
with open(image_path, "rb") as f:
img_data = f.read()
boundary = "----FormBoundary123456"
body = (
f"--{boundary}\r\n"
f'Content-Disposition: form-data; name="image"; filename="{fname}"\r\n'
f"Content-Type: image/webp\r\n\r\n"
).encode() + img_data + f"\r\n--{boundary}--\r\n".encode()
req = urllib.request.Request(
f"{COMFY}/upload/image", data=body,
headers={"Content-Type": f"multipart/form-data; boundary={boundary}"},
)
with urllib.request.urlopen(req) as resp:
result = json.loads(resp.read())
print(f" uploaded: {result['name']}")
return result["name"]
def build_workflow(image_name, prompt, frames=25):
# WanImageToVideo is a conditioning node, NOT a sampler.
# outputs: [0]=positive CONDITIONING, [1]=negative CONDITIONING, [2]=latent LATENT
# start_image is optional IMAGE — anchors first frame.
return {
"1": {"class_type": "UnetLoaderGGUF", "inputs": {"unet_name": "Wan2.2-TI2V-5B-Q4_K_M.gguf"}},
"2": {"class_type": "CLIPLoader", "inputs": {"clip_name": "umt5_xxl_fp8_e4m3fn_scaled.safetensors", "type": "wan"}},
"3": {"class_type": "VAELoader", "inputs": {"vae_name": "wan_2.1_vae.safetensors"}},
"4": {"class_type": "LoadImage", "inputs": {"image": image_name}},
"5": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["2", 0], "text": prompt}},
"6": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["2", 0], "text": "blur, low quality, distortion, text, watermark, people, jitter"}},
"7": {
"class_type": "WanImageToVideo",
"inputs": {
"positive": ["5", 0],
"negative": ["6", 0],
"vae": ["3", 0],
"start_image": ["4", 0],
"width": 832, "height": 480, "length": frames, "batch_size": 1,
},
},
"8": {
"class_type": "KSampler",
"inputs": {
"model": ["1", 0],
"positive": ["7", 0],
"negative": ["7", 1],
"latent_image": ["7", 2],
"seed": 42, "steps": 20, "cfg": 6.0,
"sampler_name": "uni_pc", "scheduler": "simple", "denoise": 1.0,
},
},
# VAEDecodeTiled handles video (5D) latents — VAEDecode only handles images (4D)
"9": {"class_type": "VAEDecodeTiled", "inputs": {"samples": ["8", 0], "vae": ["3", 0], "tile_size": 512, "overlap": 64, "temporal_size": 64, "temporal_overlap": 8}},
"10": {
"class_type": "SaveAnimatedWEBP",
"inputs": {"images": ["9", 0], "filename_prefix": "wan_test", "fps": 12, "lossless": False, "quality": 85, "method": "default"},
},
}
def queue_prompt(workflow):
data = json.dumps({"prompt": workflow}).encode()
req = urllib.request.Request(f"{COMFY}/prompt", data=data, headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())["prompt_id"]
def wait_for_result(prompt_id, timeout=1800):
start = time.time()
while time.time() - start < timeout:
with urllib.request.urlopen(f"{COMFY}/history/{prompt_id}") as resp:
hist = json.loads(resp.read())
if prompt_id in hist:
entry = hist[prompt_id]
if entry.get("status", {}).get("status_str") == "error":
print(f" ERROR: {entry['status'].get('messages', '')}")
return None
for node_out in entry.get("outputs", {}).values():
if "gifs" in node_out:
return node_out["gifs"]
if "images" in node_out:
return node_out["images"]
elapsed = int(time.time() - start)
print(f" waiting... {elapsed}s", flush=True)
time.sleep(15)
return None
def download_output(vid_info, out_path):
fname = vid_info["filename"]
subfolder = vid_info.get("subfolder", "")
img_type = vid_info.get("type", "output")
url = f"{COMFY}/view?filename={fname}&subfolder={subfolder}&type={img_type}"
with urllib.request.urlopen(url) as resp:
data = resp.read()
with open(out_path, "wb") as f:
f.write(data)
print(f" saved: {out_path} ({len(data)//1024}KB)")
print("[TEST v2] WanImageToVideo → KSampler → VAEDecode → SaveAnimatedWEBP")
image_name = upload_image(IMAGE_PATH)
workflow = build_workflow(
image_name,
"slow dolly forward across clean plush cream carpet, gentle camera push toward the far wall, warm afternoon light, cinematic smooth motion",
frames=9,
)
prompt_id = queue_prompt(workflow)
print(f" queued: {prompt_id}")
results = wait_for_result(prompt_id)
if results:
download_output(results[0], f"{OUT_DIR}/test-clip-01.webp")
print("SUCCESS")
else:
print("FAILED")