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
52.34k stars 5.52k forks source link

Is there an API you can call? #952

Open jdc4429 opened 1 year ago

jdc4429 commented 1 year ago

I just wanted to know if there was an API I can call like automatic? if so is it compatible, if not, are you going to add one? I run a website and currently use an API to allow users to create their own images. I was really liking that your interface can run SDXL (especially on my 8GB RTX 2070) and was hoping to implement it's API. 1080p images that can be used as wallpapers and such. Can also create an image in 4 different sizes currently. My website is https://AiImageCentral.com if anyone wants to check it out.

Regards,

Jeff

HuangZiy commented 1 year ago

same needs

comfyanonymous commented 1 year ago

Yes, read the top of: https://github.com/comfyanonymous/ComfyUI/blob/master/script_examples/basic_api_example.py

And this is how you can use the websockets: https://github.com/comfyanonymous/ComfyUI/blob/master/script_examples/websockets_api_example.py

jdc4429 commented 1 year ago

Thank you! Love your efficient use of memory and quick response.

Wow there are a lot of fields. I'm not sure what all of them do.

jdc4429 commented 1 year ago

Seems to be an issue.. For some reason when I run the script it works the first time, but when you call it again after that it does not run.

got prompt 100%|████████████████████████████████████████████████████████████████| 20/20 [00:02<00:00, 8.41it/s] Prompt executed in 3.84 seconds got prompt Prompt executed in 0.00 seconds got prompt Prompt executed in 0.00 seconds

comfyanonymous commented 1 year ago

comfyui caches the node outputs of the last workflow so you submit the same workflow twice it won't execute anything in it.

Set a random seed instead of the fixed seed in the script.

jdc4429 commented 1 year ago

Hmm.. I did set to -1 for the seed.. Does it not recognize -1 as random? Nevermind, error says no values less then 1. :) I didn't notice at first the other fields changing the information at the bottom. Thank you!

jdc4429 commented 1 year ago

Now my only issue in a random number generator.. I'm not finding what the actual range is for the seed for SD... Any idea what the max is? Does it depend on the model?

comfyanonymous commented 1 year ago

The max is: 18446744073709551616

jdc4429 commented 1 year ago

Well I thought it was working.. I have it working if I run the python script myself from the command line But when I try executing from shell I get this error...

Traceback (most recent call last): File "/var/www/html/comfy.py", line 181, in ws = websocket.WebSocket() TypeError: WebSocket.init() missing 3 required positional arguments: 'environ', 'socket', and 'rfile'

I'm not sure why... I install the websocket-client.. even put in script to install in environment. Says requirements are satisfied. Not sure if conflicting with another library. Will try creating environment for Comfy by itself.

comfyanonymous commented 1 year ago

For the websocket example: pip install websocket-client and make sure you don't have another package named "websocket" installed.

jdc4429 commented 1 year ago

I create a conda environment called comfy... which should be clean. Installed requirements.txt to comfy environment pip3 install websocket-client From my understanding, any outside package should not interfere? I'm not sure why I'm still getting the error... Traceback (most recent call last): File "/var/www/html/comfy.py", line 181, in ws = websocket.WebSocket() TypeError: WebSocket.init() missing 3 required positional arguments: 'environ', 'socket', and 'rfile'

I did pip list...

Package Version


bidict 0.22.1 click 8.1.3 Flask 2.2.5 Flask-SocketIO 5.3.4 importlib-metadata 6.6.0 itsdangerous 2.1.2 Jinja2 3.1.2 MarkupSafe 2.1.2 pip 23.2.1 python-engineio 4.4.1 python-socketio 5.8.0 setuptools 40.8.0 typing_extensions 4.5.0 websocket-client 1.6.1 Werkzeug 2.2.3 zipp 3.15.0

jdc4429 commented 1 year ago

I even removed all websocket packages from base...

I still get the same error. I don't understand why. It was working previously and now It's not even working when I do it manually. This is really odd...

I rebooted my system... And now again I can do it manually. but when I try and execute from the python script I get the error above.

jdc4429 commented 1 year ago

I even tried using my other webui interface.. but that doesn't work either. lol

RuntimeError: (405, '405: Method Not Allowed')

I would really like to get this working but I'm at a loss. I don't know what else I can try. I removed all other sockets, reinstalled a few times. Rebooted.. It keeps asking for those arguments when I execute from another python script...

jdc4429 commented 1 year ago

What is really screwed up.. is I can run manually from base.. But when I go into the newly created comfy environment it gives me that error.

comfyanonymous commented 1 year ago

WebSocket.init() missing 3 required positional arguments: 'environ', 'socket', and 'rfile'

That means you have "websocket" installed not "websocket-client"

jdc4429 commented 1 year ago

I uninstalled it... and rebooted.. and that was only on base.. that should not affect the newly created conda environment. I think maybe my Conda environment is messed up somehow. I may try reinstalling Anaconda... No I specifically installed websocket-client and made sure to remove any other sockets even from base... Something is screwy.

jdc4429 commented 1 year ago

I had an error with conda that was causing it to fail to allow me to install anything. I had to delete my whole anaconda3 dir and reinstall from scratch. Will setup everything I need from base and see what happens. Thanks again for your help.

jdc4429 commented 1 year ago

I think something is screwy with that code..

Just reinstalled Anaconda completely. Installed all the requirements to base. Created a new conda environment for Comfy...

Look at this.. when run first time...

(comfy) jeff@jeff-desktop:/var/www/html$ python comfy.py "test" "test" 1920 1080 Defaulting to user installation because normal site-packages is not writeable Looking in indexes: https://pypi.org/simple, https://pypi.ngc.nvidia.com Collecting websocket-client Obtaining dependency information for websocket-client from https://files.pythonhosted.org/packages/d3/a3/63e9329c8cc9be6153e919e17d0ef5b60d537fed78564872951b95bcc17c/websocket_client-1.6.1-py3-none-any.whl.metadata Downloading websocket_client-1.6.1-py3-none-any.whl.metadata (7.6 kB) Downloading websocket_client-1.6.1-py3-none-any.whl (56 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 56.9/56.9 kB 326.6 kB/s eta 0:00:00 Installing collected packages: websocket-client Successfully installed websocket-client-1.6.1 Traceback (most recent call last): File "/var/www/html/comfy.py", line 181, in ws = websocket.WebSocket() TypeError: WebSocket.init() missing 3 required positional arguments: 'environ', 'socket', and 'rfile' (comfy) jeff@jeff-desktop:/var/www/html$

jdc4429 commented 1 year ago

This is everything from the w's install from pip on my base...

w3lib 1.21.0 watchdog 2.1.6 wcwidth 0.2.5 webencodings 0.5.1 websocket-client 0.58.0 Werkzeug 2.2.3 whatthepatch 1.0.2 wheel 0.38.4 widgetsnbextension 4.0.5 wrapt 1.14.1 wurlitzer 3.0.2

No other sockets... Maybe it's the version?

Again.. I can run manually from base.. Just when I try to execute from script from running script or in the conda environment comfy.

Also...

(comfy) jeff@jeff-desktop:/var/www/html$ pip3 uninstall websockets WARNING: Skipping websockets as it is not installed. (comfy) jeff@jeff-desktop:/var/www/html$ pip3 uninstall websocket WARNING: Skipping websocket as it is not installed. (comfy) jeff@jeff-desktop:/var/www/html$ (base) jeff@jeff-desktop:~$ pip3 uninstall websockets WARNING: Skipping websockets as it is not installed. (base) jeff@jeff-desktop:~$ pip3 uninstall websocket WARNING: Skipping websocket as it is not installed. (base) jeff@jeff-desktop:~$

pydn commented 1 year ago

If you are still having issues with the API, I created an extension to convert any comfyui workflow (including custom nodes) into executable python code that will run without relying on the comfyui server. You just run the workflow_api.json file through the extension and it creates a python script that will immediate run your workflow.

If you are comfortable in Python, it may be more straightforward to use than the API. If interested, check it out here: https://github.com/pydn/ComfyUI-to-Python-Extension

rossaai commented 1 year ago

If you are still having issues with the API, I created an extension to convert any comfyui workflow (including custom nodes) into executable python code that will run without relying on the comfyui server. You just run the workflow_api.json file through the extension and it creates a python script that will immediate run your workflow.

If you are comfortable in Python, it may be more straightforward to use than the API. If interested, check it out here: https://github.com/pydn/ComfyUI-to-Python-Extension

It's amazing to add natively in ComfyUI (below Save API Format Button)

Thrasosoft commented 1 year ago

Is it possible to call the endpoint and get the image back without using websockets? I'm attempting to call my ComfyUI server from AWS lambda. I had trouble getting it to allow websockets so I am using the regular API and I can get it queue up a message but it doesn't return the image or any indication of when its done. I want to avoid polling the server constantly until the generation is complete. I also have the same issue calling from Postman. any help would be appreciated

pydn commented 1 year ago

@Thrasosoft I could be wrong, but I don't think you can use the API without polling the server. You could use my extension to automatically generate Python code from your workflow, which will execute your workflow without polling the server. https://github.com/pydn/ComfyUI-to-Python-Extension

Thrasosoft commented 1 year ago

What would it take to get something like Automatic1111's "--no-gradio-queue" option that uses http requests instead of a websocket?

rossaai commented 1 year ago

What we opted for was to directly access the ComfyUI nodes using python and then build a simple API with FastAPI.

What would it take to get something like Automatic1111's "--no-gradio-queue" option that uses http requests instead of a websocket?

Thrasosoft commented 1 year ago

When attempting to call the Server's /view? endpoint I am unable to figure out how to encode the image properly for display in a web-browser. It seems from the included script examples the data is being returned as bytes but I need it to be base64. However the below code does not get the job done. any reccomendations for correctly encoding the image information?

        const imageUrl = `${process.env.COMFY_UI_URL}/view?filename=${filename}`;
        try {
          const imageData = await requestHandler.get(imageUrl, requestOptions);
          if (imageData) {
            const base64Image = Buffer.from(imageData.data, "binary").toString("base64");
            response = { images: [base64Image] };
          } else {
            response = "Error Generating Images";
          }
        } catch (er) {
          console.log('Error getting image data: ',er);
          error = true;
          response = "Error Generating Images";
        }
      }
      return response;

The end result of this code is an encoded string that cannot be decoded as an image on the front-end. how have others solved this?

Richard0403 commented 8 months ago

When attempting to call the Server's /view? endpoint I am unable to figure out how to encode the image properly for display in a web-browser. It seems from the included script examples the data is being returned as bytes but I need it to be base64. However the below code does not get the job done. any reccomendations for correctly encoding the image information?

        const imageUrl = `${process.env.COMFY_UI_URL}/view?filename=${filename}`;
        try {
          const imageData = await requestHandler.get(imageUrl, requestOptions);
          if (imageData) {
            const base64Image = Buffer.from(imageData.data, "binary").toString("base64");
            response = { images: [base64Image] };
          } else {
            response = "Error Generating Images";
          }
        } catch (er) {
          console.log('Error getting image data: ',er);
          error = true;
          response = "Error Generating Images";
        }
      }
      return response;

The end result of this code is an encoded string that cannot be decoded as an image on the front-end. how have others solved this?

have you resolve this? base64 encode the out binary, can not preview, do you know why? @Thrasosoft

ZachariBarnes commented 8 months ago

When attempting to call the Server's /view? endpoint I am unable to figure out how to encode the image properly for display in a web-browser. It seems from the included script examples the data is being returned as bytes but I need it to be base64. However the below code does not get the job done. any reccomendations for correctly encoding the image information?

        const imageUrl = `${process.env.COMFY_UI_URL}/view?filename=${filename}`;
        try {
          const imageData = await requestHandler.get(imageUrl, requestOptions);
          if (imageData) {
            const base64Image = Buffer.from(imageData.data, "binary").toString("base64");
            response = { images: [base64Image] };
          } else {
            response = "Error Generating Images";
          }
        } catch (er) {
          console.log('Error getting image data: ',er);
          error = true;
          response = "Error Generating Images";
        }
      }
      return response;

The end result of this code is an encoded string that cannot be decoded as an image on the front-end. how have others solved this?

have you resolve this? base64 encode the out binary, can not preview, do you know why? @Thrasosoft

yeah I solved this already. I needed to get the response as an array buffer and encode it as base64

export async function getBase64Image(url:string) {
  return requestHandler
    .get(url, {
      responseType: "arraybuffer",
    })
    .then((response) =>
      Buffer.from(response.data, "binary").toString("base64")
    );
}

Then in my img tag on my webapp I needed to specify the encoding in the img src

export const getImgSrc = (imageBytes) => {
  return "data:image/png;base64," + imageBytes;
};
...
          <img
            src={props.imgSrc ? props.imgSrc : getImgSrc(props.imgResult)}
            alt={props.character.portraitPrompt}
            className={section1Styles.image}
          />
HamiguaLu commented 6 months ago

Hi, which ws endpoint/api I should use to get the current quenu size, including pending and running Thanks in advance

liusida commented 5 months ago

Hi, which ws endpoint/api I should use to get the current quenu size, including pending and running Thanks in advance

Hi, @HamiguaLu , I think it's this: https://github.com/comfyanonymous/ComfyUI/blob/831511a1eecbe271e302f2f2053f285f00614180/server.py#L440

jdc4429 commented 3 months ago

I got everything working for my website now... https://AiImageCentral.com

But I am trying to figure out how to send the requests in python but allow them to queue. Like if I do from the comfyui, I can keep queuing as many images to generate as I want. But using the second example script with the api I can only click once until that image finishes generating. Could you show me how to allow the users on the web site to keep adding to the queue instead of having to wait for an image to finish before you can click the submit button for the next generation.

If you could please help me figure this out. I have provided a copy of all the relevent code.

Here is some of my code in the main index.php that calls the python script after the submit button is pressed.

if (isset($_POST['submit_first'])) {
    $width = (int) 1920;
    $height = (int) 1080;
    $data = array('rprompt' => $rprompt,'nprompt' => $nprompt,'smodel' => $smodel,'width' => $width,'height' => $height,'cfg' => $cfg,'steps' => $steps,'sampler_name' => $sampler);
    $jsonData = json_encode($data, JSON_PRETTY_PRINT);

// $jsonData = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); $file = 'parameters.json'; file_put_contents($file, $jsonData); $command = 'C:\inetpub\wwwroot\python\python.exe c:\inetpub\wwwroot\comfy.py >> logfile 2>&1'; exec($command); header("Location: " . $_SERVER['PHP_SELF']); // Redirect to the current page to prevent resubmission } elseif (isset($_POST['submit_second'])) { $width = (int) 1280; $height = (int) 720; $data = array('rprompt' => $rprompt,'nprompt' => $nprompt,'smodel' => $smodel,'width' => $width,'height' => $height,'cfg' => $cfg,'steps' => $steps,'sampler_name' => $sampler); $jsonData = json_encode($data, JSON_PRETTY_PRINT); $file = 'parameters.json'; file_put_contents($file, $jsonData); $command = 'C:\inetpub\wwwroot\python\python.exe c:\inetpub\wwwroot\comfy.py >> logfile 2>&1 &'; exec($command); header("Location: " . $_SERVER['PHP_SELF']); // Redirect to the current page to prevent resubmission

I have included a copy of my python script (comfy.py) I call for the image generation and manipulation.

`import os import websocket import uuid import json import urllib.request import urllib.parse import sys import io import base64 from PIL import Image import random import subprocess

file = 'parameters.json' with open(file, 'r') as json_file: data = json.load(json_file)

rprompt = data['rprompt'] nprompt = data['nprompt'] smodel = data['smodel'] iwidth = int(data['width']) # Ensure width is an integer iheight = int(data['height']) # Ensure height is an integer cfg = int(data['cfg']) # get CFG steps = int(data['steps']) # get Number of Steps sampler = data['sampler_name'] server_address = "127.0.0.1:8188" client_id = str(uuid.uuid4())

def queue_prompt(prompt): p = {"prompt": prompt, "client_id": client_id} data = json.dumps(p).encode('utf-8') req = urllib.request.Request("http://{}/prompt".format(server_address), data=data) return json.loads(urllib.request.urlopen(req).read())

def get_image(filename, subfolder, folder_type): data = {"filename": filename, "subfolder": subfolder, "type": folder_type} url_values = urllib.parse.urlencode(data) with urllib.request.urlopen("http://{}/view?{}".format(server_address, url_values)) as response: return response.read()

def get_history(prompt_id): with urllib.request.urlopen("http://{}/history/{}".format(server_address, prompt_id)) as response: return json.loads(response.read())

def get_images(ws, prompt): prompt_id = queue_prompt(prompt)['prompt_id'] output_images = {} while True: out = ws.recv() if isinstance(out, str): message = json.loads(out) if message['type'] == 'executing': data = message['data'] if data['node'] is None and data['prompt_id'] == prompt_id: break # Execution is done else: continue # previews are binary data

history = get_history(prompt_id)[prompt_id]
for o in history['outputs']:
    for node_id in history['outputs']:
        node_output = history['outputs'][node_id]
        if 'images' in node_output:
            images_output = []
            for image in node_output['images']:
                image_data = get_image(image['filename'], image['subfolder'], image['type'])
                images_output.append(image_data)
        output_images[node_id] = images_output

return output_images

def random_number():

Define the lower and upper bounds of the range

lower_bound = 1
upper_bound = 18446744073709551616
# Generate a random number within the specified range
random_number = random.randint(lower_bound, upper_bound)
return random_number

prompt_text = """ { "3": { "inputs": { "seed": 763024097853041, "steps": 8, "cfg": 1, "sampler_name": "euler_ancestral", "scheduler": "sgm_uniform", "denoise": 1, "model": [ "4", 0 ], "positive": [ "6", 0 ], "negative": [ "7", 0 ], "latent_image": [ "5", 0 ] }, "class_type": "KSampler", "_meta": { "title": "KSampler" } }, "4": { "inputs": { "ckpt_name": "Epicrealism.HADES.SDXL.LIGHTNING.DPMSDE.CFG.1.STEP.4.safetensors" }, "class_type": "CheckpointLoaderSimple", "_meta": { "title": "Load Checkpoint" } }, "5": { "inputs": { "width": 1280, "height": 720, "batch_size": 1 }, "class_type": "EmptyLatentImage", "_meta": { "title": "Empty Latent Image" } }, "6": { "inputs": { "text": "A full body shot of a beautiful young girl smiling", "clip": [ "4", 1 ] }, "class_type": "CLIPTextEncode", "_meta": { "title": "CLIP Text Encode (Prompt)" } }, "7": { "inputs": { "text": "", "clip": [ "4", 1 ] }, "class_type": "CLIPTextEncode", "_meta": { "title": "CLIP Text Encode (Prompt)" } }, "8": { "inputs": { "samples": [ "3", 0 ], "vae": [ "4", 2 ] }, "class_type": "VAEDecode", "_meta": { "title": "VAE Decode" } }, "9": { "inputs": { "filename_prefix": "ComfyUI", "images": [ "14", 0 ] }, "class_type": "SaveImage", "_meta": { "title": "Save Image" } }, "14": { "inputs": { "upscale_model": [ "15", 0 ], "image": [ "8", 0 ] }, "class_type": "ImageUpscaleWithModel", "_meta": { "title": "Upscale Image (using Model)" } }, "15": { "inputs": { "model_name": "RealESRGAN_x2plus.pth" }, "class_type": "Upscale Model Loader", "_meta": { "title": "Upscale Model Loader" } } } """ prompt = json.loads(prompt_text)

Set the text prompt for our positive CLIPTextEncode

prompt["4"]["inputs"]["ckpt_name"] = smodel prompt["5"]["inputs"]["height"] = iheight prompt["5"]["inputs"]["width"] = iwidth prompt["6"]["inputs"]["text"] = rprompt prompt["7"]["inputs"]["text"] = nprompt

Seed must always be different

rnumber = random_number() prompt["3"]["inputs"]["seed"] = rnumber prompt["3"]["inputs"]["cfg"] = cfg prompt["3"]["inputs"]["steps"] = steps prompt["3"]["inputs"]["sampler_name"] = sampler

ws = websocket.WebSocket() ws.connect("ws://{}/ws?clientId={}".format(server_address, client_id)) images = get_images(ws, prompt)

for node_id in images: for image_data in images[node_id]: image = Image.open(io.BytesIO(image_data)) image.save("output.png")

os.system("del c:\inetpub\wwwroot\ComfyUI\ComfyUI\output\ComfyUI00001.png")
os.system("exiftool.exe c:\inetpub\wwwroot\ComfyUI\ComfyUI\output\ComfyUI00001.png | findstr Prompt > c:\inetpub\wwwroot\lastprompt.txt") os.system("c:\inetpub\wwwroot\python\python.exe c:\inetpub\wwwroot\png2jpg.py") drprompt = urllib.parse.unquote(rprompt) command = f'c:\inetpub\wwwroot\exiftool -UserComment="Model:{smodel} Seed:{rnumber} Sampler:{sampler} Cfg:{cfg} Steps:{steps} Prompt:{drprompt} Negative:{nprompt}" output.jpg' subprocess.run(command, shell=True) os.system("move output.jpg 1080p\Requested\") os.system("random.bat 1080p\Requested\output.jpg")

`