mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-01-31 08:40:19 +08:00
Compare commits
25 Commits
72bf314ff7
...
21f83fc066
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21f83fc066 | ||
|
|
7458e20465 | ||
|
|
b931b37e30 | ||
|
|
866a4619db | ||
|
|
fa71050a07 | ||
|
|
7795a4e86c | ||
|
|
c4c388ffc8 | ||
|
|
c804c0c12e | ||
|
|
6c9110564b | ||
|
|
b4dcbdfac7 | ||
|
|
2c859e9558 | ||
|
|
174d91c9ed | ||
|
|
e1cf4f7420 | ||
|
|
357f89a4bf | ||
|
|
477f330415 | ||
|
|
36e19df686 | ||
|
|
aba97d6ada | ||
|
|
7419345b76 | ||
|
|
41b4c3ea73 | ||
|
|
5b27c661c6 | ||
|
|
6572cbb61d | ||
|
|
4f12985e45 | ||
|
|
847e3cc3a2 | ||
|
|
e7ebda4b61 | ||
|
|
eeee0f5b1b |
31
.dockerignore
Normal file
31
.dockerignore
Normal file
@ -0,0 +1,31 @@
|
||||
# This file should remain in sync with .gitignore. If you need to make changes,
|
||||
# please add a comment explaining why. For items that must be removed, comment
|
||||
# them out instead of deleting them.
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
/output/
|
||||
/input/
|
||||
# This file prevents the image from building and would be overwritten by the
|
||||
# /data volume in any case.
|
||||
#!/input/example.png
|
||||
/models/
|
||||
/temp/
|
||||
/custom_nodes/
|
||||
!custom_nodes/example_node.py.example
|
||||
extra_model_paths.yaml
|
||||
/.vs
|
||||
.vscode/
|
||||
.idea/
|
||||
venv/
|
||||
.venv/
|
||||
/web/extensions/*
|
||||
!/web/extensions/logging.js.example
|
||||
!/web/extensions/core/
|
||||
/tests-ui/data/object_info.json
|
||||
/user/
|
||||
*.log
|
||||
web_custom_versions/
|
||||
.DS_Store
|
||||
openapi.yaml
|
||||
filtered-openapi.yaml
|
||||
uv.lock
|
||||
3
.gitattributes
vendored
3
.gitattributes
vendored
@ -1,3 +1,6 @@
|
||||
/web/assets/** linguist-generated
|
||||
/web/** linguist-vendored
|
||||
comfy_api_nodes/apis/__init__.py linguist-generated
|
||||
# Force LF eol for Docker entrypoint (fix "exec: no such file or directory"
|
||||
# error with CRLF checkouts)
|
||||
entrypoint.sh text eol=lf
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
# If you modify this file, remember to update .dockerignore as well.
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
/output/
|
||||
|
||||
85
Dockerfile
Normal file
85
Dockerfile
Normal file
@ -0,0 +1,85 @@
|
||||
# Docker buildfile for the ComfyUI image, with support for hardware
|
||||
# acceleration, file ownership synchronization, custom nodes, and custom node
|
||||
# managers.
|
||||
|
||||
# While Python 3.13 is well supported by ComfyUI, some older custom node packs
|
||||
# may not work correctly with this version, which is why we're staying on Python
|
||||
# 3.12 for now.
|
||||
#
|
||||
# Users are free to try different base Python image tags (e.g., 3.13, alpine,
|
||||
# *-slim), but for maintainability, only one base version is officially
|
||||
# supported at a time.
|
||||
FROM python:3.12.12-trixie
|
||||
|
||||
# Install cmake, which is an indirect installation dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends cmake
|
||||
|
||||
# Create a regular user whose UID and GID will match the host user's at runtime.
|
||||
# Also create a home directory for this user (-m), as some common Python tools
|
||||
# (such as uv) interact with the user’s home directory.
|
||||
RUN useradd -m comfyui
|
||||
|
||||
# Install ComfyUI under /comfyui and set folder ownership to the comfyui user.
|
||||
# With the legacy Docker builder (DOCKER_BUILDKIT=0), WORKDIR always creates missing
|
||||
# directories as root (even if a different USER is active). To ensure the comfyui user
|
||||
# can write inside, ownership must be fixed manually.
|
||||
WORKDIR /comfyui
|
||||
RUN chown comfyui:comfyui .
|
||||
|
||||
# Install ComfyUI as ComfyUI
|
||||
USER comfyui
|
||||
|
||||
# Set up a Python virtual environment and configure it as the default Python.
|
||||
#
|
||||
# Reasons for using a virtual environment:
|
||||
# - Some custom nodes use third-party tools like uv, which do not support
|
||||
# user-level installations.
|
||||
# - Custom node managers may install or update dependencies as the regular user,
|
||||
# so a global installation is not an option.
|
||||
# This leaves virtual environments as the only viable choice.
|
||||
RUN python -m venv .venv
|
||||
ENV PATH="/comfyui/.venv/bin:$PATH"
|
||||
|
||||
# Install ComfyUI's Python dependencies. Although dependency keeping is also
|
||||
# performed at startup, building ComfyUI's base dependencies into the image
|
||||
# significantly speeds up each containers' first run.
|
||||
#
|
||||
# Since this step takes a long time to complete, it's performed early to take
|
||||
# advantage of Docker's build cache, thereby accelerating subsequent builds.
|
||||
COPY requirements.txt manager_requirements.txt ./
|
||||
RUN pip install --no-cache-dir --disable-pip-version-check \
|
||||
-r requirements.txt
|
||||
|
||||
# Install ComfyUI
|
||||
COPY . .
|
||||
|
||||
# Purely declarative: inform Docker and image users that this image is designed
|
||||
# to listen on port 8188 for the web GUI.
|
||||
EXPOSE 8188
|
||||
|
||||
# Declare persistent volumes. We assign one volume per data directory to match
|
||||
# ComfyUI’s natural file layout and to let users selectively choose which
|
||||
# directories they want to mount.
|
||||
VOLUME /comfyui/.venv
|
||||
VOLUME /comfyui/custom_nodes
|
||||
VOLUME /comfyui/input
|
||||
VOLUME /comfyui/models
|
||||
VOLUME /comfyui/output
|
||||
VOLUME /comfyui/temp
|
||||
VOLUME /comfyui/user
|
||||
VOLUME /home/comfyui
|
||||
|
||||
# Switch back to root to run the entrypoint and to install additional system
|
||||
# dependencies
|
||||
USER root
|
||||
|
||||
# Configure entrypoint
|
||||
RUN chmod +x entrypoint.sh
|
||||
ENTRYPOINT [ "./entrypoint.sh" ]
|
||||
CMD [ "python", "./main.py" ]
|
||||
|
||||
# Install additional system dependencies
|
||||
ARG APT_EXTRA_PACKAGES
|
||||
RUN apt-get install -y --no-install-recommends $APT_EXTRA_PACKAGES \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
28
README.md
28
README.md
@ -46,6 +46,12 @@ ComfyUI lets you design and execute advanced stable diffusion pipelines using a
|
||||
- Get the latest commits and completely portable.
|
||||
- Available on Windows.
|
||||
|
||||
#### [Docker Install](#running-with-docker)
|
||||
- Run ComfyUI inside an isolated Docker container
|
||||
- Most secure way to run ComfyUI and custom node packs
|
||||
- Requires Docker and Docker Compose
|
||||
- Supports NVIDIA GPUs (Not tested on other hardware.)
|
||||
|
||||
#### [Manual Install](#manual-install-windows-linux)
|
||||
Supports all operating systems and GPU types (NVIDIA, AMD, Intel, Apple Silicon, Ascend).
|
||||
|
||||
@ -350,6 +356,28 @@ For models compatible with Iluvatar Extension for PyTorch. Here's a step-by-step
|
||||
| `--enable-manager-legacy-ui` | Use the legacy manager UI instead of the new UI (requires `--enable-manager`) |
|
||||
| `--disable-manager-ui` | Disable the manager UI and endpoints while keeping background features like security checks and scheduled installation completion (requires `--enable-manager`) |
|
||||
|
||||
## Running with Docker
|
||||
|
||||
Start by installing Docker, Docker Compose, and the NVIDIA Container Toolkit on
|
||||
your host. Next, edit `compose.yaml` and update the `UID` and `GID` variables to
|
||||
match your user. Additional fields are documented in the file for further
|
||||
customization.
|
||||
|
||||
Once ready, build and run the image locally:
|
||||
|
||||
```shell
|
||||
# (Re)build the Docker image. Run this before the first start, after updating
|
||||
# ComfyUI, or after changing any build arguments in `compose.yaml`.
|
||||
docker compose build
|
||||
# Start ComfyUI. This reuses the most recently built image.
|
||||
docker compose up
|
||||
```
|
||||
|
||||
To stop and remove the container along with its volumes, run:
|
||||
|
||||
```shell
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
# Running
|
||||
|
||||
|
||||
@ -1000,20 +1000,38 @@ class Autogrow(ComfyTypeI):
|
||||
names = [f"{prefix}{i}" for i in range(max)]
|
||||
# need to create a new input based on the contents of input
|
||||
template_input = None
|
||||
for _, dict_input in input.items():
|
||||
# for now, get just the first value from dict_input
|
||||
template_required = True
|
||||
for _input_type, dict_input in input.items():
|
||||
# for now, get just the first value from dict_input; if not required, min can be ignored
|
||||
if len(dict_input) == 0:
|
||||
continue
|
||||
template_input = list(dict_input.values())[0]
|
||||
template_required = _input_type == "required"
|
||||
break
|
||||
if template_input is None:
|
||||
raise Exception("template_input could not be determined from required or optional; this should never happen.")
|
||||
new_dict = {}
|
||||
new_dict_added_to = False
|
||||
# first, add possible inputs into out_dict
|
||||
for i, name in enumerate(names):
|
||||
expected_id = finalize_prefix(curr_prefix, name)
|
||||
# required
|
||||
if i < min and template_required:
|
||||
out_dict["required"][expected_id] = template_input
|
||||
type_dict = new_dict.setdefault("required", {})
|
||||
# optional
|
||||
else:
|
||||
out_dict["optional"][expected_id] = template_input
|
||||
type_dict = new_dict.setdefault("optional", {})
|
||||
if expected_id in live_inputs:
|
||||
# required
|
||||
if i < min:
|
||||
type_dict = new_dict.setdefault("required", {})
|
||||
# optional
|
||||
else:
|
||||
type_dict = new_dict.setdefault("optional", {})
|
||||
# NOTE: prefix gets added in parse_class_inputs
|
||||
type_dict[name] = template_input
|
||||
new_dict_added_to = True
|
||||
# account for the edge case that all inputs are optional and no values are received
|
||||
if not new_dict_added_to:
|
||||
finalized_prefix = finalize_prefix(curr_prefix)
|
||||
out_dict["dynamic_paths"][finalized_prefix] = finalized_prefix
|
||||
out_dict["dynamic_paths_default_value"][finalized_prefix] = DynamicPathsDefaultValue.EMPTY_DICT
|
||||
parse_class_inputs(out_dict, live_inputs, new_dict, curr_prefix)
|
||||
|
||||
@comfytype(io_type="COMFY_DYNAMICCOMBO_V3")
|
||||
@ -1151,6 +1169,8 @@ class V3Data(TypedDict):
|
||||
'Dictionary where the keys are the hidden input ids and the values are the values of the hidden inputs.'
|
||||
dynamic_paths: dict[str, Any]
|
||||
'Dictionary where the keys are the input ids and the values dictate how to turn the inputs into a nested dictionary.'
|
||||
dynamic_paths_default_value: dict[str, Any]
|
||||
'Dictionary where the keys are the input ids and the values are a string from DynamicPathsDefaultValue for the inputs if value is None.'
|
||||
create_dynamic_tuple: bool
|
||||
'When True, the value of the dynamic input will be in the format (value, path_key).'
|
||||
|
||||
@ -1504,6 +1524,7 @@ def get_finalized_class_inputs(d: dict[str, Any], live_inputs: dict[str, Any], i
|
||||
"required": {},
|
||||
"optional": {},
|
||||
"dynamic_paths": {},
|
||||
"dynamic_paths_default_value": {},
|
||||
}
|
||||
d = d.copy()
|
||||
# ignore hidden for parsing
|
||||
@ -1513,8 +1534,12 @@ def get_finalized_class_inputs(d: dict[str, Any], live_inputs: dict[str, Any], i
|
||||
out_dict["hidden"] = hidden
|
||||
v3_data = {}
|
||||
dynamic_paths = out_dict.pop("dynamic_paths", None)
|
||||
if dynamic_paths is not None:
|
||||
if dynamic_paths is not None and len(dynamic_paths) > 0:
|
||||
v3_data["dynamic_paths"] = dynamic_paths
|
||||
# this list is used for autogrow, in the case all inputs are optional and no values are passed
|
||||
dynamic_paths_default_value = out_dict.pop("dynamic_paths_default_value", None)
|
||||
if dynamic_paths_default_value is not None and len(dynamic_paths_default_value) > 0:
|
||||
v3_data["dynamic_paths_default_value"] = dynamic_paths_default_value
|
||||
return out_dict, hidden, v3_data
|
||||
|
||||
def parse_class_inputs(out_dict: dict[str, Any], live_inputs: dict[str, Any], curr_dict: dict[str, Any], curr_prefix: list[str] | None=None) -> None:
|
||||
@ -1551,11 +1576,16 @@ def add_to_dict_v1(i: Input, d: dict):
|
||||
def add_to_dict_v3(io: Input | Output, d: dict):
|
||||
d[io.id] = (io.get_io_type(), io.as_dict())
|
||||
|
||||
class DynamicPathsDefaultValue:
|
||||
EMPTY_DICT = "empty_dict"
|
||||
|
||||
def build_nested_inputs(values: dict[str, Any], v3_data: V3Data):
|
||||
paths = v3_data.get("dynamic_paths", None)
|
||||
default_value_dict = v3_data.get("dynamic_paths_default_value", {})
|
||||
if paths is None:
|
||||
return values
|
||||
values = values.copy()
|
||||
|
||||
result = {}
|
||||
|
||||
create_tuple = v3_data.get("create_dynamic_tuple", False)
|
||||
@ -1569,6 +1599,11 @@ def build_nested_inputs(values: dict[str, Any], v3_data: V3Data):
|
||||
|
||||
if is_last:
|
||||
value = values.pop(key, None)
|
||||
if value is None:
|
||||
# see if a default value was provided for this key
|
||||
default_option = default_value_dict.get(key, None)
|
||||
if default_option == DynamicPathsDefaultValue.EMPTY_DICT:
|
||||
value = {}
|
||||
if create_tuple:
|
||||
value = (value, key)
|
||||
current[p] = value
|
||||
|
||||
61
comfy_api_nodes/apis/bria.py
Normal file
61
comfy_api_nodes/apis/bria.py
Normal file
@ -0,0 +1,61 @@
|
||||
from typing import TypedDict
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class InputModerationSettings(TypedDict):
|
||||
prompt_content_moderation: bool
|
||||
visual_input_moderation: bool
|
||||
visual_output_moderation: bool
|
||||
|
||||
|
||||
class BriaEditImageRequest(BaseModel):
|
||||
instruction: str | None = Field(...)
|
||||
structured_instruction: str | None = Field(
|
||||
...,
|
||||
description="Use this instead of instruction for precise, programmatic control.",
|
||||
)
|
||||
images: list[str] = Field(
|
||||
...,
|
||||
description="Required. Publicly available URL or Base64-encoded. Must contain exactly one item.",
|
||||
)
|
||||
mask: str | None = Field(
|
||||
None,
|
||||
description="Mask image (black and white). Black areas will be preserved, white areas will be edited. "
|
||||
"If omitted, the edit applies to the entire image. "
|
||||
"The input image and the the input mask must be of the same size.",
|
||||
)
|
||||
negative_prompt: str | None = Field(None)
|
||||
guidance_scale: float = Field(...)
|
||||
model_version: str = Field(...)
|
||||
steps_num: int = Field(...)
|
||||
seed: int = Field(...)
|
||||
ip_signal: bool = Field(
|
||||
False,
|
||||
description="If true, returns a warning for potential IP content in the instruction.",
|
||||
)
|
||||
prompt_content_moderation: bool = Field(
|
||||
False, description="If true, returns 422 on instruction moderation failure."
|
||||
)
|
||||
visual_input_content_moderation: bool = Field(
|
||||
False, description="If true, returns 422 on images or mask moderation failure."
|
||||
)
|
||||
visual_output_content_moderation: bool = Field(
|
||||
False, description="If true, returns 422 on visual output moderation failure."
|
||||
)
|
||||
|
||||
|
||||
class BriaStatusResponse(BaseModel):
|
||||
request_id: str = Field(...)
|
||||
status_url: str = Field(...)
|
||||
warning: str | None = Field(None)
|
||||
|
||||
|
||||
class BriaResult(BaseModel):
|
||||
structured_prompt: str = Field(...)
|
||||
image_url: str = Field(...)
|
||||
|
||||
|
||||
class BriaResponse(BaseModel):
|
||||
status: str = Field(...)
|
||||
result: BriaResult | None = Field(None)
|
||||
198
comfy_api_nodes/nodes_bria.py
Normal file
198
comfy_api_nodes/nodes_bria.py
Normal file
@ -0,0 +1,198 @@
|
||||
from typing_extensions import override
|
||||
|
||||
from comfy_api.latest import IO, ComfyExtension, Input
|
||||
from comfy_api_nodes.apis.bria import (
|
||||
BriaEditImageRequest,
|
||||
BriaResponse,
|
||||
BriaStatusResponse,
|
||||
InputModerationSettings,
|
||||
)
|
||||
from comfy_api_nodes.util import (
|
||||
ApiEndpoint,
|
||||
convert_mask_to_image,
|
||||
download_url_to_image_tensor,
|
||||
get_number_of_images,
|
||||
poll_op,
|
||||
sync_op,
|
||||
upload_images_to_comfyapi,
|
||||
)
|
||||
|
||||
|
||||
class BriaImageEditNode(IO.ComfyNode):
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls):
|
||||
return IO.Schema(
|
||||
node_id="BriaImageEditNode",
|
||||
display_name="Bria Image Edit",
|
||||
category="api node/image/Bria",
|
||||
description="Edit images using Bria latest model",
|
||||
inputs=[
|
||||
IO.Combo.Input("model", options=["FIBO"]),
|
||||
IO.Image.Input("image"),
|
||||
IO.String.Input(
|
||||
"prompt",
|
||||
multiline=True,
|
||||
default="",
|
||||
tooltip="Instruction to edit image",
|
||||
),
|
||||
IO.String.Input("negative_prompt", multiline=True, default=""),
|
||||
IO.String.Input(
|
||||
"structured_prompt",
|
||||
multiline=True,
|
||||
default="",
|
||||
tooltip="A string containing the structured edit prompt in JSON format. "
|
||||
"Use this instead of usual prompt for precise, programmatic control.",
|
||||
),
|
||||
IO.Int.Input(
|
||||
"seed",
|
||||
default=1,
|
||||
min=1,
|
||||
max=2147483647,
|
||||
step=1,
|
||||
display_mode=IO.NumberDisplay.number,
|
||||
control_after_generate=True,
|
||||
),
|
||||
IO.Float.Input(
|
||||
"guidance_scale",
|
||||
default=3,
|
||||
min=3,
|
||||
max=5,
|
||||
step=0.01,
|
||||
display_mode=IO.NumberDisplay.number,
|
||||
tooltip="Higher value makes the image follow the prompt more closely.",
|
||||
),
|
||||
IO.Int.Input(
|
||||
"steps",
|
||||
default=50,
|
||||
min=20,
|
||||
max=50,
|
||||
step=1,
|
||||
display_mode=IO.NumberDisplay.number,
|
||||
),
|
||||
IO.DynamicCombo.Input(
|
||||
"moderation",
|
||||
options=[
|
||||
IO.DynamicCombo.Option(
|
||||
"true",
|
||||
[
|
||||
IO.Boolean.Input(
|
||||
"prompt_content_moderation", default=False
|
||||
),
|
||||
IO.Boolean.Input(
|
||||
"visual_input_moderation", default=False
|
||||
),
|
||||
IO.Boolean.Input(
|
||||
"visual_output_moderation", default=True
|
||||
),
|
||||
],
|
||||
),
|
||||
IO.DynamicCombo.Option("false", []),
|
||||
],
|
||||
tooltip="Moderation settings",
|
||||
),
|
||||
IO.Mask.Input(
|
||||
"mask",
|
||||
tooltip="If omitted, the edit applies to the entire image.",
|
||||
optional=True,
|
||||
),
|
||||
],
|
||||
outputs=[
|
||||
IO.Image.Output(),
|
||||
IO.String.Output(display_name="structured_prompt"),
|
||||
],
|
||||
hidden=[
|
||||
IO.Hidden.auth_token_comfy_org,
|
||||
IO.Hidden.api_key_comfy_org,
|
||||
IO.Hidden.unique_id,
|
||||
],
|
||||
is_api_node=True,
|
||||
price_badge=IO.PriceBadge(
|
||||
expr="""{"type":"usd","usd":0.04}""",
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def execute(
|
||||
cls,
|
||||
model: str,
|
||||
image: Input.Image,
|
||||
prompt: str,
|
||||
negative_prompt: str,
|
||||
structured_prompt: str,
|
||||
seed: int,
|
||||
guidance_scale: float,
|
||||
steps: int,
|
||||
moderation: InputModerationSettings,
|
||||
mask: Input.Image | None = None,
|
||||
) -> IO.NodeOutput:
|
||||
if not prompt and not structured_prompt:
|
||||
raise ValueError(
|
||||
"One of prompt or structured_prompt is required to be non-empty."
|
||||
)
|
||||
if get_number_of_images(image) != 1:
|
||||
raise ValueError("Exactly one input image is required.")
|
||||
mask_url = None
|
||||
if mask is not None:
|
||||
mask_url = (
|
||||
await upload_images_to_comfyapi(
|
||||
cls,
|
||||
convert_mask_to_image(mask),
|
||||
max_images=1,
|
||||
mime_type="image/png",
|
||||
wait_label="Uploading mask",
|
||||
)
|
||||
)[0]
|
||||
response = await sync_op(
|
||||
cls,
|
||||
ApiEndpoint(path="proxy/bria/v2/image/edit", method="POST"),
|
||||
data=BriaEditImageRequest(
|
||||
instruction=prompt if prompt else None,
|
||||
structured_instruction=structured_prompt if structured_prompt else None,
|
||||
images=await upload_images_to_comfyapi(
|
||||
cls,
|
||||
image,
|
||||
max_images=1,
|
||||
mime_type="image/png",
|
||||
wait_label="Uploading image",
|
||||
),
|
||||
mask=mask_url,
|
||||
negative_prompt=negative_prompt if negative_prompt else None,
|
||||
guidance_scale=guidance_scale,
|
||||
seed=seed,
|
||||
model_version=model,
|
||||
steps_num=steps,
|
||||
prompt_content_moderation=moderation.get(
|
||||
"prompt_content_moderation", False
|
||||
),
|
||||
visual_input_content_moderation=moderation.get(
|
||||
"visual_input_moderation", False
|
||||
),
|
||||
visual_output_content_moderation=moderation.get(
|
||||
"visual_output_moderation", False
|
||||
),
|
||||
),
|
||||
response_model=BriaStatusResponse,
|
||||
)
|
||||
response = await poll_op(
|
||||
cls,
|
||||
ApiEndpoint(path=f"/proxy/bria/v2/status/{response.request_id}"),
|
||||
status_extractor=lambda r: r.status,
|
||||
response_model=BriaResponse,
|
||||
)
|
||||
return IO.NodeOutput(
|
||||
await download_url_to_image_tensor(response.result.image_url),
|
||||
response.result.structured_prompt,
|
||||
)
|
||||
|
||||
|
||||
class BriaExtension(ComfyExtension):
|
||||
@override
|
||||
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
|
||||
return [
|
||||
BriaImageEditNode,
|
||||
]
|
||||
|
||||
|
||||
async def comfy_entrypoint() -> BriaExtension:
|
||||
return BriaExtension()
|
||||
@ -11,6 +11,7 @@ from .conversions import (
|
||||
audio_input_to_mp3,
|
||||
audio_to_base64_string,
|
||||
bytesio_to_image_tensor,
|
||||
convert_mask_to_image,
|
||||
downscale_image_tensor,
|
||||
image_tensor_pair_to_batch,
|
||||
pil_to_bytesio,
|
||||
@ -72,6 +73,7 @@ __all__ = [
|
||||
"audio_input_to_mp3",
|
||||
"audio_to_base64_string",
|
||||
"bytesio_to_image_tensor",
|
||||
"convert_mask_to_image",
|
||||
"downscale_image_tensor",
|
||||
"image_tensor_pair_to_batch",
|
||||
"pil_to_bytesio",
|
||||
|
||||
@ -451,6 +451,12 @@ def resize_mask_to_image(
|
||||
return mask
|
||||
|
||||
|
||||
def convert_mask_to_image(mask: Input.Image) -> torch.Tensor:
|
||||
"""Make mask have the expected amount of dims (4) and channels (3) to be recognized as an image."""
|
||||
mask = mask.unsqueeze(-1)
|
||||
return torch.cat([mask] * 3, dim=-1)
|
||||
|
||||
|
||||
def text_filepath_to_base64_string(filepath: str) -> str:
|
||||
"""Converts a text file to a base64 string."""
|
||||
with open(filepath, "rb") as f:
|
||||
|
||||
46
compose.yaml
Normal file
46
compose.yaml
Normal file
@ -0,0 +1,46 @@
|
||||
# Docker Compose file to run ComfyUI locally using Docker.
|
||||
|
||||
services:
|
||||
comfyui:
|
||||
container_name: comfyui
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
# Declare additional system dependencies for custom nodes
|
||||
APT_EXTRA_PACKAGES:
|
||||
|
||||
ports:
|
||||
- 8188:8188
|
||||
|
||||
# Optional: enable GPU access for hardware acceleration.
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- capabilities: [gpu]
|
||||
volumes:
|
||||
- ./custom_nodes:/comfyui/custom_nodes
|
||||
- ./models:/comfyui/models
|
||||
|
||||
# (Optional) Mount host ComfyUI data directories
|
||||
#
|
||||
#- ./input:/comfyui/input
|
||||
#- ./output:/comfyui/output
|
||||
#- ./temp:/comfyui/temp
|
||||
#- ./user:/comfyui/user
|
||||
|
||||
environment:
|
||||
# Overwrite the container user's UID and GID to match the host's. This
|
||||
# allows files created by ComfyUI to be mounted on the host without
|
||||
# permission issues.
|
||||
UID: 1000
|
||||
GID: 1000
|
||||
# Declare additional Python packages to install. Useful when a custom node
|
||||
# pack does not properly specify all its dependencies or relies on
|
||||
# optional dependencies.
|
||||
PIP_EXTRA_PACKAGES:
|
||||
|
||||
# Optional: Override the default command. In this case, configure ComfyUI to
|
||||
# listen on all network interfaces (which is required when not using
|
||||
# `network_mode=host`.)
|
||||
command: python ./main.py --listen 0.0.0.0
|
||||
62
entrypoint.sh
Executable file
62
entrypoint.sh
Executable file
@ -0,0 +1,62 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Entrypoint script for the ComfyUI Docker image.
|
||||
|
||||
set -e
|
||||
|
||||
user="comfyui"
|
||||
user_group="$user"
|
||||
|
||||
# Allow users to specify a UID and GID matching their own, so files created
|
||||
# inside the container retain the same numeric ownership when mounted on the
|
||||
# host.
|
||||
if [ -n "$UID" ] && [ -n "$GID" ]; then
|
||||
echo "[entrypoint] Setting user UID and GID..."
|
||||
usermod -u "$UID" "$user" > /dev/null
|
||||
groupmod -g "$GID" "$user_group"
|
||||
else
|
||||
echo "[entrypoint] Missing UID or GID environment variables; keeping default values."
|
||||
fi
|
||||
|
||||
# Changing a user's UID and GID revokes that user's access to files owned by the
|
||||
# original UID/GID. To preserve access to runtime data, the ownership of those
|
||||
# directories must be updated recursively so that their numeric owner matches
|
||||
# the user's new UID and GID.
|
||||
echo "[entrypoint] Changing directory ownership..."
|
||||
chown -R "$user:$user_group" \
|
||||
/comfyui \
|
||||
/home/comfyui
|
||||
|
||||
# To use CUDA and other NVIDIA features, regular users must belong to the group
|
||||
# that owns the /dev/nvidia* device files -- typically the video group.
|
||||
#
|
||||
# Known issue: Because these device files are mounted from the host system,
|
||||
# there's no guarantee that the device's group ID will match the intended group
|
||||
# inside the container. For example, the video group might be mapped to GID 27
|
||||
# on the host, which corresponds to the sudo group in the python:3.12 image.
|
||||
# This shouldn't cause major problems, and given the lack of a universal
|
||||
# standard for system GIDs, there isn't much we can realistically change to
|
||||
# address this issue.
|
||||
echo "[entrypoint] Adding user to GPU device groups..."
|
||||
for dev in /dev/nvidia*; do
|
||||
group=$(ls -ld "$dev" | awk '{print $4}')
|
||||
usermod -aG "$group" "$user"
|
||||
done
|
||||
|
||||
# Install or update the Python dependencies defined by ComfyUI (or any installed
|
||||
# custom node) and also install any user-defined dependencies specified in
|
||||
# PIP_EXTRA_PACKAGES.
|
||||
echo "[entrypoint] Updating Python dependencies..."
|
||||
su -c "
|
||||
pip install \\
|
||||
--no-cache-dir \\
|
||||
--disable-pip-version-check \\
|
||||
-r requirements.txt \\
|
||||
$(find custom_nodes -mindepth 2 -maxdepth 2 -type f -name requirements.txt -printf "-r '%p' ") \\
|
||||
$PIP_EXTRA_PACKAGES
|
||||
" comfyui \
|
||||
|| echo "[entrypoint] Failed to install dependencies, starting anyway" >&2
|
||||
|
||||
# Run command as comfyui
|
||||
echo "[entrypoint] Running command"
|
||||
exec su -c "$*" comfyui
|
||||
@ -1,5 +1,5 @@
|
||||
comfyui-frontend-package==1.36.14
|
||||
comfyui-workflow-templates==0.8.11
|
||||
comfyui-workflow-templates==0.8.14
|
||||
comfyui-embedded-docs==0.4.0
|
||||
torch
|
||||
torchsde
|
||||
|
||||
Loading…
Reference in New Issue
Block a user