q6 commited on
Commit
8e13700
·
1 Parent(s): 2b549cd

Add Comfy-compatible Gradio image API

Browse files
Files changed (6) hide show
  1. C Source.txt +6 -0
  2. Dockerfile +22 -0
  3. app.py +282 -0
  4. requirements.txt +5 -0
  5. style.css +36 -0
  6. workflow_api.py +694 -0
C Source.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Lora:
2
+ https://huggingface.co/HyperHail/cb/resolve/main/models/loras/5cm-illustriousXL_v01_V1-CAME-000035.safetensors?download=true
3
+ https://huggingface.co/HyperHail/cb/resolve/main/models/loras/bikabaka.safetensors?download=true
4
+ https://huggingface.co/HyperHail/cb/resolve/main/models/loras/add_saturation_XL.safetensors?download=true
5
+ https://huggingface.co/HyperHail/cb/resolve/main/models/loras/%E3%82%A2%E3%83%83%E3%83%97%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%AB_remacri_original.pt?download=true
6
+ https://huggingface.co/HyperHail/cb/resolve/main/models/checkpoints/52_novaAnimeXL_ilV190.safetensors?download=true
Dockerfile ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM nvidia/cuda:12.8.1-cudnn-runtime-ubuntu24.04
2
+
3
+ ENV DEBIAN_FRONTEND=noninteractive \
4
+ COMFYUI_PATH=/opt/ComfyUI \
5
+ DATA_DIR=/data \
6
+ PYTHONUNBUFFERED=1
7
+
8
+ RUN apt-get update && apt-get install -y --no-install-recommends \
9
+ git libgl1 libglib2.0-0 python3 python3-pip python3-venv && \
10
+ rm -rf /var/lib/apt/lists/*
11
+ RUN git clone --depth 1 https://github.com/comfyanonymous/ComfyUI.git /opt/ComfyUI
12
+
13
+ WORKDIR /app
14
+ COPY requirements.txt .
15
+ RUN python3 -m pip install --no-cache-dir --break-system-packages \
16
+ --extra-index-url https://download.pytorch.org/whl/cu128 \
17
+ -r /opt/ComfyUI/requirements.txt -r requirements.txt
18
+ COPY . .
19
+ RUN mkdir -p /data
20
+
21
+ EXPOSE 7860
22
+ CMD ["python3", "app.py"]
app.py ADDED
@@ -0,0 +1,282 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import re
4
+ import sys
5
+ import threading
6
+ import urllib.request
7
+ import uuid
8
+ from concurrent.futures import ThreadPoolExecutor
9
+ from pathlib import Path
10
+
11
+ import gradio as gr
12
+ import numpy as np
13
+ from fastapi import FastAPI, HTTPException, Query
14
+ from fastapi.middleware.cors import CORSMiddleware
15
+ from fastapi.responses import FileResponse
16
+ from PIL import Image
17
+ from pydantic import BaseModel, Field
18
+
19
+
20
+ DATA_DIR = Path(os.environ.get("DATA_DIR", "/data"))
21
+ COMFYUI_PATH = Path(os.environ.get("COMFYUI_PATH", "/opt/ComfyUI"))
22
+ OUTPUT_DIR = DATA_DIR / "output"
23
+ ASSETS = {
24
+ "checkpoint": (
25
+ "52_novaAnimeXL_ilV190.safetensors",
26
+ "checkpoints",
27
+ "https://huggingface.co/HyperHail/cb/resolve/main/models/checkpoints/52_novaAnimeXL_ilV190.safetensors?download=true",
28
+ ),
29
+ "bikabaka": (
30
+ "8_bikabaka.safetensors",
31
+ "loras",
32
+ "https://huggingface.co/HyperHail/cb/resolve/main/models/loras/bikabaka.safetensors?download=true",
33
+ ),
34
+ "remacri": (
35
+ "42_アップスケール_remacri_original.pt",
36
+ "loras",
37
+ "https://huggingface.co/HyperHail/cb/resolve/main/models/loras/%E3%82%A2%E3%83%83%E3%83%97%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%AB_remacri_original.pt?download=true",
38
+ ),
39
+ "5cm": (
40
+ "43_5cm-illustriousXL_v01_V1-CAME-000035.safetensors",
41
+ "loras",
42
+ "https://huggingface.co/HyperHail/cb/resolve/main/models/loras/5cm-illustriousXL_v01_V1-CAME-000035.safetensors?download=true",
43
+ ),
44
+ "saturation": (
45
+ "5_add_saturation_XL.safetensors",
46
+ "loras",
47
+ "https://huggingface.co/HyperHail/cb/resolve/main/models/loras/add_saturation_XL.safetensors?download=true",
48
+ ),
49
+ "vae": (
50
+ "3_sdxlVAE_sdxlVAE.safetensors",
51
+ "vae",
52
+ "https://huggingface.co/HyperHail/cb/resolve/main/models/vae/sdxlVAE_sdxlVAE.safetensors?download=true",
53
+ ),
54
+ }
55
+ SUFFIX = "(disheveled hair:0.5), skinny, shiny skin, skinny, shiny skin, (simple background, white background:1.1), masterpiece, best quality"
56
+ NEGATIVE = "(censored, mosaic censoring, bar censor:1.1), bad quality, worst quality, worst detail, bad anatomy, extra fingers, extra toes, extra legs, 4 toes, 6 toes, 4 fingers, 6 fingers, malformed fingers, extra limbs, missing fingers, extra arms, censored, deformed, disfigured, text, (multiple views:1.1)"
57
+ SAMPLERS = ["euler_ancestral"]
58
+ SCHEDULERS = ["karras"]
59
+
60
+ paths = {}
61
+ jobs = {}
62
+ state = {}
63
+ lock = threading.Lock()
64
+ pool = ThreadPoolExecutor(max_workers=1)
65
+
66
+
67
+ def plain_name(name):
68
+ return re.sub(r"^\d+_", "", name).casefold()
69
+
70
+
71
+ def find_asset(name):
72
+ wanted = plain_name(name)
73
+ if DATA_DIR.exists():
74
+ for path in DATA_DIR.rglob("*"):
75
+ if path.is_file() and plain_name(path.name) == wanted:
76
+ return path
77
+
78
+
79
+ def download(url, target):
80
+ target.parent.mkdir(parents=True, exist_ok=True)
81
+ temp = target.with_suffix(target.suffix + ".part")
82
+ print(f"Downloading {target.name}")
83
+ urllib.request.urlretrieve(url, temp)
84
+ temp.replace(target)
85
+
86
+
87
+ def ensure_assets():
88
+ DATA_DIR.mkdir(parents=True, exist_ok=True)
89
+ OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
90
+ for key, (name, kind, url) in ASSETS.items():
91
+ path = find_asset(name)
92
+ if path is None:
93
+ path = DATA_DIR / kind / name
94
+ download(url, path)
95
+ paths[key] = path
96
+
97
+
98
+ def init_comfy():
99
+ if state:
100
+ return
101
+ if not COMFYUI_PATH.is_dir():
102
+ raise RuntimeError(f"ComfyUI not found at {COMFYUI_PATH}")
103
+ ensure_assets()
104
+ sys.path.insert(0, str(COMFYUI_PATH))
105
+ import folder_paths
106
+ from nodes import CLIPTextEncode, CheckpointLoaderSimple, EmptyLatentImage, KSampler, LoraLoader, VAEDecode, VAELoader
107
+
108
+ for path in paths.values():
109
+ kind = ASSETS[next(key for key, value in paths.items() if value == path)][1]
110
+ folder_paths.add_model_folder_path(kind, str(path.parent))
111
+
112
+ checkpoint = CheckpointLoaderSimple().load_checkpoint(paths["checkpoint"].name)
113
+ loader = LoraLoader()
114
+ first = loader.load_lora(paths["bikabaka"].name, .3, 0, checkpoint[0], checkpoint[1])
115
+ second = loader.load_lora(paths["remacri"].name, .4, 0, first[0], first[1])
116
+ third = loader.load_lora(paths["5cm"].name, .4, 0, second[0], second[1])
117
+ final = loader.load_lora(paths["saturation"].name, -1.4, 0, third[0], third[1])
118
+ state.update(
119
+ model=final[0],
120
+ clip=final[1],
121
+ negative_clip=third[1],
122
+ vae=VAELoader().load_vae(paths["vae"].name)[0],
123
+ encode=CLIPTextEncode(),
124
+ latent=EmptyLatentImage(),
125
+ sample=KSampler(),
126
+ decode=VAEDecode(),
127
+ )
128
+
129
+
130
+ def generate(prompt, width=1152, height=896, steps=16):
131
+ width, height, steps = int(width), int(height), int(steps)
132
+ if not 64 <= width <= 2048 or width % 8 or not 64 <= height <= 2048 or height % 8:
133
+ raise ValueError("Width and height must be multiples of 8 from 64 to 2048")
134
+ if not 1 <= steps <= 100:
135
+ raise ValueError("Steps must be from 1 to 100")
136
+ with lock:
137
+ init_comfy()
138
+ positive = state["encode"].encode(f"{prompt},{SUFFIX}", state["clip"])[0]
139
+ negative = state["encode"].encode(NEGATIVE, state["negative_clip"])[0]
140
+ latent = state["latent"].generate(width, height, 1)[0]
141
+ samples = state["sample"].sample(
142
+ random.randrange(2**64), steps, 4, "euler_ancestral", "karras", 1,
143
+ state["model"], positive, negative, latent,
144
+ )[0]
145
+ image = state["decode"].decode(samples, state["vae"])[0][0]
146
+ return Image.fromarray(np.clip(image.cpu().numpy() * 255, 0, 255).astype(np.uint8))
147
+
148
+
149
+ def linked_value(workflow, value):
150
+ if not isinstance(value, list) or len(value) < 2:
151
+ return value
152
+ node = workflow.get(str(value[0]), {})
153
+ inputs = node.get("inputs", {})
154
+ if node.get("class_type") == "StringConcatenate":
155
+ parts = [linked_value(workflow, inputs.get("string_a", "")), linked_value(workflow, inputs.get("string_b", ""))]
156
+ return str(inputs.get("delimiter", ",")).join(map(str, parts))
157
+ return inputs.get("text", "")
158
+
159
+
160
+ def workflow_values(workflow):
161
+ sampler = next((node for node in workflow.values() if node.get("class_type") == "KSampler"), {})
162
+ inputs = sampler.get("inputs", {})
163
+ latent_id = inputs.get("latent_image", [None])[0]
164
+ latent = workflow.get(str(latent_id), {}).get("inputs", {})
165
+ positive_id = inputs.get("positive", [None])[0]
166
+ positive = workflow.get(str(positive_id), {}).get("inputs", {}).get("text", "")
167
+ return linked_value(workflow, positive), latent.get("width", 1152), latent.get("height", 896), inputs.get("steps", 16)
168
+
169
+
170
+ def run_job(job_id, workflow):
171
+ try:
172
+ prompt, width, height, steps = workflow_values(workflow)
173
+ image = generate(prompt, width, height, steps)
174
+ filename = f"{job_id}.png"
175
+ image.save(OUTPUT_DIR / filename)
176
+ jobs[job_id] = {
177
+ "outputs": {"output": {"images": [{"filename": filename, "subfolder": "", "type": "output"}]}},
178
+ "status": {"status_str": "success", "completed": True, "messages": []},
179
+ }
180
+ except Exception as error:
181
+ jobs[job_id] = {
182
+ "outputs": {},
183
+ "status": {"status_str": "error", "completed": True, "messages": [["execution_error", {
184
+ "node_id": "output", "node_type": "Generate", "exception_type": type(error).__name__, "exception_message": str(error),
185
+ }]]},
186
+ }
187
+
188
+
189
+ class DirectRequest(BaseModel):
190
+ prompt: str
191
+ width: int = Field(1152, ge=64, le=2048)
192
+ height: int = Field(896, ge=64, le=2048)
193
+ steps: int = Field(16, ge=1, le=100)
194
+
195
+
196
+ api = FastAPI()
197
+ api.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
198
+
199
+
200
+ @api.get("/system_stats")
201
+ def system_stats():
202
+ return {"system": {"os": os.name}, "devices": []}
203
+
204
+
205
+ @api.get("/object_info")
206
+ def object_info():
207
+ ensure_assets()
208
+ return {
209
+ "KSampler": {"input": {"required": {"sampler_name": [SAMPLERS], "scheduler": [SCHEDULERS]}}},
210
+ "CheckpointLoaderSimple": {"input": {"required": {"ckpt_name": [[paths["checkpoint"].name]]}}},
211
+ "UNETLoader": {"input": {"required": {"unet_name": [[]]}}},
212
+ "VAELoader": {"input": {"required": {"vae_name": [[paths["vae"].name]]}}},
213
+ }
214
+
215
+
216
+ @api.post("/prompt")
217
+ def queue_prompt(body: dict):
218
+ workflow = body.get("prompt")
219
+ if not isinstance(workflow, dict):
220
+ raise HTTPException(400, "prompt must contain a ComfyUI workflow object")
221
+ job_id = str(uuid.uuid4())
222
+ pool.submit(run_job, job_id, workflow)
223
+ return {"prompt_id": job_id, "number": len(jobs), "node_errors": {}}
224
+
225
+
226
+ @api.get("/history")
227
+ def history():
228
+ return jobs
229
+
230
+
231
+ @api.get("/history/{job_id}")
232
+ def history_item(job_id):
233
+ return {job_id: jobs[job_id]} if job_id in jobs else {}
234
+
235
+
236
+ @api.get("/view")
237
+ def view(filename: str = Query(...)):
238
+ path = OUTPUT_DIR / Path(filename).name
239
+ if not path.is_file():
240
+ raise HTTPException(404, "image not found")
241
+ return FileResponse(path, media_type="image/png")
242
+
243
+
244
+ @api.post("/interrupt")
245
+ def interrupt():
246
+ return {}
247
+
248
+
249
+ @api.post("/api/generate")
250
+ def direct(body: DirectRequest):
251
+ try:
252
+ image = generate(body.prompt, body.width, body.height, body.steps)
253
+ except ValueError as error:
254
+ raise HTTPException(422, str(error)) from error
255
+ filename = f"{uuid.uuid4()}.png"
256
+ path = OUTPUT_DIR / filename
257
+ image.save(path)
258
+ return FileResponse(path, media_type="image/png", filename=filename)
259
+
260
+
261
+ with gr.Blocks(
262
+ title="Image API",
263
+ css_paths="style.css",
264
+ head='<link rel="icon" href="data:;base64,=">',
265
+ ) as demo:
266
+ gr.HTML("<model-note>ComfyUI-compatible API and direct image generation</model-note>")
267
+ prompt_input = gr.Textbox(label="Prompt", lines=5)
268
+ with gr.Row():
269
+ width_input = gr.Number(1152, label="Width", precision=0)
270
+ height_input = gr.Number(896, label="Height", precision=0)
271
+ steps_input = gr.Slider(1, 100, 16, step=1, label="Steps")
272
+ button = gr.Button("Generate", variant="primary")
273
+ output = gr.Image(label="Image", format="png")
274
+ button.click(generate, [prompt_input, width_input, height_input, steps_input], output, api_name="generate")
275
+
276
+
277
+ app = gr.mount_gradio_app(api, demo, path="/")
278
+
279
+
280
+ if __name__ == "__main__":
281
+ import uvicorn
282
+ uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "7860")))
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ fastapi
2
+ gradio==6.20.0
3
+ numpy
4
+ Pillow
5
+ uvicorn
style.css ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Spline+Sans:wght@400;500;600&display=swap');
2
+
3
+ :root {
4
+ --bg: #112;
5
+ --panel: #223;
6
+ --text: #dde;
7
+ --muted: #99a;
8
+ --accent: #7dc;
9
+ }
10
+ body, .gradio-container {
11
+ background: var(--bg) !important;
12
+ color: var(--text) !important;
13
+ font-family: "Spline Sans", sans-serif !important;
14
+ }
15
+
16
+ .gradio-container {
17
+ max-width: 90% !important;
18
+ }
19
+
20
+ model-note {
21
+ color: var(--muted);
22
+ display: block;
23
+ margin: 0 0 1rem;
24
+ }
25
+
26
+ button.primary {
27
+ background: var(--accent) !important;
28
+ border: 0 !important;
29
+ border-radius: 0 !important;
30
+ color: var(--bg) !important;
31
+ }
32
+
33
+ .block, .form {
34
+ background: var(--panel) !important;
35
+ border-radius: 0 !important;
36
+ }
workflow_api.py ADDED
@@ -0,0 +1,694 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Imports
2
+ import json
3
+ import os
4
+ import random
5
+ import sys
6
+ from typing import Sequence, Mapping, Any, Union
7
+
8
+
9
+ def get_value_at_index(obj: Union[Sequence, Mapping], index: int) -> Any:
10
+ """Return a sequence or mapping result item by index."""
11
+ try:
12
+ return obj[index]
13
+ except KeyError:
14
+ return obj["result"][index]
15
+
16
+
17
+ def get_comfyui_path() -> str:
18
+ """Return the configured ComfyUI path, preferring COMFYUI_PATH when set."""
19
+ comfyui_path = os.environ.get("COMFYUI_PATH")
20
+ if comfyui_path:
21
+ return comfyui_path
22
+ return find_path("ComfyUI")
23
+
24
+
25
+ def find_path(name: str, path: str = None) -> str:
26
+ """Recursively search parent folders until the named entry is found."""
27
+ if path is None:
28
+ path = os.getcwd()
29
+
30
+ if name in os.listdir(path):
31
+ path_name = os.path.join(path, name)
32
+ print(f"{name} found: {path_name}")
33
+ return path_name
34
+
35
+ parent_directory = os.path.dirname(path)
36
+ if parent_directory == path:
37
+ return None
38
+
39
+ return find_path(name, parent_directory)
40
+
41
+
42
+ def add_comfyui_directory_to_sys_path() -> None:
43
+ """Add the ComfyUI checkout to sys.path."""
44
+ comfyui_path = get_comfyui_path()
45
+ if comfyui_path is not None and os.path.isdir(comfyui_path):
46
+ if comfyui_path in sys.path:
47
+ sys.path.remove(comfyui_path)
48
+ sys.path.insert(0, comfyui_path)
49
+ print(f"'{comfyui_path}' added to sys.path")
50
+
51
+
52
+ def add_extra_model_paths() -> None:
53
+ """Load ComfyUI extra model paths configuration when available."""
54
+ try:
55
+ from main import load_extra_path_config
56
+ except ImportError:
57
+ print(
58
+ "Could not import load_extra_path_config from main.py. Looking in utils.extra_config instead."
59
+ )
60
+ from utils.extra_config import load_extra_path_config
61
+
62
+ extra_model_paths = find_path("extra_model_paths.yaml")
63
+ if extra_model_paths is not None:
64
+ load_extra_path_config(extra_model_paths)
65
+ else:
66
+ print("Could not find the extra_model_paths config file.")
67
+
68
+
69
+ def bootstrap_comfyui_runtime() -> None:
70
+ """Mirror the allocator-related ComfyUI startup steps before torch import."""
71
+ add_comfyui_directory_to_sys_path()
72
+
73
+ import comfy.options
74
+
75
+ comfy.options.enable_args_parsing()
76
+
77
+ from comfy.cli_args import args
78
+
79
+ if os.name == "nt":
80
+ os.environ["MIMALLOC_PURGE_DELAY"] = "0"
81
+
82
+ if args.default_device is not None:
83
+ default_dev = args.default_device
84
+ devices = list(range(32))
85
+ devices.remove(default_dev)
86
+ devices.insert(0, default_dev)
87
+ devices = ",".join(map(str, devices))
88
+ os.environ["CUDA_VISIBLE_DEVICES"] = str(devices)
89
+ os.environ["HIP_VISIBLE_DEVICES"] = str(devices)
90
+
91
+ if args.cuda_device is not None:
92
+ os.environ["CUDA_VISIBLE_DEVICES"] = str(args.cuda_device)
93
+ os.environ["HIP_VISIBLE_DEVICES"] = str(args.cuda_device)
94
+ os.environ["ASCEND_RT_VISIBLE_DEVICES"] = str(args.cuda_device)
95
+
96
+ if args.oneapi_device_selector is not None:
97
+ os.environ["ONEAPI_DEVICE_SELECTOR"] = args.oneapi_device_selector
98
+
99
+ if args.deterministic and "CUBLAS_WORKSPACE_CONFIG" not in os.environ:
100
+ os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8"
101
+
102
+ import cuda_malloc
103
+
104
+ if "rocm" in cuda_malloc.get_torch_version_noimport():
105
+ os.environ["OCL_SET_SVM_SIZE"] = "262144"
106
+
107
+
108
+ def cleanup_comfyui_runtime(unload_models: bool | None = None) -> None:
109
+ """Best-effort cleanup for embedded or repeated generated-script execution."""
110
+ import gc
111
+
112
+ def run_cleanup_hook(name: str, should_run: bool = True) -> None:
113
+ if not should_run or not hasattr(model_management, name):
114
+ return
115
+ cleanup_fn = getattr(model_management, name)
116
+ try:
117
+ cleanup_fn()
118
+ except Exception as exc:
119
+ warnings.warn(
120
+ f"ComfyUI cleanup hook {name} failed during teardown: {exc}",
121
+ RuntimeWarning,
122
+ stacklevel=2,
123
+ )
124
+
125
+ should_unload = unload_models
126
+ if should_unload is None:
127
+ should_unload = os.environ.get(
128
+ "COMFYUI_TOPYTHON_UNLOAD_MODELS", ""
129
+ ).lower() in {
130
+ "1",
131
+ "true",
132
+ "yes",
133
+ "on",
134
+ }
135
+
136
+ try:
137
+ import comfy.model_management as model_management
138
+ except ModuleNotFoundError:
139
+ gc.collect()
140
+ return
141
+
142
+ run_cleanup_hook("cleanup_models_gc")
143
+ run_cleanup_hook("unload_all_models", should_run=should_unload)
144
+ run_cleanup_hook("soft_empty_cache")
145
+ gc.collect()
146
+
147
+
148
+ def import_custom_nodes() -> None:
149
+ """Initialize ComfyUI custom nodes in the exporter runtime."""
150
+ comfyui_path = get_comfyui_path()
151
+ if comfyui_path and comfyui_path not in sys.path:
152
+ sys.path.insert(0, comfyui_path)
153
+
154
+ import asyncio
155
+ import execution
156
+ from nodes import init_extra_nodes
157
+
158
+ if comfyui_path in sys.path:
159
+ sys.path.remove(comfyui_path)
160
+ sys.path.insert(0, comfyui_path)
161
+
162
+ import server
163
+
164
+ loop = asyncio.new_event_loop()
165
+ asyncio.set_event_loop(loop)
166
+ server_instance = server.PromptServer(loop)
167
+ execution.PromptQueue(server_instance)
168
+ asyncio.run(init_extra_nodes())
169
+
170
+
171
+ # Workflow data
172
+ def build_workflow() -> dict[str, Any]:
173
+ return {
174
+ "1": {
175
+ "inputs": {"ckpt_name": "52_novaAnimeXL_ilV190.safetensors"},
176
+ "class_type": "CheckpointLoaderSimple",
177
+ "_meta": {"title": "Loader"},
178
+ },
179
+ "2": {
180
+ "inputs": {"text": ["118", 0], "clip": ["117", 1]},
181
+ "class_type": "CLIPTextEncode",
182
+ "_meta": {"title": "CLIP Text Encode (Prompt)"},
183
+ },
184
+ "3": {
185
+ "inputs": {
186
+ "text": "(censored, mosaic censoring, bar censor:1.1), bad "
187
+ "quality, worst quality, worst detail, bad anatomy, "
188
+ "extra fingers, extra toes, extra legs, 4 toes, 6 "
189
+ "toes, 4 fingers, 6 fingers, malformed fingers, "
190
+ "extra limbs, missing fingers, extra arms, censored, "
191
+ "deformed, disfigured, text, (multiple views:1.1)",
192
+ "clip": ["28", 1],
193
+ },
194
+ "class_type": "CLIPTextEncode",
195
+ "_meta": {"title": "CLIP Text Encode (Prompt)"},
196
+ },
197
+ "5": {
198
+ "inputs": {
199
+ "seed": 809278554234612,
200
+ "steps": 16,
201
+ "cfg": 4,
202
+ "sampler_name": "euler_ancestral",
203
+ "scheduler": "karras",
204
+ "denoise": 1,
205
+ "model": ["117", 0],
206
+ "positive": ["2", 0],
207
+ "negative": ["3", 0],
208
+ "latent_image": ["27", 0],
209
+ },
210
+ "class_type": "KSampler",
211
+ "_meta": {"title": "KSampler"},
212
+ },
213
+ "10": {
214
+ "inputs": {
215
+ "lora_name": "8_bikabaka.safetensors",
216
+ "strength_model": 0.3,
217
+ "strength_clip": 0,
218
+ "model": ["1", 0],
219
+ "clip": ["1", 1],
220
+ },
221
+ "class_type": "LoraLoader",
222
+ "_meta": {"title": "Load LoRA"},
223
+ },
224
+ "27": {
225
+ "inputs": {"width": 1152, "height": 896, "batch_size": 1},
226
+ "class_type": "EmptyLatentImage",
227
+ "_meta": {"title": "Empty Landscape"},
228
+ },
229
+ "28": {
230
+ "inputs": {
231
+ "lora_name": "43_5cm-illustriousXL_v01_V1-CAME-000035.safetensors",
232
+ "strength_model": 0.4,
233
+ "strength_clip": 0,
234
+ "model": ["38", 0],
235
+ "clip": ["38", 1],
236
+ },
237
+ "class_type": "LoraLoader",
238
+ "_meta": {"title": "Load LoRA"},
239
+ },
240
+ "38": {
241
+ "inputs": {
242
+ "lora_name": "42_アップスケール_remacri_original.pt",
243
+ "strength_model": 0.4,
244
+ "strength_clip": 0,
245
+ "model": ["10", 0],
246
+ "clip": ["10", 1],
247
+ },
248
+ "class_type": "LoraLoader",
249
+ "_meta": {"title": "Load LoRA"},
250
+ },
251
+ "45": {
252
+ "inputs": {"vae_name": "3_sdxlVAE_sdxlVAE.safetensors"},
253
+ "class_type": "VAELoader",
254
+ "_meta": {"title": "Load VAE"},
255
+ },
256
+ "56": {
257
+ "inputs": {"samples": ["5", 0], "vae": ["45", 0]},
258
+ "class_type": "VAEDecode",
259
+ "_meta": {"title": "VAE Decode"},
260
+ },
261
+ "72": {
262
+ "inputs": {"images": ["56", 0]},
263
+ "class_type": "PreviewImage",
264
+ "_meta": {"title": "Preview Image"},
265
+ },
266
+ "117": {
267
+ "inputs": {
268
+ "lora_name": "5_add_saturation_XL.safetensors",
269
+ "strength_model": -1.4,
270
+ "strength_clip": 0,
271
+ "model": ["28", 0],
272
+ "clip": ["28", 1],
273
+ },
274
+ "class_type": "LoraLoader",
275
+ "_meta": {"title": "Load LoRA (Model and CLIP)"},
276
+ },
277
+ "118": {
278
+ "inputs": {
279
+ "string_a": "%prompt%",
280
+ "string_b": "(disheveled hair:0.5), skinny, shiny skin, "
281
+ "skinny, shiny skin, (simple background, white "
282
+ "background:1.1), masterpiece, best quality",
283
+ "delimiter": ",",
284
+ },
285
+ "class_type": "StringConcatenate",
286
+ "_meta": {"title": "Concatenate Text"},
287
+ },
288
+ }
289
+
290
+
291
+ def build_extra_pnginfo() -> dict[str, Any] | None:
292
+ return {
293
+ "workflow": {
294
+ "id": "e69619af-5ceb-4a83-821d-68180291905e",
295
+ "revision": 0,
296
+ "last_node_id": 121,
297
+ "last_link_id": 57,
298
+ "nodes": [
299
+ {
300
+ "id": 27,
301
+ "type": "EmptyLatentImage",
302
+ "pos": [100, 358],
303
+ "size": [270, 106],
304
+ "flags": {},
305
+ "order": 0,
306
+ "mode": 0,
307
+ "inputs": [],
308
+ "outputs": [{"name": "LATENT", "type": "LATENT", "links": [24]}],
309
+ "title": "Empty Landscape",
310
+ "properties": {"Node name for S&R": "EmptyLatentImage"},
311
+ "widgets_values": [1152, 896, 1],
312
+ },
313
+ {
314
+ "id": 45,
315
+ "type": "VAELoader",
316
+ "pos": [100, 594],
317
+ "size": [270, 58],
318
+ "flags": {},
319
+ "order": 1,
320
+ "mode": 0,
321
+ "inputs": [],
322
+ "outputs": [{"name": "VAE", "type": "VAE", "links": [32]}],
323
+ "properties": {"Node name for S&R": "VAELoader"},
324
+ "widgets_values": ["3_sdxlVAE_sdxlVAE.safetensors"],
325
+ },
326
+ {
327
+ "id": 56,
328
+ "type": "VAEDecode",
329
+ "pos": [2948.649165895271, 134.76727061509087],
330
+ "size": [140, 46],
331
+ "flags": {},
332
+ "order": 11,
333
+ "mode": 0,
334
+ "inputs": [
335
+ {"name": "samples", "type": "LATENT", "link": 31},
336
+ {"name": "vae", "type": "VAE", "link": 32},
337
+ ],
338
+ "outputs": [{"name": "IMAGE", "type": "IMAGE", "links": [33]}],
339
+ "properties": {"Node name for S&R": "VAEDecode"},
340
+ "widgets_values": [],
341
+ },
342
+ {
343
+ "id": 10,
344
+ "type": "LoraLoader",
345
+ "pos": [600, 130],
346
+ "size": [290.43334045410154, 126],
347
+ "flags": {},
348
+ "order": 4,
349
+ "mode": 0,
350
+ "inputs": [
351
+ {"name": "model", "type": "MODEL", "link": 25},
352
+ {"name": "clip", "type": "CLIP", "link": 26},
353
+ ],
354
+ "outputs": [
355
+ {"name": "MODEL", "type": "MODEL", "links": [29]},
356
+ {"name": "CLIP", "type": "CLIP", "links": [30]},
357
+ ],
358
+ "title": "Load LoRA",
359
+ "properties": {"Node name for S&R": "LoraLoader"},
360
+ "widgets_values": ["8_bikabaka.safetensors", 0.3, 0],
361
+ },
362
+ {
363
+ "id": 72,
364
+ "type": "PreviewImage",
365
+ "pos": [3188.649165895271, 134.76727061509087],
366
+ "size": [285.77604360195164, 258],
367
+ "flags": {},
368
+ "order": 12,
369
+ "mode": 0,
370
+ "inputs": [{"name": "images", "type": "IMAGE", "link": 33}],
371
+ "outputs": [{"name": "images", "type": "IMAGE", "links": None}],
372
+ "properties": {"Node name for S&R": "PreviewImage"},
373
+ "widgets_values": [],
374
+ },
375
+ {
376
+ "id": 1,
377
+ "type": "CheckpointLoaderSimple",
378
+ "pos": [100, 130],
379
+ "size": [270, 98],
380
+ "flags": {},
381
+ "order": 2,
382
+ "mode": 0,
383
+ "inputs": [],
384
+ "outputs": [
385
+ {"name": "MODEL", "type": "MODEL", "links": [25]},
386
+ {"name": "CLIP", "type": "CLIP", "links": [26]},
387
+ {"name": "VAE", "type": "VAE", "links": None},
388
+ ],
389
+ "title": "Loader",
390
+ "properties": {"Node name for S&R": "CheckpointLoaderSimple"},
391
+ "widgets_values": ["52_novaAnimeXL_ilV190.safetensors"],
392
+ },
393
+ {
394
+ "id": 2,
395
+ "type": "CLIPTextEncode",
396
+ "pos": [1880.866680908203, 130],
397
+ "size": [400, 200],
398
+ "flags": {},
399
+ "order": 9,
400
+ "mode": 0,
401
+ "inputs": [
402
+ {"name": "clip", "type": "CLIP", "link": 49},
403
+ {
404
+ "name": "text",
405
+ "type": "STRING",
406
+ "widget": {"name": "text"},
407
+ "link": 54,
408
+ },
409
+ ],
410
+ "outputs": [
411
+ {"name": "CONDITIONING", "type": "CONDITIONING", "links": [39]}
412
+ ],
413
+ "properties": {"Node name for S&R": "CLIPTextEncode"},
414
+ "widgets_values": [""],
415
+ },
416
+ {
417
+ "id": 5,
418
+ "type": "KSampler",
419
+ "pos": [2578.649165895271, 134.76727061509087],
420
+ "size": [270, 262],
421
+ "flags": {},
422
+ "order": 10,
423
+ "mode": 0,
424
+ "inputs": [
425
+ {"name": "model", "type": "MODEL", "link": 50},
426
+ {"name": "positive", "type": "CONDITIONING", "link": 39},
427
+ {"name": "negative", "type": "CONDITIONING", "link": 23},
428
+ {"name": "latent_image", "type": "LATENT", "link": 24},
429
+ ],
430
+ "outputs": [{"name": "LATENT", "type": "LATENT", "links": [31]}],
431
+ "properties": {"Node name for S&R": "KSampler"},
432
+ "widgets_values": [
433
+ 809278554234612,
434
+ "randomize",
435
+ 16,
436
+ 4,
437
+ "euler_ancestral",
438
+ "karras",
439
+ 1,
440
+ ],
441
+ },
442
+ {
443
+ "id": 38,
444
+ "type": "LoraLoader",
445
+ "pos": [990, 130],
446
+ "size": [290.43334045410154, 126],
447
+ "flags": {},
448
+ "order": 5,
449
+ "mode": 0,
450
+ "inputs": [
451
+ {"name": "model", "type": "MODEL", "link": 29},
452
+ {"name": "clip", "type": "CLIP", "link": 30},
453
+ ],
454
+ "outputs": [
455
+ {"name": "MODEL", "type": "MODEL", "links": [27]},
456
+ {"name": "CLIP", "type": "CLIP", "links": [28]},
457
+ ],
458
+ "title": "Load LoRA",
459
+ "properties": {"Node name for S&R": "LoraLoader"},
460
+ "widgets_values": ["42_アップスケール_remacri_original.pt", 0.4, 0],
461
+ },
462
+ {
463
+ "id": 28,
464
+ "type": "LoraLoader",
465
+ "pos": [1380, 130],
466
+ "size": [290.43334045410154, 126],
467
+ "flags": {},
468
+ "order": 6,
469
+ "mode": 0,
470
+ "inputs": [
471
+ {"name": "model", "type": "MODEL", "link": 27},
472
+ {"name": "clip", "type": "CLIP", "link": 28},
473
+ ],
474
+ "outputs": [
475
+ {"name": "MODEL", "type": "MODEL", "links": [47]},
476
+ {"name": "CLIP", "type": "CLIP", "links": [20, 48]},
477
+ ],
478
+ "title": "Load LoRA",
479
+ "properties": {"Node name for S&R": "LoraLoader"},
480
+ "widgets_values": [
481
+ "43_5cm-illustriousXL_v01_V1-CAME-000035.safetensors",
482
+ 0.4,
483
+ 0,
484
+ ],
485
+ },
486
+ {
487
+ "id": 117,
488
+ "type": "LoraLoader",
489
+ "pos": [1523.7427746854546, 341.08039710943746],
490
+ "size": [290.43334045410154, 126],
491
+ "flags": {},
492
+ "order": 8,
493
+ "mode": 0,
494
+ "inputs": [
495
+ {"name": "model", "type": "MODEL", "link": 47},
496
+ {"name": "clip", "type": "CLIP", "link": 48},
497
+ ],
498
+ "outputs": [
499
+ {"name": "MODEL", "type": "MODEL", "links": [50]},
500
+ {"name": "CLIP", "type": "CLIP", "links": [49]},
501
+ ],
502
+ "properties": {"Node name for S&R": "LoraLoader"},
503
+ "widgets_values": ["5_add_saturation_XL.safetensors", -1.4, 0],
504
+ },
505
+ {
506
+ "id": 118,
507
+ "type": "StringConcatenate",
508
+ "pos": [1363.7030337022063, -256.7007293998441],
509
+ "size": [400, 200],
510
+ "flags": {},
511
+ "order": 3,
512
+ "mode": 0,
513
+ "inputs": [],
514
+ "outputs": [{"name": "STRING", "type": "STRING", "links": [54]}],
515
+ "properties": {"Node name for S&R": "StringConcatenate"},
516
+ "widgets_values": [
517
+ "%prompt%",
518
+ "(disheveled hair:0.5), skinny, "
519
+ "shiny skin, skinny, shiny skin, "
520
+ "(simple background, white "
521
+ "background:1.1), masterpiece, "
522
+ "best quality",
523
+ ",",
524
+ ],
525
+ },
526
+ {
527
+ "id": 3,
528
+ "type": "CLIPTextEncode",
529
+ "pos": [1881.5394309031356, 459.32725000506747],
530
+ "size": [400, 200],
531
+ "flags": {},
532
+ "order": 7,
533
+ "mode": 0,
534
+ "inputs": [{"name": "clip", "type": "CLIP", "link": 20}],
535
+ "outputs": [
536
+ {"name": "CONDITIONING", "type": "CONDITIONING", "links": [23]}
537
+ ],
538
+ "properties": {"Node name for S&R": "CLIPTextEncode"},
539
+ "widgets_values": [
540
+ "(censored, mosaic censoring, bar "
541
+ "censor:1.1), bad quality, worst "
542
+ "quality, worst detail, bad "
543
+ "anatomy, extra fingers, extra "
544
+ "toes, extra legs, 4 toes, 6 toes, "
545
+ "4 fingers, 6 fingers, malformed "
546
+ "fingers, extra limbs, missing "
547
+ "fingers, extra arms, censored, "
548
+ "deformed, disfigured, text, "
549
+ "(multiple views:1.1)"
550
+ ],
551
+ },
552
+ ],
553
+ "links": [
554
+ [20, 28, 1, 3, 0, "CLIP"],
555
+ [23, 3, 0, 5, 2, "CONDITIONING"],
556
+ [24, 27, 0, 5, 3, "LATENT"],
557
+ [25, 1, 0, 10, 0, "MODEL"],
558
+ [26, 1, 1, 10, 1, "CLIP"],
559
+ [27, 38, 0, 28, 0, "MODEL"],
560
+ [28, 38, 1, 28, 1, "CLIP"],
561
+ [29, 10, 0, 38, 0, "MODEL"],
562
+ [30, 10, 1, 38, 1, "CLIP"],
563
+ [31, 5, 0, 56, 0, "LATENT"],
564
+ [32, 45, 0, 56, 1, "VAE"],
565
+ [33, 56, 0, 72, 0, "IMAGE"],
566
+ [39, 2, 0, 5, 1, "CONDITIONING"],
567
+ [47, 28, 0, 117, 0, "MODEL"],
568
+ [48, 28, 1, 117, 1, "CLIP"],
569
+ [49, 117, 1, 2, 0, "CLIP"],
570
+ [50, 117, 0, 5, 0, "MODEL"],
571
+ [54, 118, 0, 2, 1, "STRING"],
572
+ ],
573
+ "groups": [],
574
+ "config": {},
575
+ "extra": {
576
+ "ds": {
577
+ "scale": 0.6303940863128564,
578
+ "offset": [-602.8886169463092, 486.2591310892753],
579
+ },
580
+ "frontendVersion": "1.45.20",
581
+ },
582
+ "version": 0.4,
583
+ }
584
+ }
585
+
586
+
587
+ workflow = build_workflow()
588
+ prompt = json.loads(json.dumps(workflow))
589
+ extra_pnginfo = build_extra_pnginfo()
590
+
591
+
592
+ # Workflow execution
593
+ def main(unload_models: bool | None = None):
594
+ bootstrap_comfyui_runtime()
595
+ add_extra_model_paths()
596
+ import_custom_nodes()
597
+
598
+ # Node imports
599
+ from nodes import (
600
+ CLIPTextEncode,
601
+ CheckpointLoaderSimple,
602
+ EmptyLatentImage,
603
+ KSampler,
604
+ LoraLoader,
605
+ NODE_CLASS_MAPPINGS,
606
+ VAEDecode,
607
+ VAELoader,
608
+ )
609
+
610
+ import torch
611
+
612
+ try:
613
+ with torch.inference_mode():
614
+ checkpointloadersimple = CheckpointLoaderSimple()
615
+ checkpointloadersimple_1 = checkpointloadersimple.load_checkpoint(
616
+ ckpt_name="52_novaAnimeXL_ilV190.safetensors"
617
+ )
618
+ stringconcatenate = NODE_CLASS_MAPPINGS["StringConcatenate"]()
619
+ stringconcatenate_118 = stringconcatenate.EXECUTE_NORMALIZED(
620
+ string_a="%prompt%",
621
+ string_b="(disheveled hair:0.5), skinny, shiny skin, skinny, shiny skin, (simple background, white background:1.1), masterpiece, best quality",
622
+ delimiter=",",
623
+ )
624
+ loraloader = LoraLoader()
625
+ loraloader_10 = loraloader.load_lora(
626
+ lora_name="8_bikabaka.safetensors",
627
+ strength_model=0.3,
628
+ strength_clip=0,
629
+ model=get_value_at_index(checkpointloadersimple_1, 0),
630
+ clip=get_value_at_index(checkpointloadersimple_1, 1),
631
+ )
632
+ loraloader_38 = loraloader.load_lora(
633
+ lora_name="42_\u30a2\u30c3\u30d7\u30b9\u30b1\u30fc\u30eb_remacri_original.pt",
634
+ strength_model=0.4,
635
+ strength_clip=0,
636
+ model=get_value_at_index(loraloader_10, 0),
637
+ clip=get_value_at_index(loraloader_10, 1),
638
+ )
639
+ loraloader_28 = loraloader.load_lora(
640
+ lora_name="43_5cm-illustriousXL_v01_V1-CAME-000035.safetensors",
641
+ strength_model=0.4,
642
+ strength_clip=0,
643
+ model=get_value_at_index(loraloader_38, 0),
644
+ clip=get_value_at_index(loraloader_38, 1),
645
+ )
646
+ loraloader_117 = loraloader.load_lora(
647
+ lora_name="5_add_saturation_XL.safetensors",
648
+ strength_model=-1.4,
649
+ strength_clip=0,
650
+ model=get_value_at_index(loraloader_28, 0),
651
+ clip=get_value_at_index(loraloader_28, 1),
652
+ )
653
+ cliptextencode = CLIPTextEncode()
654
+ cliptextencode_2 = cliptextencode.encode(
655
+ text=get_value_at_index(stringconcatenate_118, 0),
656
+ clip=get_value_at_index(loraloader_117, 1),
657
+ )
658
+ cliptextencode_3 = cliptextencode.encode(
659
+ text="(censored, mosaic censoring, bar censor:1.1), bad quality, worst quality, worst detail, bad anatomy, extra fingers, extra toes, extra legs, 4 toes, 6 toes, 4 fingers, 6 fingers, malformed fingers, extra limbs, missing fingers, extra arms, censored, deformed, disfigured, text, (multiple views:1.1)",
660
+ clip=get_value_at_index(loraloader_28, 1),
661
+ )
662
+ emptylatentimage = EmptyLatentImage()
663
+ emptylatentimage_27 = emptylatentimage.generate(
664
+ width=1152, height=896, batch_size=1
665
+ )
666
+ vaeloader = VAELoader()
667
+ vaeloader_45 = vaeloader.load_vae(vae_name="3_sdxlVAE_sdxlVAE.safetensors")
668
+ ksampler = KSampler()
669
+ vaedecode = VAEDecode()
670
+ for q in range(1):
671
+ node_5_seed = prompt["5"]["inputs"]["seed"] = random.randint(1, 2**64)
672
+ ksampler_5 = ksampler.sample(
673
+ seed=node_5_seed,
674
+ steps=16,
675
+ cfg=4,
676
+ sampler_name="euler_ancestral",
677
+ scheduler="karras",
678
+ denoise=1,
679
+ model=get_value_at_index(loraloader_117, 0),
680
+ positive=get_value_at_index(cliptextencode_2, 0),
681
+ negative=get_value_at_index(cliptextencode_3, 0),
682
+ latent_image=get_value_at_index(emptylatentimage_27, 0),
683
+ )
684
+ vaedecode_56 = vaedecode.decode(
685
+ samples=get_value_at_index(ksampler_5, 0),
686
+ vae=get_value_at_index(vaeloader_45, 0),
687
+ )
688
+ finally:
689
+ cleanup_comfyui_runtime(unload_models=unload_models)
690
+
691
+
692
+ # Entrypoint
693
+ if __name__ == "__main__":
694
+ main()