comfyanonymous / ComfyUI

The most powerful and modular diffusion model GUI, api and backend with a graph/nodes interface.
https://www.comfy.org/
GNU General Public License v3.0
50.45k stars 5.3k forks source link

the output image without workflow in metadata by comfyui API call #2100

Open onefish51 opened 9 months ago

onefish51 commented 9 months ago

great work ! when I run the demoscript_examples/websockets_api_example.py, I found the output image without the workflow. ComfyUI_00020_ image

How I get the workflow when I call comfyUI by websockets API ?

onefish51 commented 9 months ago

I see! because "extra_data" not in prompt_text = """ so https://github.com/comfyanonymous/ComfyUI/blob/777f6b15225197898a5f49742682a2be859072d7/server.py#L474 not work

onefish51 commented 9 months ago

How can I auto convert prompt_text to workflow ?

prompt_text = """
{
    "3": {
        "class_type": "KSampler",
        "inputs": {
            "cfg": 8,
            "denoise": 1,
            "latent_image": [
                "5",
                0
            ],
            "model": [
                "4",
                0
            ],
            "negative": [
                "7",
                0
            ],
            "positive": [
                "6",
                0
            ],
            "sampler_name": "euler",
            "scheduler": "normal",
            "seed": 8566257,
            "steps": 20
        }
    },
    "4": {
        "class_type": "CheckpointLoaderSimple",
        "inputs": {
            "ckpt_name": "v1-5-pruned-emaonly.ckpt"
        }
    },
    "5": {
        "class_type": "EmptyLatentImage",
        "inputs": {
            "batch_size": 1,
            "height": 512,
            "width": 512
        }
    },
    "6": {
        "class_type": "CLIPTextEncode",
        "inputs": {
            "clip": [
                "4",
                1
            ],
            "text": "masterpiece best quality girl"
        }
    },
    "7": {
        "class_type": "CLIPTextEncode",
        "inputs": {
            "clip": [
                "4",
                1
            ],
            "text": "bad hands"
        }
    },
    "8": {
        "class_type": "VAEDecode",
        "inputs": {
            "samples": [
                "3",
                0
            ],
            "vae": [
                "4",
                2
            ]
        }
    },
    "9": {
        "class_type": "SaveImage",
        "inputs": {
            "filename_prefix": "ComfyUI",
            "images": [
                "8",
                0
            ]
        }
    }
}
"""
Thireus commented 9 months ago

I'd like to know as well.

RousseauRemi commented 3 weeks ago

Hello, I (with the help of Perplexity) create a method to do that for my workflows, I don't use a lot of node so, there is maybe some type node to add, but it's a good start :

SAVE_IMAGE = "SaveImage"
CTRL_NET_APPLY_ADVANCED = "ControlNetApplyAdvanced"
CLIP_SET_LAST_LAYER = "CLIPSetLastLayer"
PREVIEW_IMAGE = "PreviewImage"
LOAD_IMAGE = "LoadImage"
VAE_DECODE = "VAEDecode"
LORA_LOADER = "LoraLoader"
CLIP_TEXT_ENCODE = "CLIPTextEncode"
K_SAMPLER = "KSampler"
CONTROL_NET_LOADER = "ControlNetLoader"
EMPTY_LATENT_IMAGE = "EmptyLatentImage"
OPENPOSE_PREPROCESSOR = "OpenposePreprocessor"
CHECKPOINT_LOADER_SIMPLE = "CheckpointLoaderSimple"
SAVE_IMAGE_WEBSOCKET = "SaveImageWebsocket"

 def convert_workflow_to_prompt_format(self, workflow_json):
        prompt_format = {}
        link_to_node = {}
        node_output_types = {}

        for node in workflow_json.get("nodes", []):
            node_id = str(node["id"])
            outputs = node.get("outputs", [])
            for i, output in enumerate(outputs):
                links = output.get("links", [])
                if links is None:
                    continue
                for link in links:
                    if link is not None:
                        link_to_node[link] = (node_id, i)
                        node_output_types[f"{node_id}_{i}"] = output.get("type")

        for node in workflow_json.get("nodes", []):
            node_id = str(node["id"])
            node_type = node["type"]

            node_inputs = {}
            widgets_values = node.get("widgets_values", [])

            if node_type == CHECKPOINT_LOADER_SIMPLE and widgets_values:
                node_inputs["ckpt_name"] = widgets_values[0]
            elif node_type == LORA_LOADER and len(widgets_values) >= 3:
                node_inputs["lora_name"] = widgets_values[0]
                node_inputs["strength_model"] = widgets_values[1]
                node_inputs["strength_clip"] = widgets_values[2]
            elif node_type == CONTROL_NET_LOADER and widgets_values:
                node_inputs["control_net_name"] = widgets_values[0]
            elif node_type == LOAD_IMAGE and widgets_values:
                node_inputs["image"] = widgets_values[0]
                node_inputs["upload"] = "image"
            elif node_type == CTRL_NET_APPLY_ADVANCED and len(widgets_values) >= 3:
                node_inputs["strength"] = widgets_values[0]
                node_inputs["start_percent"] = widgets_values[1]
                node_inputs["end_percent"] = widgets_values[2]
            elif node_type == CLIP_SET_LAST_LAYER and widgets_values:
                node_inputs["stop_at_clip_layer"] = widgets_values[0]
            elif node_type == EMPTY_LATENT_IMAGE and len(widgets_values) >= 3:
                node_inputs["width"] = widgets_values[0]
                node_inputs["height"] = widgets_values[1]
                node_inputs["batch_size"] = widgets_values[2]
            elif node_type == K_SAMPLER and len(widgets_values) >= 6:
                node_inputs["seed"] = widgets_values[0]
                node_inputs["steps"] = widgets_values[2]
                node_inputs["cfg"] = widgets_values[3]
                node_inputs["sampler_name"] = widgets_values[4]
                node_inputs["scheduler"] = widgets_values[5]
                node_inputs["denoise"] = 1
            elif node_type == CLIP_TEXT_ENCODE and widgets_values:
                node_inputs["text"] = widgets_values[0]
            elif node_type == OPENPOSE_PREPROCESSOR and len(widgets_values) >= 4:
                node_inputs["detect_body"] = widgets_values[0]
                node_inputs["detect_hand"] = widgets_values[1]
                node_inputs["detect_face"] = widgets_values[2]
                node_inputs["resolution"] = widgets_values[3]

            inputs = node.get("inputs", [])
            for input_data in inputs:
                input_name = input_data["name"]
                link = input_data.get("link")
                if link is not None:
                    source_node_id, slot_index = link_to_node.get(link, (link, 0))
                    node_inputs[input_name] = [source_node_id, slot_index]

            prompt_format[node_id] = {
                "inputs": node_inputs,
                "class_type": node_type,
                "_meta": {
                    "title": self.get_node_title(node_type)
                }
            }

        return prompt_format

    def get_node_title(self, node_type):
        titles = {
            CHECKPOINT_LOADER_SIMPLE: "Load Checkpoint",
            LORA_LOADER: "Load LoRA",
            CONTROL_NET_LOADER: "Load ControlNet Model",
            CTRL_NET_APPLY_ADVANCED: "Apply ControlNet (Advanced)",
            LOAD_IMAGE: "Load Image",
            OPENPOSE_PREPROCESSOR: "OpenPose Pose",
            CLIP_SET_LAST_LAYER: "CLIP Set Last Layer",
            CLIP_TEXT_ENCODE: "CLIP Text Encode (Prompt)",
            EMPTY_LATENT_IMAGE: "Empty Latent Image",
            K_SAMPLER: "KSampler",
            VAE_DECODE: "VAE Decode",
            SAVE_IMAGE_WEBSOCKET: "SaveImageWebsocket"
        }
        return titles.get(node_type, node_type)

I test it on a workflow that I converted to a prompt workflow using ComfyUi Dev mode option, and it return the same prompt using this method.