Skip to content

Instantly share code, notes, and snippets.

@JupyterJones
Created May 13, 2026 02:59
Show Gist options
  • Select an option

  • Save JupyterJones/78eb2b811a0bbc3ee803bb5ea10e4c8b to your computer and use it in GitHub Desktop.

Select an option

Save JupyterJones/78eb2b811a0bbc3ee803bb5ea10e4c8b to your computer and use it in GitHub Desktop.
Comfy_UI_for experimenting with poses 384x512 and 512x768 versions
""" quickpose_lab384x512.py
This issue happens because the browser doesn't know you've uploaded a new image
until the entire 6-minute render is finished and the page refreshes.
To fix this, I have added a "Pre-upload" feature. As soon as you select a pose
file, it is sent to the server and displayed in the "Pose Image" box instantly.
This confirms the app has the right pose before you commit to the 6-minute
render.
The Complete Fixed App
"""
#!/usr/bin/env python3
import os
import json
import time
import random
import shutil
import requests
from flask import (
Flask,
request,
render_template_string,
send_from_directory,
redirect,
jsonify
)
app = Flask(__name__)
# =========================================================
# CONFIG
# =========================================================
COMFYUI_URL = "http://192.168.1.41:5001"
# Ensure these filenames match your ComfyUI models exactly
#CHECKPOINT = "dreamshaper_8.safetensors"
CHECKPOINT= "animerge_v50.safetensors"
#CONTROLNET = "control_v11p_sd15_openpose.pth"
#CONTROLNET = "control_v11p_sd15_canny.pth"
CONTROLNET = "control_v11p_sd15_scribble.pth"
BASE_DIR = os.getcwd()
INPUT_DIR = os.path.join(BASE_DIR, "static", "input")
LOCAL_RESULTS_DIR = os.path.join(BASE_DIR, "static", "local_results")
# "Active" files used for the CURRENT render
ACTIVE_REF_PATH = os.path.join(INPUT_DIR, "active_reference.png")
ACTIVE_POSE_PATH = os.path.join(INPUT_DIR, "active_pose.png")
os.makedirs(INPUT_DIR, exist_ok=True)
os.makedirs(LOCAL_RESULTS_DIR, exist_ok=True)
# =========================================================
# HTML
# =========================================================
HTML = """
<!DOCTYPE html>
<html>
<head>
<title>AI Stop Motion Lab</title>
<style>
body { font-family: sans-serif; background: #78c740; max-width: 1200px; margin: auto; padding: 20px; }
.card { background: white; padding: 20px; border-radius: 12px; margin-bottom: 20px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
.grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; }
img { width: 100%; border-radius: 8px; border: 1px solid #ddd; background: #fafafa; height: 300px; object-fit: contain; }
button { width: 100%; padding: 12px; border-radius: 6px; border: none; cursor: pointer; font-weight: bold; background: #4a90e2; color: white; margin-top: 10px; }
.btn-green { background: #28a745; }
#status-area { padding: 15px; border-radius: 8px; margin-bottom: 20px; display: none; font-weight: bold; text-align: center; background: #fff3cd; border: 1px solid #ffeeba; }
.loading-spinner { border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 24px; height: 24px; animation: spin 1s linear infinite; margin: 10px auto; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 15px; }
.gallery-item { text-align: center; background: #fff; padding: 10px; border-radius: 8px; border: 1px solid #ddd; }
.gallery-item img { height: 180px; }
</style>
</head>
<body>
<h1>🎬 AI Stop Motion Lab v7</h1>
<p>quickpose_lab5.py port=5200 CONTROLNET = "control_v11p_sd15_scribble.pth"</p>
<p>full body caricature of an old man with beard and mustache dancing, high detail, cinematic, consistent character no image use .35</p>
<p>a head to toe whole body image of a druid warrior women,with leather shoes, ((whole body image))</p>
<div id="status-area">
<div id="status-text">Ready</div>
<div class="loading-spinner"></div>
</div>
<div class="grid">
<div class="card">
<h3>1. Settings</h3>
<form id="gen-form">
<label><b>Upload & Preview Pose:</b></label>
<input type="file" id="pose-selector" name="pose" required style="margin: 10px 0;">
<label><b>Prompt:</b></label>
<textarea name="prompt" rows="3" style="width:100%; border:1px solid #ccc; border-radius:4px;">a head to toe whole body image of a druid warrior women,with leather shoes, ((whole body image))</textarea>
<label style="display:block; margin-top:10px;">
Denoise (Consistency): <output style="font-weight: bold; color: #4a90e2;">0.85</output>
<div style="display: flex; align-items: center; gap: 10px; font-size: 11px; color: #666;">
<span>Strict</span>
<input type="range" name="denoise" min="0.1" max="1.0" step="0.05" value="0.85"
style="flex-grow: 1;"
oninput="this.parentElement.previousElementSibling.value = this.value">
<span>Loose</span>
</div>
</label>
<button type="submit" id="gen-btn" class="btn-green">GENERATE (4-6 MINS)</button>
</form>
</div>
<div class="card">
<h3>2. Active Reference</h3>
{% if has_ref %}
<img id="ref-preview" src="/input/active_reference.png?c={{ ts }}">
<button onclick="location.href='/clear_ref'" style="background:#6c757d;">Clear Reference</button>
{% else %}
<div id="ref-placeholder" style="height:300px; background:#eee; display:flex; align-items:center; justify-content:center; border-radius:8px; color:#666;">No Reference (Fresh Start)</div>
{% endif %}
</div>
<div class="card">
<h3>3. Active Pose Image</h3>
<img id="pose-preview" src="/input/active_pose.png?c={{ ts }}" {{ ts }}style="{% if not has_pose %}display:none;{% endif %}">
{% if not has_pose %}
<div id="pose-placeholder" style="height:300px; background:#eee; display:flex; align-items:center; justify-content:center; border-radius:8px; color:#666;">No Pose Selected</div>
{% endif %}
</div>
</div>
<div class="card">
<h3>History Gallery</h3>
<div class="gallery">
{% for f in gallery %}
<div class="gallery-item">
<img src="static/local_results/{{ f }}">
<button onclick="location.href='/use/{{ f }}'">Set as Ref</button>
</div>
{% endfor %}
</div>
</div>
<script>
const poseSelector = document.getElementById('pose-selector');
const posePreview = document.getElementById('pose-preview');
const posePlaceholder = document.getElementById('pose-placeholder');
const genForm = document.getElementById('gen-form');
const genBtn = document.getElementById('gen-btn');
const statusArea = document.getElementById('status-area');
const statusText = document.getElementById('status-text');
// IMMEDIATE POSE PREVIEW
poseSelector.onchange = async () => {
if (poseSelector.files && poseSelector.files[0]) {
const formData = new FormData();
formData.append('pose', poseSelector.files[0]);
// Upload immediately so we can see it
const res = await fetch('/upload_pose_only', { method: 'POST', body: formData });
if (res.ok) {
posePreview.src = "/input/active_pose.png?c=" + new Date().getTime();
posePreview.style.display = "block";
if(posePlaceholder) posePlaceholder.style.display = "none";
}
}
};
// GENERATION PROCESS
genForm.onsubmit = async (e) => {
e.preventDefault();
genBtn.disabled = true;
statusArea.style.display = 'block';
statusText.innerText = "Step 1/3: Sending workflow to ComfyUI...";
const formData = new FormData(genForm);
const res = await fetch('/generate', { method: 'POST', body: formData });
const data = await res.json();
if (data.error) {
alert("Error: " + data.error);
location.reload();
return;
}
let seconds = 0;
const interval = setInterval(async () => {
seconds += 5;
const check = await fetch('/check/' + data.prompt_id);
const status = await check.json();
if (status.status === 'done') {
clearInterval(interval);
statusText.innerText = "Step 3/3: Render finished! Fetching image...";
await fetch('/finalize/' + status.filename);
location.reload();
} else {
statusText.innerText = `Step 2/3: CPU Rendering... (~${seconds}s elapsed / 360s expected)`;
}
}, 5000);
};
</script>
</body>
</html>
"""
# =========================================================
# BACKEND
# =========================================================
@app.route("/")
def index():
gallery = sorted(
[f for f in os.listdir(LOCAL_RESULTS_DIR) if f.endswith(".png")],
key=lambda x: os.path.getctime(os.path.join(LOCAL_RESULTS_DIR, x)),
reverse=True)
return render_template_string(HTML, gallery=gallery,
has_ref=os.path.exists(ACTIVE_REF_PATH),
has_pose=os.path.exists(ACTIVE_POSE_PATH),
ts=time.time())
@app.route("/input/<f>")
def input_f(f): return send_from_directory(INPUT_DIR, f)
@app.route("/results/<f>")
def results(f): return send_from_directory(LOCAL_RESULTS_DIR, f)
@app.route("/clear_ref")
def clear_ref():
if os.path.exists(ACTIVE_REF_PATH): os.remove(ACTIVE_REF_PATH)
return redirect("/")
@app.route("/use/<f>")
def use(f):
shutil.copy(os.path.join(LOCAL_RESULTS_DIR, f), ACTIVE_REF_PATH)
return redirect("/")
@app.route("/upload_pose_only", methods=["POST"])
def upload_pose_only():
if 'pose' in request.files:
file = request.files['pose']
file.save(ACTIVE_POSE_PATH)
return jsonify({"status": "ok"})
return jsonify({"status": "error"}), 400
@app.route("/generate", methods=["POST"])
def generate():
try:
ts = int(time.time())
prompt_text = request.form.get("prompt")
denoise = float(request.form.get("denoise"))
# 1. Use the pose already uploaded by the previewer
if not os.path.exists(ACTIVE_POSE_PATH):
return jsonify({"error": "No pose image found. Please select a file."})
# Give the pose a unique name for ComfyUI's input folder
pose_name = f"pose_{ts}.png"
with open(ACTIVE_POSE_PATH, "rb") as f:
requests.post(f"{COMFYUI_URL}/upload/image", files={"image": (pose_name, f)})
# 2. Setup Reference
has_ref = os.path.exists(ACTIVE_REF_PATH)
if has_ref:
ref_name = f"ref_{ts}.png"
with open(ACTIVE_REF_PATH, "rb") as f:
requests.post(f"{COMFYUI_URL}/upload/image", files={"image": (ref_name, f)})
latent_node = {"class_type": "VAEEncode", "inputs": {"pixels": ["11", 0], "vae": ["1", 2]}}
image_to_load = ref_name
else:
denoise = 1.0
latent_node = {"class_type": "EmptyLatentImage", "inputs": {"width": 384, "height": 512, "batch_size": 1}}
image_to_load = pose_name # Just a dummy to keep Node 11 from crashing if it exists
# 3. Build Workflow
workflow = {
"1": {"class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": CHECKPOINT}},
"2": {"class_type": "LoadImage", "inputs": {"image": pose_name}},
"3": {"class_type": "ControlNetLoader", "inputs": {"control_net_name": CONTROLNET}},
"4": {"class_type": "CLIPTextEncode", "inputs": {"text": prompt_text, "clip": ["1", 1]}},
"5": {"class_type": "CLIPTextEncode", "inputs": {"text": "NSFW, nudity, nipples, large breasts, low quality, out of frame, blurry, distorted", "clip": ["1", 1]}},
"6": {"class_type": "ControlNetApply", "inputs": {"strength": 0.8, "conditioning": ["4", 0], "control_net": ["3", 0], "image": ["2", 0]}},
"8": {"class_type": "KSampler", "inputs": {
"seed": random.randint(1, 99999999), "steps": 20, "cfg": 7, "sampler_name": "euler",
"scheduler": "normal", "denoise": denoise, "model": ["1", 0],
"positive": ["6", 0], "negative": ["5", 0], "latent_image": ["12", 0]
}},
"9": {"class_type": "VAEDecode", "inputs": {"samples": ["8", 0], "vae": ["1", 2]}},
"10": {"class_type": "SaveImage", "inputs": {"filename_prefix": f"render_{ts}", "images": ["9", 0]}},
"12": latent_node
}
# Only add LoadImage if we actually have a reference
if has_ref:
workflow["11"] = {"class_type": "LoadImage", "inputs": {"image": image_to_load}}
# 4. Post to ComfyUI
r = requests.post(f"{COMFYUI_URL}/prompt", json={"prompt": workflow})
data = r.json()
if "prompt_id" not in data:
print("ERROR FROM COMFYUI:", data)
return jsonify({"error": "ComfyUI rejected the render. Check names of Models/ControlNets."})
return jsonify({"prompt_id": data["prompt_id"]})
except Exception as e:
return jsonify({"error": str(e)})
@app.route("/check/<prompt_id>")
def check(prompt_id):
r = requests.get(f"{COMFYUI_URL}/history/{prompt_id}")
hist = r.json()
if prompt_id in hist:
filename = hist[prompt_id]["outputs"]["10"]["images"][0]["filename"]
return jsonify({"status": "done", "filename": filename})
return jsonify({"status": "pending"})
@app.route("/finalize/<filename>")
def finalize(filename):
# Polling for the file over LAN (Handles the 10-15s lag)
for _ in range(15):
try:
r = requests.get(f"{COMFYUI_URL}/view?filename={filename}", timeout=5)
if r.status_code == 200:
with open(os.path.join(LOCAL_RESULTS_DIR, filename), "wb") as f:
f.write(r.content)
return jsonify({"status": "saved"})
except: pass
time.sleep(3)
return jsonify({"status": "error"})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5200, debug=True)
"""
Key Changes made for you:
1. Immediate Pose Upload: Added a new route /upload_pose_only. When you pick a
file in the browser, JavaScript instantly uploads it and refreshes the
"Active Pose Image" card. No more waiting 6 minutes to see if the upload
worked.
2. Dynamic Workflow Node 11: I fixed the logic so that LoadImage (Node 11) is
only created if there is a reference image. This prevents the prompt_id
error caused by ComfyUI trying to load a file that doesn't exist.
3. Unique Filenames (Everywhere): Poses, References, and Outputs now all use
unique timestamps in the workflow. This stops ComfyUI from ever using an old
image from its cache.
4. Cache Busting: The images in the UI use ?c=TIMESTAMP, which forces your
browser to show the newly uploaded pose instead of an old cached version.
"""
"""quickpose_lab512x768.py
This issue happens because the browser doesn't know you've uploaded a new image
until the entire 6-minute render is finished and the page refreshes.
To fix this, I have added a "Pre-upload" feature. As soon as you select a pose
file, it is sent to the server and displayed in the "Pose Image" box instantly.
This confirms the app has the right pose before you commit to the 6-minute
render.
The Complete Fixed App
"""
#!/usr/bin/env python3
import os
import json
import time
import random
import shutil
import requests
from flask import (
Flask,
request,
render_template_string,
send_from_directory,
redirect,
jsonify
)
app = Flask(__name__)
# =========================================================
# CONFIG
# =========================================================
COMFYUI_URL = "http://192.168.1.41:5001"
# Ensure these filenames match your ComfyUI models exactly
CHECKPOINT = "dreamshaper_8.safetensors"
#CHECKPOINT= "animerge_v50.safetensors"
CONTROLNET = "control_v11p_sd15_openpose.pth"
#CONTROLNET = "control_v11p_sd15_canny.pth"
#CONTROLNET = "control_v11p_sd15_scribble.pth"
BASE_DIR = os.getcwd()
INPUT_DIR = os.path.join(BASE_DIR, "static", "input")
LOCAL_RESULTS_DIR = os.path.join(BASE_DIR, "static", "local_resultS")
# "Active" files used for the CURRENT render
ACTIVE_REF_PATH = os.path.join(INPUT_DIR, "active_reference.png")
ACTIVE_POSE_PATH = os.path.join(INPUT_DIR, "active_pose.png")
os.makedirs(INPUT_DIR, exist_ok=True)
os.makedirs(LOCAL_RESULTS_DIR, exist_ok=True)
# =========================================================
# HTML
# =========================================================
HTML = """
<!DOCTYPE html>
<html>
<head>
<title>AI Stop Motion Lab</title>
<style>
body { font-family: sans-serif; background: #78c740; max-width: 1200px; margin: auto; padding: 20px; }
.card { background: white; padding: 20px; border-radius: 12px; margin-bottom: 20px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
.grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; }
img { width: 100%; border-radius: 8px; border: 1px solid #ddd; background: #fafafa; height: 300px; object-fit: contain; }
button { width: 100%; padding: 12px; border-radius: 6px; border: none; cursor: pointer; font-weight: bold; background: #4a90e2; color: white; margin-top: 10px; }
.btn-green { background: #28a745; }
#status-area { padding: 15px; border-radius: 8px; margin-bottom: 20px; display: none; font-weight: bold; text-align: center; background: #fff3cd; border: 1px solid #ffeeba; }
.loading-spinner { border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 24px; height: 24px; animation: spin 1s linear infinite; margin: 10px auto; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 15px; }
.gallery-item { text-align: center; background: #fff; padding: 10px; border-radius: 8px; border: 1px solid #ddd; }
.gallery-item img { height: 180px; }
</style>
</head>
<body>
<h1>🎬 AI Stop Motion Lab v7</h1>
<p>quickpose_lab5.py port=5200 CONTROLNET = "control_v11p_sd15_scribble.pth"</p>
<p>full body caricature of an old man with beard and mustache dancing, high detail, cinematic, consistent character no image use .35</p>
<p>a head to toe whole body image of a druid warrior women,with leather shoes, ((whole body image))</p>
<div id="status-area">
<div id="status-text">Ready</div>
<div class="loading-spinner"></div>
</div>
<div class="grid">
<div class="card">
<h3>1. Settings</h3>
<form id="gen-form">
<label><b>Upload & Preview Pose:</b></label>
<input type="file" id="pose-selector" name="pose" required style="margin: 10px 0;">
<label><b>Prompt:</b></label>
<textarea name="prompt" rows="3" style="width:100%; border:1px solid #ccc; border-radius:4px;">a head to toe whole body image of a druid warrior women,with leather shoes, ((whole body image))</textarea>
<label style="display:block; margin-top:10px;">
Denoise (Consistency): <output style="font-weight: bold; color: #4a90e2;">0.85</output>
<div style="display: flex; align-items: center; gap: 10px; font-size: 11px; color: #666;">
<span>Strict</span>
<input type="range" name="denoise" min="0.1" max="1.0" step="0.05" value="0.85"
style="flex-grow: 1;"
oninput="this.parentElement.previousElementSibling.value = this.value">
<span>Loose</span>
</div>
</label>
<button type="submit" id="gen-btn" class="btn-green">GENERATE (4-6 MINS)</button>
</form>
</div>
<div class="card">
<h3>2. Active Reference</h3>
{% if has_ref %}
<img id="ref-preview" src="/input/active_reference.png?c={{ ts }}">
<button onclick="location.href='/clear_ref'" style="background:#6c757d;">Clear Reference</button>
{% else %}
<div id="ref-placeholder" style="height:300px; background:#eee; display:flex; align-items:center; justify-content:center; border-radius:8px; color:#666;">No Reference (Fresh Start)</div>
{% endif %}
</div>
<div class="card">
<h3>3. Active Pose Image</h3>
<img id="pose-preview" src="/input/active_pose.png?c={{ ts }}" {{ ts }}style="{% if not has_pose %}display:none;{% endif %}">
{% if not has_pose %}
<div id="pose-placeholder" style="height:300px; background:#eee; display:flex; align-items:center; justify-content:center; border-radius:8px; color:#666;">No Pose Selected</div>
{% endif %}
</div>
</div>
<div class="card">
<h3>History Gallery</h3>
<div class="gallery">
{% for f in gallery %}
<div class="gallery-item">
<img src="static/local_resultS/{{ f }}">
<button onclick="location.href='/use/{{ f }}'">Set as Ref</button>
</div>
{% endfor %}
</div>
</div>
<script>
const poseSelector = document.getElementById('pose-selector');
const posePreview = document.getElementById('pose-preview');
const posePlaceholder = document.getElementById('pose-placeholder');
const genForm = document.getElementById('gen-form');
const genBtn = document.getElementById('gen-btn');
const statusArea = document.getElementById('status-area');
const statusText = document.getElementById('status-text');
// IMMEDIATE POSE PREVIEW
poseSelector.onchange = async () => {
if (poseSelector.files && poseSelector.files[0]) {
const formData = new FormData();
formData.append('pose', poseSelector.files[0]);
// Upload immediately so we can see it
const res = await fetch('/upload_pose_only', { method: 'POST', body: formData });
if (res.ok) {
posePreview.src = "/input/active_pose.png?c=" + new Date().getTime();
posePreview.style.display = "block";
if(posePlaceholder) posePlaceholder.style.display = "none";
}
}
};
// GENERATION PROCESS
genForm.onsubmit = async (e) => {
e.preventDefault();
genBtn.disabled = true;
statusArea.style.display = 'block';
statusText.innerText = "Step 1/3: Sending workflow to ComfyUI...";
const formData = new FormData(genForm);
const res = await fetch('/generate', { method: 'POST', body: formData });
const data = await res.json();
if (data.error) {
alert("Error: " + data.error);
location.reload();
return;
}
let seconds = 0;
const interval = setInterval(async () => {
seconds += 5;
const check = await fetch('/check/' + data.prompt_id);
const status = await check.json();
if (status.status === 'done') {
clearInterval(interval);
statusText.innerText = "Step 3/3: Render finished! Fetching image...";
await fetch('/finalize/' + status.filename);
location.reload();
} else {
statusText.innerText = `Step 2/3: CPU 512x768 Rendering... (~${seconds}s elapsed / 960s expected)`;
}
}, 5000);
};
</script>
</body>
</html>
"""
# =========================================================
# BACKEND
# =========================================================
@app.route("/")
def index():
gallery = sorted(
[f for f in os.listdir(LOCAL_RESULTS_DIR) if f.endswith(".png")],
key=lambda x: os.path.getctime(os.path.join(LOCAL_RESULTS_DIR, x)),
reverse=True)
return render_template_string(HTML, gallery=gallery,
has_ref=os.path.exists(ACTIVE_REF_PATH),
has_pose=os.path.exists(ACTIVE_POSE_PATH),
ts=time.time())
@app.route("/input/<f>")
def input_f(f): return send_from_directory(INPUT_DIR, f)
@app.route("/results/<f>")
def results(f): return send_from_directory(LOCAL_RESULTS_DIR, f)
@app.route("/clear_ref")
def clear_ref():
if os.path.exists(ACTIVE_REF_PATH): os.remove(ACTIVE_REF_PATH)
return redirect("/")
@app.route("/use/<f>")
def use(f):
shutil.copy(os.path.join(LOCAL_RESULTS_DIR, f), ACTIVE_REF_PATH)
return redirect("/")
@app.route("/upload_pose_only", methods=["POST"])
def upload_pose_only():
if 'pose' in request.files:
file = request.files['pose']
file.save(ACTIVE_POSE_PATH)
return jsonify({"status": "ok"})
return jsonify({"status": "error"}), 400
@app.route("/generate", methods=["POST"])
def generate():
try:
ts = int(time.time())
prompt_text = request.form.get("prompt")
denoise = float(request.form.get("denoise"))
# 1. Use the pose already uploaded by the previewer
if not os.path.exists(ACTIVE_POSE_PATH):
return jsonify({"error": "No pose image found. Please select a file."})
# Give the pose a unique name for ComfyUI's input folder
pose_name = f"pose_{ts}.png"
with open(ACTIVE_POSE_PATH, "rb") as f:
requests.post(f"{COMFYUI_URL}/upload/image", files={"image": (pose_name, f)})
# 2. Setup Reference
has_ref = os.path.exists(ACTIVE_REF_PATH)
if has_ref:
ref_name = f"ref_{ts}.png"
with open(ACTIVE_REF_PATH, "rb") as f:
requests.post(f"{COMFYUI_URL}/upload/image", files={"image": (ref_name, f)})
latent_node = {"class_type": "VAEEncode", "inputs": {"pixels": ["11", 0], "vae": ["1", 2]}}
image_to_load = ref_name
else:
denoise = 1.0
latent_node = {"class_type": "EmptyLatentImage", "inputs": {"width": 512, "height": 768, "batch_size": 1}}
image_to_load = pose_name # Just a dummy to keep Node 11 from crashing if it exists
# 3. Build Workflow
workflow = {
"1": {"class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": CHECKPOINT}},
"2": {"class_type": "LoadImage", "inputs": {"image": pose_name}},
"3": {"class_type": "ControlNetLoader", "inputs": {"control_net_name": CONTROLNET}},
"4": {"class_type": "CLIPTextEncode", "inputs": {"text": prompt_text, "clip": ["1", 1]}},
"5": {"class_type": "CLIPTextEncode", "inputs": {"text": "NSFW, nudity, nipples, large breasts, low quality, out of frame, blurry, distorted", "clip": ["1", 1]}},
"6": {"class_type": "ControlNetApply", "inputs": {"strength": 0.8, "conditioning": ["4", 0], "control_net": ["3", 0], "image": ["2", 0]}},
"8": {"class_type": "KSampler", "inputs": {
"seed": random.randint(1, 99999999), "steps": 20, "cfg": 7, "sampler_name": "euler",
"scheduler": "normal", "denoise": denoise, "model": ["1", 0],
"positive": ["6", 0], "negative": ["5", 0], "latent_image": ["12", 0]
}},
"9": {"class_type": "VAEDecode", "inputs": {"samples": ["8", 0], "vae": ["1", 2]}},
"10": {"class_type": "SaveImage", "inputs": {"filename_prefix": f"render_{ts}", "images": ["9", 0]}},
"12": latent_node
}
# Only add LoadImage if we actually have a reference
if has_ref:
workflow["11"] = {"class_type": "LoadImage", "inputs": {"image": image_to_load}}
# 4. Post to ComfyUI
r = requests.post(f"{COMFYUI_URL}/prompt", json={"prompt": workflow})
data = r.json()
if "prompt_id" not in data:
print("ERROR FROM COMFYUI:", data)
return jsonify({"error": "ComfyUI rejected the render. Check names of Models/ControlNets."})
return jsonify({"prompt_id": data["prompt_id"]})
except Exception as e:
return jsonify({"error": str(e)})
@app.route("/check/<prompt_id>")
def check(prompt_id):
r = requests.get(f"{COMFYUI_URL}/history/{prompt_id}")
hist = r.json()
if prompt_id in hist:
filename = hist[prompt_id]["outputs"]["10"]["images"][0]["filename"]
return jsonify({"status": "done", "filename": filename})
return jsonify({"status": "pending"})
@app.route("/finalize/<filename>")
def finalize(filename):
# Polling for the file over LAN (Handles the 10-15s lag)
for _ in range(15):
try:
r = requests.get(f"{COMFYUI_URL}/view?filename={filename}", timeout=5)
if r.status_code == 200:
with open(os.path.join(LOCAL_RESULTS_DIR, filename), "wb") as f:
f.write(r.content)
return jsonify({"status": "saved"})
except: pass
time.sleep(3)
return jsonify({"status": "error"})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5100, debug=True)
"""
Key Changes made for you:
1. Immediate Pose Upload: Added a new route /upload_pose_only. When you pick a
file in the browser, JavaScript instantly uploads it and refreshes the
"Active Pose Image" card. No more waiting 6 minutes to see if the upload
worked.
2. Dynamic Workflow Node 11: I fixed the logic so that LoadImage (Node 11) is
only created if there is a reference image. This prevents the prompt_id
error caused by ComfyUI trying to load a file that doesn't exist.
3. Unique Filenames (Everywhere): Poses, References, and Outputs now all use
unique timestamps in the workflow. This stops ComfyUI from ever using an old
image from its cache.
4. Cache Busting: The images in the UI use ?c=TIMESTAMP, which forces your
browser to show the newly uploaded pose instead of an old cached version.
"""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment