Skip to content

Releases: Chaoses-Ib/ComfyScript

v0.4.3

05 Apr 11:15

Choose a tag to compare

Fixes

  • Runtime
    • Node outputs from nodes other than SaveImage nodes are now also converted to Result (#22)
    • Add wait() method on ImageBatchResult
    • Real mode: Unwrap args when calling node (#31)
  • Nodes

v0.4.2

26 Feb 11:17

Choose a tag to compare

New features

  • Runtime: Virtual mode: Path/PurePath can now be used as node arguments without str() (#24)

Fixes

  • Runtime: comfyui package main() is now async (#28)
  • Transpiler: Multiplexer node elimination with removed inputs (#25)

v0.4.1

13 Feb 10:11

Choose a tag to compare

New features

  • Runtime: Standalone virtual mode: Join ComfyUI (wait for all tasks to be done) at process exit by default (#23)

Changes

  • Runtime
    • String enums with members {'true', 'false'} and {'yes', 'no'} are now recognized as bool enums
    • Limit the maximum number of enum values to 2000 by default (#23)

Fixes

  • Runtime: Standalone: Restore the original event loop after starting ComfyUI (#23)
  • Runtime/Transpiler: Bugs related to empty id (#23, #22)

v0.4.0: Standalone virtual mode, global enums, node docstrings, and real mode workflow tracking

11 Feb 12:54

Choose a tag to compare

New features

  • Runtime

    • Standalone virtual mode

      Virtual mode can now start a ComfyUI server by itself. By default, if the server is not available at http://127.0.0.1:8188/, virtual mode will try to start a new ComfyUI server. See load() for details.

    • Global enums

      Some enums are now also available in a shorter form, for example:

      # Before
      CheckpointLoaderSimple.ckpt_name.v1_5_pruned_emaonly
      # After
      Checkpoints.v1_5_pruned_emaonly

      Currently, these global enums include Checkpoints, CLIPs, CLIPVisions, ControlNets, Diffusers, Embeddings, GLIGENs, Hypernetworks, Loras, PhotoMakers, StyleModels, UNETs, UpscaleModels, VAEs and Samplers, Schedulers. See global enums for details.

    • Node docstrings

      Information about the nodes are now added to type stubs, including the display name, description, category, input range and round and output node. For example:

      In standalone virtual mode and real mode, each node's module name will also be added to their docstrings. This can be used to figure out where a node came from.

    • Real mode

      • Workflow tracking (#21)

        Now workflows will be automatically tracked and saved to images in real mode. But note that changes to inputs made by user code instead of nodes will not be tracked. This means the saved workflows are not guaranteed to be able to reproduce the same results. If user code changes have to be made, one can add some custom metadata to keep the results reproducible. See real mode for an example.

      • The hidden inputs of nodes are now shown in type stubs (#21)

        e.g. extra_pnginfo, prompt and unique_id.

      • Improved compatibility with custom nodes by trying to simulate the real ComfyUI environment as far as possible

    • Enums are now of StrEnum and FloatEnum types if applicable. Now their values can be used interchangeably with str and float.

      For example, they can now be used as real mode node arguments.

  • Nodes: New nodes for converting images between Image and PIL.Image.Image, mainly for use in real mode.

    def ImageToPIL(
        images: Image
    ) -> PilImage
    
    def PILToImage(
        images: PilImage
    ) -> Image
    
    def PILToMask(
        images: PilImage
    ) -> Image

    See images for all nodes about loading/saving images.

Changes

  • All dependencies are optional now

    Now ComfyScript should be installed with [default], for example:

    python -m pip install -e ".[default]"

    [default] is necessary to install common dependencies. See pyproject.toml for other options. If no option is specified, comfy-script will be installed without any dependencies.

  • Runtime

    • VAEs enum member names are changed

      For example, foo.vae.pt and foo.vae.safetensors's name will now be foo instead of foo_vae.

    • Real mode: Nodes are now wrapped instead of being directly modified (#20)

Fixes

  • Runtime
    • Ignore invalid str enum values (#22)

      This is mainly for mitigating hacks made by ComfyUI-VideoHelperSuite. See #22 for details.

    • Node output name to id conversion

v0.3.2

03 Feb 20:15

Choose a tag to compare

New features

Changes

  • Nodes: Additional nodes are now installed as package dependencies instead of Git submodules (#7, #15)

    See comfyui-legacy for details.

Fixes

  • Runtime
    • get_nodes_info() should not load nodes (#15)
    • Real mode: Modify a class only once (#18)

v0.3.1

29 Jan 14:50

Choose a tag to compare

New features

Fixes

  • Runtime
    • Fix support for nodes with int and float enum types (#13)
    • Real mode: Fix ComfyUI root locating (#14 @ShagaONhan)
  • Transpiler: Fix mapping optional inputs to positional arguments (#12)
  • Runtime/Transpiler: String literal emitting with strings ending with '

v0.3.0: Using ComfyUI as a function library

28 Jan 20:06

Choose a tag to compare

New features

  • Add real mode (#6)

    See below for details.

  • Add pyproject.toml and PyPI package

    After installing the package by python -m pip install -e ., you can import comfy_script anywhere, no longer limited to the root directory of the repository.

Changes

  • The project layout was changed. You cannot import script at the repository root anymore.

    Instead, you should install ComfyScript as a package by python -m pip install -e ., or add src directory to sys.path:

    import sys
    sys.path.insert(0, 'src')

    And then replace all

    from script.runtime import *
    load()
    from script.runtime.nodes import *

    with

    from comfy_script.runtime import *
    load()
    from comfy_script.runtime.nodes import *

Fixes

  • Runtime: Fix support for outputs of enum types (#9)

Real mode

In the original mode - virtual mode, calling a node is not executing it. Instead, the entire workflow will only get executed when it is sent to ComfyUI's server, by generating workflow JSON from the workflow (wf.api_format_json()).

In real mode, calling a node will execute it directly:

print(CheckpointLoaderSimple('v1-5-pruned-emaonly.ckpt'))
# (<comfy.model_patcher.ModelPatcher object at 0x000002198721ECB0>, <comfy.sd.CLIP object at 0x000002198721C250>, <comfy.sd.VAE object at 0x000002183B128EB0>)

Real mode is thus more flexible and powerful than virtual mode. It can be used to:

  • Doing ML research.

  • Reuse custom nodes in other projects.

    Besides research projects and commercial products, it is also possible to integrate ComfyUI into sd-webui. This way, a feature can be implemented as a node once and then be used in both ComfyUI and sd-webui.

  • Making developing custom nodes easier.

  • Optimizing caching to run workflows faster.

    Because real mode executes the nodes directly, it cannot utilize ComfyUI's cache system. But if the lifetime of variables are maintained carefully enough, it is possible to run workflows faster than ComfyUI, since ComfyUI's cache system uses a naive single-slot cache.

Differences from virtual mode:

  • Scripts cannot be executed through the API of a ComfyUI server.

    However, it is still possible to run scripts on a remote machine without the API. For example, you can launching a Jupyter Server and connect to it remotely.

  • As mentioned above, nodes will not cache the output themselves. It is the user's responsibility to avoid re-executing nodes with the same inputs.

  • The outputs of output nodes (e.g. SaveImage) is not converted to result classes (e.g. ImageBatchResult).

    This may be changed in future versions.

A complete example:

from comfy_script.runtime.real import *
load()
from comfy_script.runtime.real.nodes import *

# Or: with torch.inference_mode()
with Workflow():
    model, clip, vae = CheckpointLoaderSimple('v1-5-pruned-emaonly.ckpt')
    print(model, clip, vae, sep='\n')
    # <comfy.model_patcher.ModelPatcher object at 0x000002198721ECB0>
    # <comfy.sd.CLIP object at 0x000002198721C250>
    # <comfy.sd.VAE object at 0x000002183B128EB0>

    conditioning = CLIPTextEncode('beautiful scenery nature glass bottle landscape, , purple galaxy bottle,', clip)
    conditioning2 = CLIPTextEncode('text, watermark', clip)
    print(conditioning2)
    # [[
    #   tensor([[
    #     [-0.3885, ..., 0.0674],
    #     ...,
    #     [-0.8676, ..., -0.0057]
    #   ]]),
    #   {'pooled_output': tensor([[-1.2670e+00, ..., -1.5058e-01]])}
    # ]]

    latent = EmptyLatentImage(512, 512, 1)
    print(latent)
    # {'samples': tensor([[
    #   [[0., ..., 0.],
    #     ...,
    #    [0., ..., 0.]],
    #   [[0., ..., 0.],
    #     ...,
    #    [0., ..., 0.]],
    #   [[0., ..., 0.],
    #     ...,
    #    [0., ..., 0.]],
    #   [[0., ..., 0.],
    #     ...,
    #    [0., ..., 0.]]
    # ]])}

    latent = KSampler(model, 156680208700286, 20, 8, 'euler', 'normal', conditioning, conditioning2, latent, 1)

    image = VAEDecode(latent, vae)
    print(image)
    # tensor([[
    #   [[0.3389, 0.3652, 0.3428],
    #     ...,
    #     [0.4277, 0.3789, 0.1445]],
    #   ...,
    #   [[0.6348, 0.5898, 0.5270],
    #     ...,
    #     [0.7012, 0.6680, 0.5952]]
    # ]])

    print(SaveImage(image, 'ComfyUI'))
    # {'ui': {'images': [
    #   {'filename': 'ComfyUI_00001_.png',
    #     'subfolder': '',
    #     'type': 'output'}
    # ]}}

Naked mode

If you have ever gotten to know the internals of ComfyUI, you will realize that real mode is not completely real. Some changes were made to nodes to improve the development experience and keep the code compatible with virtual mode. If you want the real real mode, you can enable naked mode by load(naked=True).

In naked mode, ComfyScript will not execute any code after load() (except Workflow(), with can be basically replaced with torch.inference_mode()).

An example:

import random

from comfy_script.runtime.real import *
load(naked=True)
from comfy_script.runtime.real.nodes import *

# Or: with torch.inference_mode()
with Workflow():
    checkpointloadersimple = CheckpointLoaderSimple()
    checkpointloadersimple_4 = checkpointloadersimple.load_checkpoint(
        ckpt_name="sd_xl_base_1.0.safetensors"
    )

    emptylatentimage = EmptyLatentImage()
    emptylatentimage_5 = emptylatentimage.generate(
        width=1024, height=1024, batch_size=1
    )

    cliptextencode = CLIPTextEncode()
    cliptextencode_6 = cliptextencode.encode(
        text="evening sunset scenery blue sky nature, glass bottle with a galaxy in it",
        clip=checkpointloadersimple_4[1],
    )

    cliptextencode_7 = cliptextencode.encode(
        text="text, watermark", clip=checkpointloadersimple_4[1]
    )

    checkpointloadersimple_12 = checkpointloadersimple.load_checkpoint(
        ckpt_name="sd_xl_refiner_1.0.safetensors"
    )

    cliptextencode_15 = cliptextencode.encode(
        text="evening sunset scenery blue sky nature, glass bottle with a galaxy in it",
        clip=checkpointloadersimple_12[1],
    )

    cliptextencode_16 = cliptextencode.encode(
        text="text, watermark", clip=checkpointloadersimple_12[1]
    )

    ksampleradvanced = KSamplerAdvanced()
    vaedecode = VAEDecode()
    saveimage = SaveImage()

    for q in range(10):
        ksampleradvanced_10 = ksampleradvanced.sample(
            add_noise="enable",
            noise_seed=random.randint(1, 2**64),
            steps=25,
            cfg=8,
            sampler_name="euler",
            scheduler="normal",
            start_at_step=0,
            end_at_step=20,
            return_with_leftover_noise="enable",
            model=checkpointloadersimple_4[0],
            positive=cliptextencode_6[0],
            negative=cliptextencode_7[0],
            latent_image=emptylatentimage_5[0],
        )

        ksampleradvanced_11 = ksampleradvanced.sample(
            add_noise="disable",
            noise_seed=random.randint(1, 2**64),
            steps=25,
            cfg=8,
            sampler_name="euler",
            scheduler="normal",
            start_at_step=20,
            end_at_step=10000,
            return_with_leftover_noise="disable",
            model=checkpointloadersimple_12[0],
            positive=cliptextencode_15[0],
            negative=cliptextencode_16[0],
            latent_image=ksampleradvanced_10[0],
        )

        vaedecode_17 = vaedecode.decode(
            samples=ksampleradvanced_11[0], vae=checkpointloadersimple_12[2]
        )

        saveimage_19 = saveimage.save_images(
            filename_prefix="ComfyUI", images=vaedecode_17[0]
        )

As you may have noticed, naked mode is compatible with the code generated by ComfyUI-to-Python-Extension. You can use it to convert ComfyUI's workflows to naked mode scripts.

v0.2.2

15 Jan 15:31

Choose a tag to compare

Fixes

  • Runtime/Transpiler: Upgrade nest_asyncio to v1.5.9 to avoid freezing the process under certain circumstances (erdewit/nest_asyncio#87)

v0.2.1

04 Jan 02:40

Choose a tag to compare

Fixes

  • Runtime
    • Fix type name collision (#5)
    • Fix support for any type (*) inputs and outputs (#5)
    • Fix support for enum members with sunder names (#5)
    • Fix support for inputs with bool value list (#5)
    • Fix type stub support for non-Python-id input names (#5)
    • Fix type stub support for inputs with multi-line default values (#5)
    • from script.runtime.nodes import * will not override load() anymore
  • Transpiler: Fix support for ComfyUI web UI's integer node ids (#4)
  • Fix error messages for empty Git submodules (#7)

v0.2.0: A Python front end for ComfyUI

01 Jan 00:31

Choose a tag to compare

New features

  • Add script runtime (#1)
  • Transpiler
    • Support API format workflows
    • Add CLI
    • Add multiplexer node elimination
  • Error messages for missing dependencies

Fixes

  • Transpiler
    • Support image_upload config
    • Support non-integer node ids
    • Allow node outputs to have missing slot_index
    • Some node/variable name errors

Changes