Add all-node N-Suite test workflow
This commit is contained in:
@@ -38,6 +38,10 @@ For uninstallation, remove the extension through ComfyUI Manager or delete its f
|
||||
# Update
|
||||
Update through ComfyUI Manager. For a manual install, run `git pull` in the cloned extension directory, install `requirements.txt` again in ComfyUI's Python environment, and restart ComfyUI.
|
||||
|
||||
## Test workflow
|
||||
|
||||
[`examples/N-Suite-all-nodes-test.json`](examples/N-Suite-all-nodes-test.json) connects all 14 N-Suite node types in one workflow. Follow the [test instructions](examples/README.md) to add an image, a short MP4, and numbered PNG frames before running it.
|
||||
|
||||
# Features
|
||||
|
||||
## 📽️ Video Nodes 📽️
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
# N-Suite: test di tutti i nodi
|
||||
|
||||
Apri `N-Suite-all-nodes-test.json` in ComfyUI. Il workflow contiene tutti i 14 tipi di nodo N-Suite presenti in questa versione, con anteprime dei risultati e tre prove di salvataggio video.
|
||||
|
||||
Prima di premere **Queue Prompt**:
|
||||
|
||||
1. Scegli una tua immagine nel nodo **LoadImage** della sezione 01. Il loader GPT usa Moondream, già selezionato nel workflow.
|
||||
2. Copia un MP4 breve nella cartella `ComfyUI/input/n-suite`, ricarica la pagina e selezionalo nel nodo **LoadVideo** della sezione 04. Un video di pochi secondi riduce il tempo necessario per RIFE.
|
||||
3. Metti almeno due immagini PNG della stessa dimensione, con nomi numerati come `0001.png` e `0002.png`, nella cartella `ComfyUI/input/n-suite/test_frames`. Il nodo **String Variable** della sezione 05 contiene il percorso visto dal container: `/workspace/ComfyUI/input/n-suite/test_frames`.
|
||||
4. Premi **Queue Prompt**. Il nodo CLIP usa `clip_l.safetensors`, già disponibile nell'installazione per cui è stato creato il workflow.
|
||||
|
||||
La risposta Moondream e i condizionamenti CLIP compaiono nei nodi **Preview as Text**. Le immagini e la maschera compaiono nelle anteprime. I video vengono scritti in `ComfyUI/output/n-suite/videos` con prefissi `n_suite_test_*`. Se un ramo fallisce, ComfyUI evidenzia il nodo che ha generato l'errore.
|
||||
|
||||
Il file è generato da `generate_test_workflow.py` usando gli schemi `/object_info` di ComfyUI. Su un'installazione diversa, rigeneralo con `python examples/generate_test_workflow.py http://127.0.0.1:8188` dalla cartella del repository.
|
||||
@@ -0,0 +1,144 @@
|
||||
"""Generate the all-node smoke test from a running ComfyUI instance.
|
||||
|
||||
Usage: python examples/generate_test_workflow.py http://127.0.0.1:8188
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from urllib.request import urlopen
|
||||
|
||||
|
||||
url = sys.argv[1].rstrip("/") if len(sys.argv) > 1 else "http://127.0.0.1:8188"
|
||||
schema = json.load(urlopen(f"{url}/object_info"))
|
||||
nodes = []
|
||||
links = []
|
||||
|
||||
|
||||
def add(kind, pos, values=None, title=None, size=None):
|
||||
info = schema[kind]
|
||||
values = values or {}
|
||||
inputs, widgets = [], []
|
||||
for name, spec in {**info["input"].get("required", {}), **info["input"].get("optional", {})}.items():
|
||||
raw_type = spec[0]
|
||||
input_type = "COMBO" if isinstance(raw_type, list) else raw_type
|
||||
options = spec[1] if len(spec) > 1 and isinstance(spec[1], dict) else {}
|
||||
entry = {"name": name, "type": input_type, "link": None}
|
||||
if input_type in ("COMBO", "STRING", "INT", "FLOAT", "BOOLEAN") and not options.get("forceInput"):
|
||||
entry["widget"] = {"name": name}
|
||||
default = options.get("default", raw_type[0] if isinstance(raw_type, list) and raw_type else "")
|
||||
widgets.append(values.get(name, default))
|
||||
inputs.append(entry)
|
||||
if kind == "LoadImage":
|
||||
inputs.append({"name": "upload", "type": "IMAGEUPLOAD", "widget": {"name": "upload"}, "link": None})
|
||||
widgets.append("image")
|
||||
node_id = len(nodes) + 1
|
||||
node = {
|
||||
"id": node_id, "type": kind, "pos": pos, "size": size or [350, 180],
|
||||
"flags": {}, "order": node_id - 1, "mode": 0, "inputs": inputs,
|
||||
"outputs": [{"name": name, "type": typ, "links": []} for name, typ in
|
||||
zip(info.get("output_name", info["output"]), info["output"])],
|
||||
"properties": {"Node name for S&R": kind}, "widgets_values": widgets,
|
||||
}
|
||||
if title:
|
||||
node["title"] = title
|
||||
nodes.append(node)
|
||||
return node_id
|
||||
|
||||
|
||||
def connect(source, slot, target, input_name):
|
||||
origin = nodes[source - 1]
|
||||
dest = nodes[target - 1]
|
||||
dest_slot = next(i for i, item in enumerate(dest["inputs"]) if item["name"] == input_name)
|
||||
assert dest["inputs"][dest_slot]["link"] is None
|
||||
link_id = len(links) + 1
|
||||
links.append([link_id, source, slot, target, dest_slot, origin["outputs"][slot]["type"]])
|
||||
origin["outputs"][slot]["links"].append(link_id)
|
||||
dest["inputs"][dest_slot]["link"] = link_id
|
||||
|
||||
|
||||
image = add("LoadImage", [80, 100], {"image": "example.png"}, "Scegli la tua immagine", [380, 330])
|
||||
questions = add("String Variable [n-suite]", [80, 500],
|
||||
{"string": "What is in this image?,What colors are in this image?"}, "Domande di prova")
|
||||
dynamic = add("DynamicPrompt [n-suite]", [520, 490],
|
||||
{"cached": "NO", "number_of_random_tag": "Fixed", "fixed_number_of_random_tag": 1})
|
||||
caption_model = add("GPT Loader Simple [n-suite]", [520, 100], {"ckpt_name": "moondream"})
|
||||
caption = add("GPT Sampler [n-suite]", [930, 100],
|
||||
{"max_tokens": 128, "cached": "NO", "print_output": "enable"}, size=[390, 700])
|
||||
caption_preview = add("PreviewAny", [1400, 150], title="Risposta Moondream")
|
||||
noise = add("Float Variable [n-suite]", [80, 1040], {"value": 0.1})
|
||||
pad = add("ImagePadForOutpaintAdvanced [n-suite]", [500, 930],
|
||||
{"left": 32, "right": 32, "top": 32, "bottom": 32}, size=[430, 590])
|
||||
padded_preview = add("PreviewImage", [1030, 970], title="Immagine con bordo", size=[350, 300])
|
||||
mask_to_image = add("MaskToImage", [1030, 1330])
|
||||
mask_preview = add("PreviewImage", [1410, 1320], title="Maschera del bordo", size=[350, 300])
|
||||
clip = add("CLIPLoader", [2030, 100], {"clip_name": "clip_l.safetensors", "type": "stable_diffusion"})
|
||||
encode = add("CLIPTextEncodeAdvancedNSuite [n-suite]", [2460, 100],
|
||||
{"styles": "NAI", "positive_prompt": "a small test image", "negative_prompt": "blurry"}, size=[400, 350])
|
||||
positive_preview = add("PreviewAny", [2940, 100], title="Condizionamento positivo")
|
||||
negative_preview = add("PreviewAny", [2940, 400], title="Condizionamento negativo")
|
||||
multiplier = add("Integer Variable [n-suite]", [80, 2060], {"value": 2})
|
||||
video = add("LoadVideo [n-suite]", [430, 1950],
|
||||
{"video": "SELECT_VIDEO.mp4", "framerate": "original", "resize_by": "none",
|
||||
"images_limit": 0, "batch_size": 0, "starting_frame": 0, "autoplay": False, "use_ram": False},
|
||||
size=[420, 570])
|
||||
interpolator = add("FrameInterpolator [n-suite]", [940, 1990])
|
||||
interpolated_video = add("SaveVideo [n-suite]", [1430, 1970],
|
||||
{"SaveVideo": True, "SaveFrames": False, "filename_prefix": "n_suite_test_interpolated"})
|
||||
video_info = add("PreviewAny", [940, 2290], title="Metadati video originale")
|
||||
folder = add("String Variable [n-suite]", [80, 2990],
|
||||
{"string": "/workspace/ComfyUI/input/n-suite/test_frames"}, "Cartella frame: cambia qui", [500, 130])
|
||||
image_folder = add("LoadImageFromFolder [n-suite]", [660, 2860])
|
||||
image_folder_preview = add("PreviewImage", [1100, 2840], title="Immagini dalla cartella", size=[330, 260])
|
||||
manual_metadata = add("SetMetadataForSaveVideo [n-suite]", [1100, 3210],
|
||||
{"fps": 24, "VideoName": "n_suite_folder"})
|
||||
manual_video = add("SaveVideo [n-suite]", [1550, 2890],
|
||||
{"SaveVideo": True, "SaveFrames": False, "filename_prefix": "n_suite_test_manual_metadata"})
|
||||
frame_folder = add("LoadFramesFromFolder [n-suite]", [660, 3530], {"fps": 24})
|
||||
frame_folder_preview = add("PreviewImage", [1100, 3520], title="Frame numerati", size=[330, 260])
|
||||
frame_video = add("SaveVideo [n-suite]", [1550, 3510],
|
||||
{"SaveVideo": True, "SaveFrames": False, "filename_prefix": "n_suite_test_folder_frames"})
|
||||
|
||||
for args in [
|
||||
(questions, 0, dynamic, "variable_prompt"), (dynamic, 0, caption, "prompt"),
|
||||
(caption_model, 0, caption, "model"), (image, 0, caption, "image"),
|
||||
(caption, 0, caption_preview, "source"), (image, 0, pad, "image"),
|
||||
(noise, 0, pad, "noise"), (pad, 0, padded_preview, "images"),
|
||||
(pad, 1, mask_to_image, "mask"), (mask_to_image, 0, mask_preview, "images"),
|
||||
(clip, 0, encode, "clip"), (encode, 0, positive_preview, "source"),
|
||||
(encode, 1, negative_preview, "source"), (video, 0, interpolator, "images"),
|
||||
(video, 2, interpolator, "METADATA"), (multiplier, 0, interpolator, "multiplier"),
|
||||
(interpolator, 0, interpolated_video, "images"),
|
||||
(interpolator, 1, interpolated_video, "METADATA"), (video, 2, video_info, "source"),
|
||||
(folder, 0, image_folder, "folder"), (folder, 0, frame_folder, "folder"),
|
||||
(image_folder, 0, image_folder_preview, "images"),
|
||||
(image_folder, 0, manual_video, "images"),
|
||||
(image_folder, 3, manual_metadata, "number_of_frames"),
|
||||
(manual_metadata, 0, manual_video, "METADATA"),
|
||||
(frame_folder, 0, frame_folder_preview, "images"),
|
||||
(frame_folder, 0, frame_video, "images"),
|
||||
(frame_folder, 1, frame_video, "METADATA"),
|
||||
]:
|
||||
connect(*args)
|
||||
|
||||
groups = [
|
||||
("01 FOTO + MOONDREAM: scegli la foto in LoadImage", [40, 40, 1750, 790], "#3f789e"),
|
||||
("02 IMAGE PAD: controlla immagine e maschera", [40, 870, 1760, 790], "#637c49"),
|
||||
("03 CLIP: usa il modello clip_l presente", [1980, 40, 1400, 680], "#76578e"),
|
||||
("04 VIDEO: copia un MP4 in input/n-suite, ricarica e selezionalo", [40, 1880, 1790, 690], "#896a3c"),
|
||||
("05 CARTELLA: aggiungi 0001.png e 0002.png in test_frames", [40, 2780, 1920, 1050], "#3f789e"),
|
||||
]
|
||||
used = {node["type"] for node in nodes if "[n-suite]" in node["type"].lower()}
|
||||
expected = {name for name in schema if "[n-suite]" in name.lower()}
|
||||
assert used == expected, f"Missing N-Suite nodes: {sorted(expected - used)}"
|
||||
workflow = {
|
||||
"id": str(uuid.uuid4()), "revision": 0, "last_node_id": len(nodes), "last_link_id": len(links),
|
||||
"nodes": nodes, "links": links,
|
||||
"groups": [{"id": i, "title": title, "bounding": bounds, "color": color, "flags": {}}
|
||||
for i, (title, bounds, color) in enumerate(groups, 1)],
|
||||
"config": {}, "extra": {"ds": {"scale": 0.55, "offset": [70, 70]}}, "version": 0.4,
|
||||
}
|
||||
destination = Path(__file__).with_name("N-Suite-all-nodes-test.json")
|
||||
destination.write_text(json.dumps(workflow, ensure_ascii=False, indent=2) + "\n")
|
||||
print(f"Saved {destination}: {len(nodes)} nodes, {len(links)} links, {len(used)} N-Suite types")
|
||||
Reference in New Issue
Block a user