diff --git a/README.md b/README.md index 5812f8e0b..bfd35bfe0 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Ctrl can also be replaced with Cmd instead for macOS users [ 2. Open a command line on Windows. This is usually cmd.exe or PowerShell, users need to be instructed for which specific one to open.]: # [ 3. Create a virtual environment on a disk with a lot of space. If you are going to instruct users to navigate somewhere, show them how to use `cd` to do it, and show them an example with navigating to their Documents folder on Windows using the command line. Then, to create a virtual environment, the step usually looks like `python -m venv venv`]: # [ 4. Activate the virtual environment. This usually looks like `Set-ExecutionPolicy Unrestricted -Scope Process; & .\venv\Scripts\activate.ps1` in PowerShell. Observe the caveat of the execution policy problem. Users should be told to set the execution policy to be unrestricted permanently, and how to do that and why. Remember, they need something ergonomic and easy.]: # -[ 5. Install this package. Currently, that is done with `pip install git+https://github.com/doctorpangloss/ComfyUI.git@installable`.]: # +[ 5. Install this package. Currently, that is done with `pip install git+https://github.com/hiddenswitch/ComfyUI.git@installable`.]: # [ 6. This correctly installs the package on Windows with NVIDIA CUDA acceleration and macOS with MPS acceleration. However, users may have weird setups and need more help.]: # [ 7. Now users need a model. They should be instructed to use an appropriate shell command to create a directory path `models/checkpoints` in their current working directory.]: # [ 8. Then, they must download https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.ckpt into the path ./models/checkpoints/v1-5-pruned-emaonly.ckpt]: # diff --git a/comfy/cmd/folder_paths.py b/comfy/cmd/folder_paths.py index d405a63d9..ecd9e2991 100644 --- a/comfy/cmd/folder_paths.py +++ b/comfy/cmd/folder_paths.py @@ -22,7 +22,7 @@ else: base_path = os.getcwd() models_dir = os.path.join(base_path, "models") folder_names_and_paths["checkpoints"] = ([os.path.join(models_dir, "checkpoints")], supported_pt_extensions) -folder_names_and_paths["configs"] = ([os.path.join(models_dir, "configs"), resource_filename("comfy", "configs/")], [".yaml"]) +folder_names_and_paths["configs"] = ([os.path.join(models_dir, "configs"), resource_filename("comfy", "configs/")], set([".yaml"])) folder_names_and_paths["loras"] = ([os.path.join(models_dir, "loras")], supported_pt_extensions) folder_names_and_paths["vae"] = ([os.path.join(models_dir, "vae")], supported_pt_extensions) folder_names_and_paths["clip"] = ([os.path.join(models_dir, "clip")], supported_pt_extensions) diff --git a/script_examples/basic_api_example.py b/script_examples/basic_api_example.py index 242d3175f..9b6aa0e92 100644 --- a/script_examples/basic_api_example.py +++ b/script_examples/basic_api_example.py @@ -1,18 +1,14 @@ -import json -from urllib import request, parse -import random - -#This is the ComfyUI api prompt format. - -#If you want it for a specific workflow you can "enable dev mode options" -#in the settings of the UI (gear beside the "Queue Size: ") this will enable -#a button on the UI to save workflows in api format. - -#keep in mind ComfyUI is pre alpha software so this format will change a bit. - -#this is the one for the default workflow -prompt_text = """ -{ +# This sample shows how to execute a ComfyUI workflow, saving an image file to the location you specify. +# +# This script does not need to run within a ComfyUI directory. Instead, this can be used inside your own +# Python application or located elsewhere. It should **not** be in the Git repository directory. +# +# First, you will need to install ComfyUI. Follow the **Manual Install (Windows, Linux, macOS)** instructions in the +# README.md. If you are an experienced developer, instead run `pip install git+https://github.com/hiddenswitch/ComfyUI.git` +# +# Now you should develop your workflow. Start ComfyUI as normal; navigate to "Settings" in the menu, and check "Enable +# Dev mode Options". Then click "Save (API Format)". Copy and paste the contents of this file here: +_PROMPT_FROM_WEB_UI = { "3": { "class_type": "KSampler", "inputs": { @@ -98,23 +94,66 @@ prompt_text = """ } } } -""" - -def queue_prompt(prompt): - p = {"prompt": prompt} - data = json.dumps(p).encode('utf-8') - req = request.Request("http://127.0.0.1:8188/prompt", data=data) - request.urlopen(req) -prompt = json.loads(prompt_text) -#set the text prompt for our positive CLIPTextEncode -prompt["6"]["inputs"]["text"] = "masterpiece best quality man" +# Observe this is an ordinary dictionary. The JSON that was saved from the workflow is compatible with Python syntax. +# +# Now, QUIT AND CLOSE YOUR COMFYUI SERVER. You don't need it anymore. This script will handle starting and stopping +# the server for you. Actually, it will create an object that does the same thing that pressing Queue Prompt does. +# +# We'll now write the entrypoint of our script. This is an `async def main()` because async helps us start and stop the +# code object that will run your workflow, just like pressing the Queue Prompt button. +async def main(): + import copy -#set the seed for our KSampler node -prompt["3"]["inputs"]["seed"] = 5 + # Let's make some changes to the prompt. First we'll change the input text: + prompt_dict = copy.deepcopy(_PROMPT_FROM_WEB_UI) + prompt_dict["6"]["inputs"]["text"] = "masterpiece best quality man" + + # Let's set the seed for our KSampler node: + prompt_dict["3"]["inputs"]["seed"] = 5 + + # Now we will validate the prompt. This Prompt class contains everything we need to validate the prompt. + from comfy.api.components.schema.prompt import Prompt + prompt = Prompt.validate(prompt_dict) + + # Your prompt is ready to be processed. + # You should **not** be running the ComfyUI application (the thing you start with /main.py). You don't need it. You + # are not making any HTTP requests, you are not running a server, you are not connecting to anything, you are not + # executing the main.py from the ComfyUI git repository, you don't even need that Git repository located anywhere. + + from comfy.cli_args_types import Configuration + + # Let's specify some settings. Suppose this is the structure of your directories: + # C:/Users/comfyanonymous/Documents/models + # C:/Users/comfyanonymous/Documents/models/checkpoints + # C:/Users/comfyanonymous/Documents/models/loras + # C:/Users/comfyanonymous/Documents/outputs + # Then your "current working directory" (`cwd`) should be set to "C:/Users/comfyanonymous/Documents": + # configuration.cwd = "C:/Users/comfyanonymous/Documents/" + # Or, if your models directory is located in the same directory as this script: + # configuration.cwd = os.path.dirname(__file__) + configuration = Configuration() + + from comfy.client.embedded_comfy_client import EmbeddedComfyClient + async with EmbeddedComfyClient(configuration=configuration) as client: + # This will run your prompt + outputs = await client.queue_prompt(prompt) + + # At this point, your prompt is finished and all the outputs, like saving images, have been completed. + # Now the outputs will contain the same thing that the Web UI expresses: a file path for each output. + # Let's find the node ID of the first SaveImage node. This will work when you change your workflow JSON from + # the example above. + save_image_node_id = next(key for key in prompt if prompt[key].class_type == "SaveImage") + + # Now let's print the absolute path to the image. + print(outputs[save_image_node_id]["images"][0]["abs_path"]) + # At this point, all the models have been unloaded from VRAM, and everything has been cleaned up. -queue_prompt(prompt) - +# Now let's make this script runnable: +import asyncio +if __name__ == "__main__": + # Since our main function is async, it must be run as async too. + asyncio.run(main()) diff --git a/script_examples/remote_api_example.py b/script_examples/remote_api_example.py new file mode 100644 index 000000000..1406614cd --- /dev/null +++ b/script_examples/remote_api_example.py @@ -0,0 +1,139 @@ +# This sample shows how to execute a ComfyUI workflow against a remote ComfyUI server or the server running on your +# local machine. It will return the bytes of the image in the workflow. +# +# This script does not need to run within a ComfyUI directory. Instead, this can be used inside your own +# Python application or located elsewhere. It should **not** be in the Git repository directory. +# +# First, you will need to install ComfyUI. Follow the **Manual Install (Windows, Linux, macOS)** instructions in the +# README.md. If you are an experienced developer, instead run `pip install git+https://github.com/hiddenswitch/ComfyUI.git` +# +# Now you should develop your workflow. Start ComfyUI as normal; navigate to "Settings" in the menu, and check "Enable +# Dev mode Options". Then click "Save (API Format)". Copy and paste the contents of this file here: +_PROMPT_FROM_WEB_UI = { + "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 + ] + } + } +} + + +# Observe this is an ordinary dictionary. The JSON that was saved from the workflow is compatible with Python syntax. +# +# Now, QUIT AND CLOSE YOUR COMFYUI SERVER. You don't need it anymore. This script will handle starting and stopping +# the server for you. Actually, it will create an object that does the same thing that pressing Queue Prompt does. +# +# We'll now write the entrypoint of our script. This is an `async def main()` because async helps us start and stop the +# code object that will run your workflow, just like pressing the Queue Prompt button. +async def main(): + import copy + + # Let's make some changes to the prompt. First we'll change the input text: + prompt_dict = copy.deepcopy(_PROMPT_FROM_WEB_UI) + prompt_dict["6"]["inputs"]["text"] = "masterpiece best quality man" + + # Let's set the seed for our KSampler node: + prompt_dict["3"]["inputs"]["seed"] = 5 + + # Now we will validate the prompt. This Prompt class contains everything we need to validate the prompt. + from comfy.api.components.schema.prompt import Prompt + prompt = Prompt.validate(prompt_dict) + + # Your prompt is ready to be processed. You should start your ComfyUI server; or, specify a remote URL for it. + # Let's create the client we will use to access it: + from comfy.client.aio_client import AsyncRemoteComfyClient + client = AsyncRemoteComfyClient(server_address="http://localhost:8188") + + # Now let's get the bytes of the PNG image saved by the SaveImage node: + png_image_bytes = await client.queue_prompt(prompt) + + # You can save these bytes wherever you need! + with open("image.png", "rb") as f: + f.write(png_image_bytes) + + +# Now let's make this script runnable: +import asyncio + +if __name__ == "__main__": + # Since our main function is async, it must be run as async too. + asyncio.run(main()) diff --git a/script_examples/websockets_api_example.py b/script_examples/websockets_api_example.py deleted file mode 100644 index 57a6cbd9b..000000000 --- a/script_examples/websockets_api_example.py +++ /dev/null @@ -1,164 +0,0 @@ -#This is an example that uses the websockets api to know when a prompt execution is done -#Once the prompt execution is done it downloads the images using the /history endpoint - -import websocket #NOTE: websocket-client (https://github.com/websocket-client/websocket-client) -import uuid -import json -import urllib.request -import urllib.parse - -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 - -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 - ] - } - } -} -""" - -prompt = json.loads(prompt_text) -#set the text prompt for our positive CLIPTextEncode -prompt["6"]["inputs"]["text"] = "masterpiece best quality man" - -#set the seed for our KSampler node -prompt["3"]["inputs"]["seed"] = 5 - -ws = websocket.WebSocket() -ws.connect("ws://{}/ws?clientId={}".format(server_address, client_id)) -images = get_images(ws, prompt) - -#Commented out code to display the output images: - -# for node_id in images: -# for image_data in images[node_id]: -# from PIL import Image -# import io -# image = Image.open(io.BytesIO(image_data)) -# image.show() -