Comfy Apps: 12-workflow image-gen web GUI + payment frontend
- 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
This commit is contained in:
160
generate_examples.py
Normal file
160
generate_examples.py
Normal file
@@ -0,0 +1,160 @@
|
||||
#!/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}")
|
||||
Reference in New Issue
Block a user