68d29ae532
- Fix mobile nav: all 3 dropdowns now get click handlers (was only first) - Remove Our Work from nav - Add Google Maps embed to homepage footer - Update title and meta description/keywords/canonical - Unique hero image per page (14 pages updated) - Remove technician clip from hero reel - Add .cpanel.yml for cPanel Git deployment - Add hero image generation script (ComfyUI SDXL) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
245 lines
10 KiB
Python
245 lines
10 KiB
Python
"""
|
|
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)
|