diff --git a/.cpanel.yml b/.cpanel.yml
index e88f51e..a072119 100644
--- a/.cpanel.yml
+++ b/.cpanel.yml
@@ -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"
diff --git a/.gitignore b/.gitignore
index 8383823..fed7c46 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@ node_modules/
build/
.DS_Store
*.log
+__pycache__/
+*.pyc
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 0000000..b34eefa
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,14 @@
+Options -Indexes
+RewriteEngine On
+
+# Deny sensitive files
+
+ Order allow,deny
+ Deny from all
+
+
+# Deny tools directory
+RewriteRule ^tools/ - [F,L]
+
+ErrorDocument 404 /404.html
+ErrorDocument 500 /500.html
diff --git a/assets/images/hero/hero-about.jpg b/assets/images/hero/hero-about.jpg
deleted file mode 100644
index df4b8a8..0000000
Binary files a/assets/images/hero/hero-about.jpg and /dev/null differ
diff --git a/assets/images/hero/hero-about.webp b/assets/images/hero/hero-about.webp
index 3dc0e26..beed56d 100644
Binary files a/assets/images/hero/hero-about.webp and b/assets/images/hero/hero-about.webp differ
diff --git a/assets/images/hero/hero-add-ons.jpg b/assets/images/hero/hero-add-ons.jpg
deleted file mode 100644
index 0a82a20..0000000
Binary files a/assets/images/hero/hero-add-ons.jpg and /dev/null differ
diff --git a/assets/images/hero/hero-add-ons.webp b/assets/images/hero/hero-add-ons.webp
index 74ee5bc..22e91a6 100644
Binary files a/assets/images/hero/hero-add-ons.webp and b/assets/images/hero/hero-add-ons.webp differ
diff --git a/assets/images/hero/hero-area-rugs.jpg b/assets/images/hero/hero-area-rugs.jpg
deleted file mode 100644
index 19addbe..0000000
Binary files a/assets/images/hero/hero-area-rugs.jpg and /dev/null differ
diff --git a/assets/images/hero/hero-area-rugs.webp b/assets/images/hero/hero-area-rugs.webp
index f3b571b..70945dc 100644
Binary files a/assets/images/hero/hero-area-rugs.webp and b/assets/images/hero/hero-area-rugs.webp differ
diff --git a/assets/images/hero/hero-before-after.webp b/assets/images/hero/hero-before-after.webp
deleted file mode 100644
index ccef775..0000000
Binary files a/assets/images/hero/hero-before-after.webp and /dev/null differ
diff --git a/assets/images/hero/hero-carpet-cleaning.webp b/assets/images/hero/hero-carpet-cleaning.webp
index 22401d7..affe8f1 100644
Binary files a/assets/images/hero/hero-carpet-cleaning.webp and b/assets/images/hero/hero-carpet-cleaning.webp differ
diff --git a/assets/images/hero/hero-clean-result.jpg b/assets/images/hero/hero-clean-result.jpg
deleted file mode 100644
index f56e0f5..0000000
Binary files a/assets/images/hero/hero-clean-result.jpg and /dev/null differ
diff --git a/assets/images/hero/hero-clean-result.webp b/assets/images/hero/hero-clean-result.webp
index b729a20..3c87030 100644
Binary files a/assets/images/hero/hero-clean-result.webp and b/assets/images/hero/hero-clean-result.webp differ
diff --git a/assets/images/hero/hero-commercial.jpg b/assets/images/hero/hero-commercial.jpg
deleted file mode 100644
index 1972305..0000000
Binary files a/assets/images/hero/hero-commercial.jpg and /dev/null differ
diff --git a/assets/images/hero/hero-commercial.webp b/assets/images/hero/hero-commercial.webp
index ee737f3..6dba9c8 100644
Binary files a/assets/images/hero/hero-commercial.webp and b/assets/images/hero/hero-commercial.webp differ
diff --git a/assets/images/hero/hero-floors.jpg b/assets/images/hero/hero-floors.jpg
deleted file mode 100644
index 0e0ea17..0000000
Binary files a/assets/images/hero/hero-floors.jpg and /dev/null differ
diff --git a/assets/images/hero/hero-floors.webp b/assets/images/hero/hero-floors.webp
index 9b7eeb0..db281bb 100644
Binary files a/assets/images/hero/hero-floors.webp and b/assets/images/hero/hero-floors.webp differ
diff --git a/assets/images/hero/hero-hotels.jpg b/assets/images/hero/hero-hotels.jpg
deleted file mode 100644
index cbc7263..0000000
Binary files a/assets/images/hero/hero-hotels.jpg and /dev/null differ
diff --git a/assets/images/hero/hero-hotels.webp b/assets/images/hero/hero-hotels.webp
index 4739bd7..6f1fbfe 100644
Binary files a/assets/images/hero/hero-hotels.webp and b/assets/images/hero/hero-hotels.webp differ
diff --git a/assets/images/hero/hero-living-room.jpg b/assets/images/hero/hero-living-room.jpg
deleted file mode 100644
index 5024663..0000000
Binary files a/assets/images/hero/hero-living-room.jpg and /dev/null differ
diff --git a/assets/images/hero/hero-living-room.webp b/assets/images/hero/hero-living-room.webp
index 358ea90..e12e590 100644
Binary files a/assets/images/hero/hero-living-room.webp and b/assets/images/hero/hero-living-room.webp differ
diff --git a/assets/images/hero/hero-offices.jpg b/assets/images/hero/hero-offices.jpg
deleted file mode 100644
index 9c1a8be..0000000
Binary files a/assets/images/hero/hero-offices.jpg and /dev/null differ
diff --git a/assets/images/hero/hero-offices.webp b/assets/images/hero/hero-offices.webp
index e43f8e6..2283d23 100644
Binary files a/assets/images/hero/hero-offices.webp and b/assets/images/hero/hero-offices.webp differ
diff --git a/assets/images/hero/hero-property-management.jpg b/assets/images/hero/hero-property-management.jpg
deleted file mode 100644
index 12dcba9..0000000
Binary files a/assets/images/hero/hero-property-management.jpg and /dev/null differ
diff --git a/assets/images/hero/hero-property-management.webp b/assets/images/hero/hero-property-management.webp
index de0b601..97cbee5 100644
Binary files a/assets/images/hero/hero-property-management.webp and b/assets/images/hero/hero-property-management.webp differ
diff --git a/assets/images/hero/hero-retail.jpg b/assets/images/hero/hero-retail.jpg
deleted file mode 100644
index bba82dc..0000000
Binary files a/assets/images/hero/hero-retail.jpg and /dev/null differ
diff --git a/assets/images/hero/hero-retail.webp b/assets/images/hero/hero-retail.webp
index 368f477..c109b45 100644
Binary files a/assets/images/hero/hero-retail.webp and b/assets/images/hero/hero-retail.webp differ
diff --git a/assets/images/hero/hero-service-area.jpg b/assets/images/hero/hero-service-area.jpg
deleted file mode 100644
index 9eb2341..0000000
Binary files a/assets/images/hero/hero-service-area.jpg and /dev/null differ
diff --git a/assets/images/hero/hero-service-area.webp b/assets/images/hero/hero-service-area.webp
index 3d3a9cf..8d8e5b6 100644
Binary files a/assets/images/hero/hero-service-area.webp and b/assets/images/hero/hero-service-area.webp differ
diff --git a/assets/images/hero/hero-stairs.webp b/assets/images/hero/hero-stairs.webp
index d4e3413..aa1d18f 100644
Binary files a/assets/images/hero/hero-stairs.webp and b/assets/images/hero/hero-stairs.webp differ
diff --git a/assets/images/hero/hero-technician.webp b/assets/images/hero/hero-technician.webp
deleted file mode 100644
index 5a917ed..0000000
Binary files a/assets/images/hero/hero-technician.webp and /dev/null differ
diff --git a/assets/images/hero/hero-upholstery.jpg b/assets/images/hero/hero-upholstery.jpg
deleted file mode 100644
index de9b4a6..0000000
Binary files a/assets/images/hero/hero-upholstery.jpg and /dev/null differ
diff --git a/assets/images/hero/hero-upholstery.webp b/assets/images/hero/hero-upholstery.webp
index 749a36f..d0abb15 100644
Binary files a/assets/images/hero/hero-upholstery.webp and b/assets/images/hero/hero-upholstery.webp differ
diff --git a/assets/images/hero/hero-vacation-rentals.jpg b/assets/images/hero/hero-vacation-rentals.jpg
deleted file mode 100644
index b7b5945..0000000
Binary files a/assets/images/hero/hero-vacation-rentals.jpg and /dev/null differ
diff --git a/assets/images/hero/hero-vacation-rentals.webp b/assets/images/hero/hero-vacation-rentals.webp
index 3735e3e..bc24d6f 100644
Binary files a/assets/images/hero/hero-vacation-rentals.webp and b/assets/images/hero/hero-vacation-rentals.webp differ
diff --git a/assets/images/services/add-ons.webp b/assets/images/services/add-ons.webp
index d4cb2f9..367e1c5 100644
Binary files a/assets/images/services/add-ons.webp and b/assets/images/services/add-ons.webp differ
diff --git a/assets/images/services/area-rug-cleaning.webp b/assets/images/services/area-rug-cleaning.webp
index f72d6dc..020634d 100644
Binary files a/assets/images/services/area-rug-cleaning.webp and b/assets/images/services/area-rug-cleaning.webp differ
diff --git a/assets/images/services/carpet-cleaning.webp b/assets/images/services/carpet-cleaning.webp
index 0f62e36..ce3468d 100644
Binary files a/assets/images/services/carpet-cleaning.webp and b/assets/images/services/carpet-cleaning.webp differ
diff --git a/assets/images/services/commercial-overview.webp b/assets/images/services/commercial-overview.webp
index c70b9b9..aad5d0c 100644
Binary files a/assets/images/services/commercial-overview.webp and b/assets/images/services/commercial-overview.webp differ
diff --git a/assets/images/services/floor-cleaning.webp b/assets/images/services/floor-cleaning.webp
index 88c93fe..e798494 100644
Binary files a/assets/images/services/floor-cleaning.webp and b/assets/images/services/floor-cleaning.webp differ
diff --git a/assets/images/services/hotels-inns.webp b/assets/images/services/hotels-inns.webp
index 6d9caf5..a075983 100644
Binary files a/assets/images/services/hotels-inns.webp and b/assets/images/services/hotels-inns.webp differ
diff --git a/assets/images/services/office-spaces.webp b/assets/images/services/office-spaces.webp
index 4aefec9..fff3b29 100644
Binary files a/assets/images/services/office-spaces.webp and b/assets/images/services/office-spaces.webp differ
diff --git a/assets/images/services/property-management.webp b/assets/images/services/property-management.webp
index f4b173e..c0f2dda 100644
Binary files a/assets/images/services/property-management.webp and b/assets/images/services/property-management.webp differ
diff --git a/assets/images/services/retail-showrooms.webp b/assets/images/services/retail-showrooms.webp
index 06f666d..cd0e62c 100644
Binary files a/assets/images/services/retail-showrooms.webp and b/assets/images/services/retail-showrooms.webp differ
diff --git a/assets/images/services/stairs-cleaning.webp b/assets/images/services/stairs-cleaning.webp
index bbb0deb..4664c43 100644
Binary files a/assets/images/services/stairs-cleaning.webp and b/assets/images/services/stairs-cleaning.webp differ
diff --git a/assets/images/services/upholstery-cleaning.webp b/assets/images/services/upholstery-cleaning.webp
index c418ad9..f57139e 100644
Binary files a/assets/images/services/upholstery-cleaning.webp and b/assets/images/services/upholstery-cleaning.webp differ
diff --git a/assets/images/services/vacation-rentals.webp b/assets/images/services/vacation-rentals.webp
index afbdc54..0805045 100644
Binary files a/assets/images/services/vacation-rentals.webp and b/assets/images/services/vacation-rentals.webp differ
diff --git a/tools/__pycache__/build-paced-reel.cpython-313.pyc b/tools/__pycache__/build-paced-reel.cpython-313.pyc
deleted file mode 100644
index 6772f0b..0000000
Binary files a/tools/__pycache__/build-paced-reel.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/build-reel-server.cpython-313.pyc b/tools/__pycache__/build-reel-server.cpython-313.pyc
deleted file mode 100644
index 7dd4cf6..0000000
Binary files a/tools/__pycache__/build-reel-server.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/convert-to-webp.cpython-313.pyc b/tools/__pycache__/convert-to-webp.cpython-313.pyc
deleted file mode 100644
index 3bdcd84..0000000
Binary files a/tools/__pycache__/convert-to-webp.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/gen-hero-images.cpython-313.pyc b/tools/__pycache__/gen-hero-images.cpython-313.pyc
deleted file mode 100644
index fc2d3d3..0000000
Binary files a/tools/__pycache__/gen-hero-images.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/gen-images-comfyui.cpython-313.pyc b/tools/__pycache__/gen-images-comfyui.cpython-313.pyc
deleted file mode 100644
index 8e7d197..0000000
Binary files a/tools/__pycache__/gen-images-comfyui.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/gen-images.cpython-313.pyc b/tools/__pycache__/gen-images.cpython-313.pyc
deleted file mode 100644
index 08c8fe8..0000000
Binary files a/tools/__pycache__/gen-images.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/gen-locations.cpython-313.pyc b/tools/__pycache__/gen-locations.cpython-313.pyc
deleted file mode 100644
index bfcaae2..0000000
Binary files a/tools/__pycache__/gen-locations.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/gen-missing-2.cpython-313.pyc b/tools/__pycache__/gen-missing-2.cpython-313.pyc
deleted file mode 100644
index 44be8c2..0000000
Binary files a/tools/__pycache__/gen-missing-2.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/gen-service-images.cpython-313.pyc b/tools/__pycache__/gen-service-images.cpython-313.pyc
deleted file mode 100644
index 423f70e..0000000
Binary files a/tools/__pycache__/gen-service-images.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/gen-video.cpython-313.pyc b/tools/__pycache__/gen-video.cpython-313.pyc
deleted file mode 100644
index 785ef37..0000000
Binary files a/tools/__pycache__/gen-video.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/regen-3shots.cpython-313.pyc b/tools/__pycache__/regen-3shots.cpython-313.pyc
deleted file mode 100644
index dccf22c..0000000
Binary files a/tools/__pycache__/regen-3shots.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/regen-commercial-overview.cpython-313.pyc b/tools/__pycache__/regen-commercial-overview.cpython-313.pyc
deleted file mode 100644
index c89ae30..0000000
Binary files a/tools/__pycache__/regen-commercial-overview.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/regen-full-reel.cpython-313.pyc b/tools/__pycache__/regen-full-reel.cpython-313.pyc
deleted file mode 100644
index 07fd19a..0000000
Binary files a/tools/__pycache__/regen-full-reel.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/regen-images-targeted.cpython-313.pyc b/tools/__pycache__/regen-images-targeted.cpython-313.pyc
deleted file mode 100644
index 9f93eee..0000000
Binary files a/tools/__pycache__/regen-images-targeted.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/regen-industrial.cpython-313.pyc b/tools/__pycache__/regen-industrial.cpython-313.pyc
deleted file mode 100644
index fb01125..0000000
Binary files a/tools/__pycache__/regen-industrial.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/regen-shot.cpython-313.pyc b/tools/__pycache__/regen-shot.cpython-313.pyc
deleted file mode 100644
index fe56598..0000000
Binary files a/tools/__pycache__/regen-shot.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/regen-shot02.cpython-313.pyc b/tools/__pycache__/regen-shot02.cpython-313.pyc
deleted file mode 100644
index 16c1d39..0000000
Binary files a/tools/__pycache__/regen-shot02.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/regen-shot04.cpython-313.pyc b/tools/__pycache__/regen-shot04.cpython-313.pyc
deleted file mode 100644
index a909bcf..0000000
Binary files a/tools/__pycache__/regen-shot04.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/regen-v3-shot04.cpython-313.pyc b/tools/__pycache__/regen-v3-shot04.cpython-313.pyc
deleted file mode 100644
index ce701ac..0000000
Binary files a/tools/__pycache__/regen-v3-shot04.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/regen-v3.cpython-313.pyc b/tools/__pycache__/regen-v3.cpython-313.pyc
deleted file mode 100644
index bbcf67e..0000000
Binary files a/tools/__pycache__/regen-v3.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/regen-v4.cpython-313.pyc b/tools/__pycache__/regen-v4.cpython-313.pyc
deleted file mode 100644
index 74ca987..0000000
Binary files a/tools/__pycache__/regen-v4.cpython-313.pyc and /dev/null differ
diff --git a/tools/__pycache__/regen-veo31.cpython-313.pyc b/tools/__pycache__/regen-veo31.cpython-313.pyc
deleted file mode 100644
index d714ea5..0000000
Binary files a/tools/__pycache__/regen-veo31.cpython-313.pyc and /dev/null differ
diff --git a/tools/gen-hero-images.py b/tools/gen-hero-images.py
deleted file mode 100644
index f293de7..0000000
--- a/tools/gen-hero-images.py
+++ /dev/null
@@ -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)
diff --git a/tools/gen-images-comfyui.py b/tools/gen-images-comfyui.py
deleted file mode 100644
index c9865bb..0000000
--- a/tools/gen-images-comfyui.py
+++ /dev/null
@@ -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.")
diff --git a/tools/gen-images-flux.py b/tools/gen-images-flux.py
new file mode 100644
index 0000000..26fdbaf
--- /dev/null
+++ b/tools/gen-images-flux.py
@@ -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)
diff --git a/tools/gen-images.py b/tools/gen-images.py
deleted file mode 100644
index a80529f..0000000
--- a/tools/gen-images.py
+++ /dev/null
@@ -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/")
diff --git a/tools/gen-missing-2.py b/tools/gen-missing-2.py
deleted file mode 100644
index f29a23b..0000000
--- a/tools/gen-missing-2.py
+++ /dev/null
@@ -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.")
diff --git a/tools/gen-video-wan.py b/tools/gen-video-wan.py
new file mode 100644
index 0000000..5c46300
--- /dev/null
+++ b/tools/gen-video-wan.py
@@ -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")
diff --git a/tools/pipeline.html b/tools/pipeline.html
new file mode 100644
index 0000000..3cdc88f
--- /dev/null
+++ b/tools/pipeline.html
@@ -0,0 +1,151 @@
+
+
+
+
+Image Gen Pipeline
+
+
+
+
+Lahr Carpet Cleaning — Image Generation Pipeline
+
+Model Stack
+
+
+
Prompt
+
gen-images-flux.py
+
28 images (16 hero + 12 svc)
+
+
→
+
+
API
+
ComfyUI
+
localhost:8188
+
+
→
+
+
UNet (model)
+
FLUX.1 Schnell
+
Q8_0 GGUF · 12GB · 12B params
+
+
→
+
+
Sampler
+
KSampler
+
4 steps · euler · cfg=1.0
+
+
→
+
+
Decode
+
FLUX AE
+
ae.safetensors · 108MB
+
+
→
+
+
Output
+
JPEG → WebP
+
1024×576 · q92 → q80
+
+
+
+Text Encoders
+
+
+
CLIP-L
+
clip_l.safetensors
+
235MB · short prompts
+
+
+
+
+
T5-XXL fp8
+
t5xxl_fp8_e4m3fn
+
4.6GB · long prompt understanding
+
+
→
+
+
Node
+
DualCLIPLoader
+
type: flux
+
+
+
+Hardware
+
+
+
Execution
CPU only (VRAM too small)
+
+
Total ETA
~1h50m for 28 images
+
+
+Model Files on Disk
+
+
UNet
flux1-schnell-Q8_0.gguf · 12GB
+
T5-XXL
t5xxl_fp8_e4m3fn.safetensors · 4.6GB
+
CLIP-L
clip_l.safetensors · 235MB
+
VAE
ae.safetensors · 108MB (official BFL)
+
+
+Generation Progress
+
+
+
14% — reload page to update
+
+
+Prompt Strategy
+
+
Low-angle perspective (35mm / 24mm lens specified in prompt)
+
Carpet/floor texture sharp in foreground — subject recedes into bokeh
+
Shallow depth of field + vanishing point for depth cues
+
No people, no machines, no equipment
+
Finger Lakes / upstate NY context for residential scenes
+
+
Previous model: RealVisXL V5.0 fp16 (SDXL 3.5B) — rejected: flat angles, poor depth
+
Current model: FLUX.1 Schnell (12B transformer) — better spatial understanding
+
+
+
+
diff --git a/tools/regen-3shots.py b/tools/regen-3shots.py
deleted file mode 100644
index 8916696..0000000
--- a/tools/regen-3shots.py
+++ /dev/null
@@ -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.")
diff --git a/tools/regen-commercial-overview.py b/tools/regen-commercial-overview.py
deleted file mode 100644
index 672f119..0000000
--- a/tools/regen-commercial-overview.py
+++ /dev/null
@@ -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.")
diff --git a/tools/regen-full-reel.py b/tools/regen-full-reel.py
deleted file mode 100644
index cbecc11..0000000
--- a/tools/regen-full-reel.py
+++ /dev/null
@@ -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.")
diff --git a/tools/regen-images-targeted.py b/tools/regen-images-targeted.py
deleted file mode 100644
index 58f2ff2..0000000
--- a/tools/regen-images-targeted.py
+++ /dev/null
@@ -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.")
diff --git a/tools/regen-industrial.py b/tools/regen-industrial.py
deleted file mode 100644
index d459148..0000000
--- a/tools/regen-industrial.py
+++ /dev/null
@@ -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.")
diff --git a/tools/regen-shot.py b/tools/regen-shot.py
deleted file mode 100644
index 9ed4894..0000000
--- a/tools/regen-shot.py
+++ /dev/null
@@ -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:]}")
diff --git a/tools/regen-shot02.py b/tools/regen-shot02.py
deleted file mode 100644
index 48c9237..0000000
--- a/tools/regen-shot02.py
+++ /dev/null
@@ -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.")
diff --git a/tools/regen-shot04.py b/tools/regen-shot04.py
deleted file mode 100644
index 566a1e1..0000000
--- a/tools/regen-shot04.py
+++ /dev/null
@@ -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.")
diff --git a/tools/regen-v3-shot04.py b/tools/regen-v3-shot04.py
deleted file mode 100644
index 35459c8..0000000
--- a/tools/regen-v3-shot04.py
+++ /dev/null
@@ -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.")
diff --git a/tools/regen-v3.py b/tools/regen-v3.py
deleted file mode 100644
index 3f3cb1a..0000000
--- a/tools/regen-v3.py
+++ /dev/null
@@ -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.")
diff --git a/tools/regen-v4.py b/tools/regen-v4.py
deleted file mode 100644
index b5e1288..0000000
--- a/tools/regen-v4.py
+++ /dev/null
@@ -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.")
diff --git a/tools/regen-veo31.py b/tools/regen-veo31.py
deleted file mode 100644
index 67b2377..0000000
--- a/tools/regen-veo31.py
+++ /dev/null
@@ -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.")
diff --git a/tools/review-all.html b/tools/review-all.html
new file mode 100644
index 0000000..55b29cc
--- /dev/null
+++ b/tools/review-all.html
@@ -0,0 +1,117 @@
+
+
+
+
+Image Review — All 28
+
+
+
+
+
+
+Hero Images (16)
+
+
+Service Cards (12)
+
+
+
+
![]()
+
+
+
FLUX.1 Schnell · Q8_0 GGUF · 4 steps · euler · cfg=1.0
+
+
+
✕
+
+
+
+
+
diff --git a/tools/review-heroes.html b/tools/review-heroes.html
new file mode 100644
index 0000000..6a3bd0e
--- /dev/null
+++ b/tools/review-heroes.html
@@ -0,0 +1,35 @@
+
+
+
+
+Hero Image Review
+
+
+
+Hero Images — RealVisXL V5.0 (15 of 16)
+
+

hero-carpet-cleaning
+

hero-upholstery
+

hero-floors
+

hero-area-rugs
+

hero-add-ons
+

hero-commercial
+

hero-offices
+

hero-vacation-rentals
+

hero-hotels
+

hero-retail
+

hero-property-management
+

hero-about
+

hero-service-area
+

hero-living-room
+

hero-clean-result
+
+
+
diff --git a/tools/wan-test-v2.py b/tools/wan-test-v2.py
new file mode 100644
index 0000000..cb04e57
--- /dev/null
+++ b/tools/wan-test-v2.py
@@ -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")