Download ComfyUI/custom_nodes/koolook/scripts/run_loop_demo_api_test.py from vidfom/aimusic: direct link, hf CLI and curl.
- Browser
- Download file 7.53 kB
-
https://huggingface.co/vidfom/aimusic/resolve/main/ComfyUI/custom_nodes/koolook/scripts/run_loop_demo_api_test.py
- Command line
-
hf download hf://vidfom/aimusic/ComfyUI/custom_nodes/koolook/scripts/run_loop_demo_api_test.py
-
curl -L -o run_loop_demo_api_test.py https://huggingface.co/vidfom/aimusic/resolve/main/ComfyUI/custom_nodes/koolook/scripts/run_loop_demo_api_test.py
7.53 kB
| """Run the loop demo against a local ComfyUI server. | |
| This is a repo-local validation harness for | |
| `docs/automations/loop-demo/LOOP_demo_pipeline.json`. It submits an API-format version | |
| of the queue-controller demo graph to a running ComfyUI server and verifies | |
| that four EXR files are written across four prompt executions. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| import time | |
| import urllib.error | |
| import urllib.parse | |
| import urllib.request | |
| def _validate_http_url(url: str) -> None: | |
| parsed = urllib.parse.urlsplit(str(url or "")) | |
| if parsed.scheme not in {"http", "https"} or not parsed.netloc: | |
| raise RuntimeError(f"Only http(s) ComfyUI server URLs are allowed: {url!r}") | |
| def _post_json(url: str, payload: dict) -> dict: | |
| _validate_http_url(url) | |
| request = urllib.request.Request( | |
| url, | |
| data=json.dumps(payload).encode("utf-8"), | |
| headers={"Content-Type": "application/json"}, | |
| ) | |
| with urllib.request.urlopen(request, timeout=30) as response: # nosec B310 | |
| return json.load(response) | |
| def _get_json(url: str, timeout: float = 15) -> dict: | |
| _validate_http_url(url) | |
| with urllib.request.urlopen(url, timeout=timeout) as response: # nosec B310 | |
| return json.load(response) | |
| def _assert_inside(parent: Path, child: Path) -> None: | |
| parent = parent.resolve() | |
| child = child.resolve() | |
| if parent != child and parent not in child.parents: | |
| raise RuntimeError(f"Refusing to touch path outside repo: {child}") | |
| def _dump_server_debug(server: str) -> None: | |
| for endpoint in ("queue", "history?max_items=20"): | |
| try: | |
| payload = _get_json(f"{server}/{endpoint}", timeout=10) | |
| except Exception as exc: | |
| print(f"Could not read /{endpoint}: {exc}") | |
| continue | |
| print(f"/{endpoint}:") | |
| print(json.dumps(payload, indent=2)[:8000]) | |
| def build_prompt(output_base: Path) -> dict: | |
| """Build the API prompt equivalent of the current loop demo. | |
| The canvas workflow contains a ComfyUI subgraph node. The API prompt below | |
| flattens that demo subgraph to its current single inner node, | |
| `EasyResize_Koolook`, so the prompt can be submitted directly to `/prompt`. | |
| """ | |
| return { | |
| "12": {"class_type": "easy int", "inputs": {"value": 4}}, | |
| "10": { | |
| "class_type": "Text Multiline", | |
| "inputs": {"text": str(output_base) + "\\"}, | |
| }, | |
| "9": { | |
| "class_type": "Easy_Pattern", | |
| "inputs": { | |
| "batch_size": ["12", 0], | |
| "width": 512, | |
| "height": 512, | |
| "bg_color_mode": "Custom", | |
| "show_text": True, | |
| "text_color_mode": "White", | |
| "start_from": 1, | |
| "step": 1, | |
| "font_size": 256, | |
| "position": "center", | |
| "zero_pad": 0, | |
| "bg_color": "#C71585", | |
| "text_color": "#FFFFFF", | |
| "prefix": "", | |
| "suffix": "", | |
| }, | |
| }, | |
| "8": { | |
| "class_type": "EasyAIPipeline", | |
| "inputs": { | |
| "shot_duration": 81, | |
| "seed_value": 453453453, | |
| "instruction": "Place your base folder path in the FIELD below", | |
| "base_directory_path": ["10", 0], | |
| "extension": ".%04d.exr", | |
| "shot_name": "faceLoop", | |
| "ai_method": "", | |
| "version": "1", | |
| "disable_versioning": False, | |
| "enable_overwrite": False, | |
| "no_subfolders": True, | |
| }, | |
| }, | |
| "22": {"class_type": "easy int", "inputs": {"value": 0}}, | |
| "3": { | |
| "class_type": "ImageFromBatch", | |
| "inputs": {"image": ["9", 0], "batch_index": ["22", 0], "length": 1}, | |
| }, | |
| "19": { | |
| "class_type": "EasyResize_Koolook", | |
| "inputs": { | |
| "image": ["3", 0], | |
| "base_on": "Width", | |
| "base_size": 512, | |
| "aspect_ratio": "16:9", | |
| "divisible_by": 32, | |
| "upscale_method": "nearest-exact", | |
| "keep_proportion": "stretch", | |
| "crop_position": "center", | |
| "pad_color_mode": "Black", | |
| "panel_color_mode": "Black", | |
| "device": "cpu", | |
| "pad_color": "0, 0, 0", | |
| "panel_color": "0, 0, 0", | |
| "invert_composed_MASK": False, | |
| }, | |
| }, | |
| "21": { | |
| "class_type": "Koolook_LoopStatus", | |
| "inputs": { | |
| "value": ["19", 0], | |
| "index": ["22", 0], | |
| "total": ["12", 0], | |
| "filepath": ["8", 0], | |
| "label": "EXR_SAFE", | |
| "auto_queue_next": True, | |
| "index_node_id": "22", | |
| "server_url": "http://127.0.0.1:8188", | |
| "max_auto_queue_depth": 100, | |
| "remaining_auto_queue_depth": -1, | |
| }, | |
| }, | |
| "4": { | |
| "class_type": "SaveEXRFrames", | |
| "inputs": { | |
| "images": ["21", 0], | |
| "filepath": ["8", 0], | |
| "tonemap": "linear", | |
| "start_frame": ["22", 0], | |
| "overwrite": True, | |
| "save_workflow": "none", | |
| }, | |
| }, | |
| } | |
| def run(server: str, output_base: Path, repo_root: Path) -> int: | |
| _assert_inside(repo_root, output_base) | |
| run_output = output_base / f"run-{int(time.time() * 1000)}" | |
| _assert_inside(repo_root, run_output) | |
| run_output.mkdir(parents=True, exist_ok=True) | |
| system = _get_json(f"{server}/system_stats") | |
| print(f"ComfyUI {system['system']['comfyui_version']} at {server}") | |
| payload = { | |
| "prompt": build_prompt(run_output.resolve()), | |
| "client_id": "koolook-loop-test", | |
| } | |
| try: | |
| queued = _post_json(f"{server}/prompt", payload) | |
| except urllib.error.HTTPError as exc: | |
| print(exc.read().decode("utf-8", errors="replace")) | |
| return 1 | |
| prompt_id = queued["prompt_id"] | |
| print(f"queued {prompt_id}") | |
| for _ in range(240): | |
| time.sleep(0.5) | |
| history = _get_json(f"{server}/history/{prompt_id}") | |
| if prompt_id in history: | |
| status = history[prompt_id].get("status", {}) | |
| print(json.dumps(status, indent=2)) | |
| if status.get("status_str") != "success": | |
| return 1 | |
| break | |
| else: | |
| print("Timed out waiting for ComfyUI history.") | |
| return 1 | |
| files = [] | |
| for _ in range(240): | |
| files = sorted(run_output.rglob("*.exr")) | |
| if len(files) >= 4: | |
| break | |
| time.sleep(0.5) | |
| for file in files: | |
| print(file) | |
| if len(files) != 4: | |
| print(f"Expected 4 EXRs, found {len(files)}.") | |
| _dump_server_debug(server) | |
| return 1 | |
| print("OK: wrote 4 EXR frames.") | |
| return 0 | |
| def main() -> int: | |
| repo_root = Path(__file__).resolve().parents[1] | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--server", default="http://127.0.0.1:8188") | |
| parser.add_argument( | |
| "--output-base", | |
| type=Path, | |
| default=repo_root / ".tmp" / "comfy-loop-test-output", | |
| ) | |
| args = parser.parse_args() | |
| return run(args.server.rstrip("/"), args.output_base, repo_root) | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |