- Flask frontend + serialized GPU worker + BTCPay invoice/webhook - 12 example images rendered from the real ComfyUI workflows - fix: ipadapter workflow uses modern IPAdapter node (was broken IPAdapterApply) - helper scripts: generate/optimize examples - README with deploy recipe
161 lines
6.1 KiB
Python
161 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Generate one example image per Comfy App workflow and save to static/examples/.
|
|
Uses the real comfy_apps functions + comfy_workflows_lib builders against nightmare.
|
|
"""
|
|
import os, sys, shutil, time, json
|
|
from PIL import Image, ImageFilter, ImageDraw, ImageOps
|
|
|
|
sys.path.insert(0, os.path.expanduser("~/auto-publisher/core"))
|
|
import comfy_apps as CA
|
|
import comfy_workflows_lib as CL
|
|
|
|
BASE = os.path.expanduser("~/comfy-web")
|
|
EXAMPLES = os.path.join(BASE, "static", "examples")
|
|
os.makedirs(EXAMPLES, exist_ok=True)
|
|
|
|
def upload(local, name):
|
|
CL.upload_image(local, name=name)
|
|
print(f" uploaded {name}")
|
|
|
|
def save_result(app, local_path):
|
|
if not local_path or not os.path.exists(local_path):
|
|
print(f" [{app}] NO RESULT")
|
|
return False
|
|
ext = os.path.splitext(local_path)[1] or ".png"
|
|
dest = os.path.join(EXAMPLES, app + ext)
|
|
shutil.copy(local_path, dest)
|
|
print(f" [{app}] -> {dest} ({os.path.getsize(dest)//1024} KB)")
|
|
return True
|
|
|
|
def gen(app, fn, **params):
|
|
print(f"[{app}] building workflow...")
|
|
wf = fn(**params)
|
|
pid, err = CL.submit(wf)
|
|
if err:
|
|
print(f"[{app}] SUBMIT ERROR: {json.dumps(err)[:300]}")
|
|
return None
|
|
print(f"[{app}] pid={pid}, rendering...")
|
|
paths = CL.fetch_result(pid, timeout=900)
|
|
if not paths:
|
|
print(f"[{app}] TIMEOUT/FAILED")
|
|
return None
|
|
save_result(app, paths[0])
|
|
return paths[0]
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# free GPU first
|
|
# ---------------------------------------------------------------------------
|
|
print("freeing GPU (unload ollama, apply comfy OC)...")
|
|
CL.free_gpu()
|
|
time.sleep(2)
|
|
|
|
results = {}
|
|
|
|
# 1. Text-to-image bases
|
|
print("\n=== BASE T2I ===")
|
|
results["logo"] = gen("logo", CA.app_logo,
|
|
prompt="minimalist geometric logo mark of a phoenix rising from flames, gold metallic on deep black, symmetrical vector emblem, clean lines, no text",
|
|
steps=8)
|
|
results["hero"] = gen("hero", CA.app_hero,
|
|
prompt="epic cinematic fantasy landscape, floating islands with waterfalls at golden sunset, volumetric god rays, dramatic clouds, ultra detailed",
|
|
steps=20)
|
|
results["article"] = gen("article", CA.app_article,
|
|
prompt="photorealistic over-the-shoulder shot of a barista pouring latte art in a sunlit cafe, shallow depth of field, morning light, editorial photography",
|
|
steps=8)
|
|
results["portrait"] = gen("portrait", CA.app_portrait,
|
|
prompt="professional studio portrait of a woman in her 30s, soft window light, warm tones, sharp focus, 85mm, head and shoulders",
|
|
steps=8)
|
|
|
|
# upload base images to ComfyUI input/ for the derived apps
|
|
print("\n=== UPLOADING BASES ===")
|
|
names = {}
|
|
for app, p in results.items():
|
|
if p and os.path.exists(p):
|
|
n = f"base_{app}.png"
|
|
upload(p, n)
|
|
names[app] = n
|
|
|
|
# make a low-res version of the portrait for upscale/restore_face demo
|
|
lowres_path = None
|
|
if results.get("portrait"):
|
|
im = Image.open(results["portrait"]).convert("RGB")
|
|
im = im.resize((192, 256), Image.LANCZOS)
|
|
lowres_path = os.path.join(BASE, "uploads", "base_lowres.png")
|
|
im.save(lowres_path)
|
|
upload(lowres_path, "base_lowres.png")
|
|
print(" created low-res portrait (192x256) for upscale demo")
|
|
|
|
# make an inpaint mask (white blob on black) for the article image
|
|
mask_path = os.path.join(BASE, "uploads", "inpaint_mask.png")
|
|
if results.get("article"):
|
|
a = Image.open(results["article"])
|
|
m = Image.new("L", a.size, 0)
|
|
d = ImageDraw.Draw(m)
|
|
w, h = a.size
|
|
d.ellipse([w*0.35, h*0.25, w*0.75, h*0.75], fill=255)
|
|
m.save(mask_path)
|
|
upload(mask_path, "inpaint_mask.png")
|
|
print(" created inpaint mask")
|
|
|
|
# make a canny-ish control image from the article
|
|
control_path = os.path.join(BASE, "uploads", "control_canny.png")
|
|
if results.get("article"):
|
|
a = Image.open(results["article"]).convert("L")
|
|
e = a.filter(ImageFilter.FIND_EDGES)
|
|
e = ImageOps.autocontrast(e)
|
|
e.save(control_path)
|
|
upload(control_path, "control_canny.png")
|
|
print(" created control edge image")
|
|
|
|
# 2. Derived apps
|
|
print("\n=== DERIVED ===")
|
|
if names.get("hero"):
|
|
results["banner"] = gen("banner", CA.app_banner,
|
|
image_name=names["hero"], prompt="epic cinematic fantasy landscape, floating islands, golden sunset, seamless extension",
|
|
left=192, right=192, steps=25)
|
|
|
|
if names.get("portrait"):
|
|
results["outpaint"] = gen("outpaint", CA.app_outpaint,
|
|
image_name=names["portrait"], prompt="extend the studio backdrop with soft bokeh lights, seamless",
|
|
left=96, right=96, top=64, bottom=64, steps=25)
|
|
|
|
if names.get("article") and os.path.exists(mask_path):
|
|
results["inpaint"] = gen("inpaint", CA.app_inpaint,
|
|
image_name=names["article"], mask_name="inpaint_mask.png",
|
|
prompt="a glowing golden espresso machine with steam", steps=20, denoise=0.7)
|
|
|
|
if lowres_path:
|
|
results["upscale"] = gen("upscale", CA.app_upscale,
|
|
image_name="base_lowres.png", upscaler="4x-UltraSharp.pth")
|
|
results["restore_face"] = gen("restore_face", CA.app_restore_face,
|
|
image_name="base_lowres.png", upscaler="RealESRGAN_x4.pth")
|
|
|
|
if names.get("hero"):
|
|
results["ipadapter"] = gen("ipadapter", CA.app_ipadapter,
|
|
style_image=names["hero"],
|
|
prompt="a cozy cottage by a lake at sunset, same painterly style", steps=20, cfg=7.0)
|
|
|
|
if os.path.exists(control_path):
|
|
results["controlnet"] = gen("controlnet", CA.app_controlnet,
|
|
prompt="a detailed illustration of a barista in a cafe, line art", control_image="control_canny.png",
|
|
control_type="canny", steps=20, cfg=7.0)
|
|
|
|
if names.get("portrait"):
|
|
try:
|
|
results["face_swap"] = gen("face_swap", CA.app_face_swap,
|
|
face_image=names["portrait"],
|
|
prompt="a samurai warrior in full ornate armor, studio lighting, dramatic", steps=20)
|
|
except Exception as e:
|
|
print(f"[face_swap] ERROR: {e}")
|
|
|
|
# restore GPU
|
|
print("\nrestoring GPU (ollama OC profile)...")
|
|
CL.restore_gpu()
|
|
|
|
# summary
|
|
print("\n=== SUMMARY ===")
|
|
for app, p in results.items():
|
|
status = "OK" if (p and os.path.exists(p)) else "MISSING"
|
|
print(f" {app:14s} {status}")
|