comfy-diffusion 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- comfy_diffusion/__init__.py +30 -0
- comfy_diffusion/_runtime.py +26 -0
- comfy_diffusion/audio.py +168 -0
- comfy_diffusion/conditioning.py +25 -0
- comfy_diffusion/lora.py +34 -0
- comfy_diffusion/models.py +245 -0
- comfy_diffusion/runtime.py +86 -0
- comfy_diffusion/sampling.py +383 -0
- comfy_diffusion/vae.py +390 -0
- comfy_diffusion-0.1.0.dist-info/METADATA +183 -0
- comfy_diffusion-0.1.0.dist-info/RECORD +13 -0
- comfy_diffusion-0.1.0.dist-info/WHEEL +5 -0
- comfy_diffusion-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Public package entrypoint for comfy_diffusion."""
|
|
2
|
+
|
|
3
|
+
from ._runtime import ensure_comfyui_on_path
|
|
4
|
+
from .lora import apply_lora
|
|
5
|
+
from .runtime import check_runtime
|
|
6
|
+
from .vae import (
|
|
7
|
+
vae_decode,
|
|
8
|
+
vae_decode_batch,
|
|
9
|
+
vae_decode_batch_tiled,
|
|
10
|
+
vae_decode_tiled,
|
|
11
|
+
vae_encode,
|
|
12
|
+
vae_encode_batch,
|
|
13
|
+
vae_encode_batch_tiled,
|
|
14
|
+
vae_encode_tiled,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
ensure_comfyui_on_path()
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"check_runtime",
|
|
21
|
+
"vae_decode",
|
|
22
|
+
"vae_decode_batch",
|
|
23
|
+
"vae_decode_batch_tiled",
|
|
24
|
+
"vae_decode_tiled",
|
|
25
|
+
"vae_encode",
|
|
26
|
+
"vae_encode_batch",
|
|
27
|
+
"vae_encode_batch_tiled",
|
|
28
|
+
"vae_encode_tiled",
|
|
29
|
+
"apply_lora",
|
|
30
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Internal runtime bootstrap for comfy_diffusion.
|
|
2
|
+
|
|
3
|
+
Path insertion is intentionally lightweight and import-safe: this module must not
|
|
4
|
+
import torch or comfy internals just to make ComfyUI discoverable.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _comfyui_root() -> Path:
|
|
14
|
+
"""Return the absolute path to the vendored ComfyUI directory."""
|
|
15
|
+
return Path(__file__).resolve().parents[1] / "vendor" / "ComfyUI"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def ensure_comfyui_on_path() -> Path:
|
|
19
|
+
"""Ensure vendored ComfyUI is importable and return the inserted path."""
|
|
20
|
+
comfyui_root = _comfyui_root()
|
|
21
|
+
comfyui_root_str = str(comfyui_root)
|
|
22
|
+
|
|
23
|
+
if comfyui_root_str not in sys.path:
|
|
24
|
+
sys.path.insert(0, comfyui_root_str)
|
|
25
|
+
|
|
26
|
+
return comfyui_root
|
comfy_diffusion/audio.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Audio helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Protocol, cast
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _LtxvAudioVaeEncoder(Protocol):
|
|
9
|
+
sample_rate: int
|
|
10
|
+
|
|
11
|
+
def encode(self, audio: Any) -> Any: ...
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _LtxvAudioVaeDecoder(Protocol):
|
|
15
|
+
output_sample_rate: int
|
|
16
|
+
|
|
17
|
+
def decode(self, latent: Any) -> Any: ...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class _LtxvAudioVae(Protocol):
|
|
21
|
+
sample_rate: int
|
|
22
|
+
latent_channels: int
|
|
23
|
+
latent_frequency_bins: int
|
|
24
|
+
|
|
25
|
+
def num_of_latents_from_frames(self, frames_number: int, frame_rate: int) -> int: ...
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _AceStep15Clip(Protocol):
|
|
29
|
+
def tokenize(
|
|
30
|
+
self,
|
|
31
|
+
tags: str,
|
|
32
|
+
*,
|
|
33
|
+
lyrics: str,
|
|
34
|
+
bpm: int,
|
|
35
|
+
duration: float,
|
|
36
|
+
timesignature: int,
|
|
37
|
+
language: str,
|
|
38
|
+
keyscale: str,
|
|
39
|
+
seed: int,
|
|
40
|
+
generate_audio_codes: bool,
|
|
41
|
+
cfg_scale: float,
|
|
42
|
+
temperature: float,
|
|
43
|
+
top_p: float,
|
|
44
|
+
top_k: int,
|
|
45
|
+
min_p: float,
|
|
46
|
+
) -> Any: ...
|
|
47
|
+
|
|
48
|
+
def encode_from_tokens_scheduled(self, tokens: Any) -> Any: ...
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _get_ltxv_empty_latent_audio_type() -> Any:
|
|
52
|
+
"""Resolve ComfyUI LTXVEmptyLatentAudio node at call time."""
|
|
53
|
+
from ._runtime import ensure_comfyui_on_path
|
|
54
|
+
|
|
55
|
+
ensure_comfyui_on_path()
|
|
56
|
+
from comfy_extras.nodes_lt_audio import LTXVEmptyLatentAudio
|
|
57
|
+
|
|
58
|
+
return LTXVEmptyLatentAudio
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _get_ace_step_15_latent_audio_dependencies() -> tuple[Any, Any]:
|
|
62
|
+
"""Resolve torch and ComfyUI model management at call time."""
|
|
63
|
+
from ._runtime import ensure_comfyui_on_path
|
|
64
|
+
|
|
65
|
+
ensure_comfyui_on_path()
|
|
66
|
+
import comfy.model_management
|
|
67
|
+
import torch
|
|
68
|
+
|
|
69
|
+
return torch, comfy.model_management
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _unwrap_node_output(output: Any) -> Any:
|
|
73
|
+
"""Return first output for ComfyUI V3 nodes and tuple-style APIs."""
|
|
74
|
+
if hasattr(output, "result"):
|
|
75
|
+
return output.result[0]
|
|
76
|
+
if isinstance(output, tuple):
|
|
77
|
+
return output[0]
|
|
78
|
+
return output
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def ltxv_audio_vae_encode(vae: _LtxvAudioVaeEncoder, audio: Any) -> dict[str, Any]:
|
|
82
|
+
"""Encode raw audio with an LTXV audio VAE."""
|
|
83
|
+
audio_latents = vae.encode(audio)
|
|
84
|
+
return {"samples": audio_latents, "sample_rate": int(vae.sample_rate), "type": "audio"}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def ltxv_audio_vae_decode(vae: _LtxvAudioVaeDecoder, latent: Any) -> dict[str, Any]:
|
|
88
|
+
"""Decode latent audio with an LTXV audio VAE."""
|
|
89
|
+
latent_tensor = latent["samples"] if isinstance(latent, dict) else latent
|
|
90
|
+
if getattr(latent_tensor, "is_nested", False):
|
|
91
|
+
latent_tensor = latent_tensor.unbind()[-1]
|
|
92
|
+
audio = vae.decode(latent_tensor).to(latent_tensor.device)
|
|
93
|
+
return {"waveform": audio, "sample_rate": int(vae.output_sample_rate)}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def ltxv_empty_latent_audio(
|
|
97
|
+
audio_vae: _LtxvAudioVae,
|
|
98
|
+
frames_number: int,
|
|
99
|
+
frame_rate: int = 25,
|
|
100
|
+
batch_size: int = 1,
|
|
101
|
+
) -> dict[str, Any]:
|
|
102
|
+
"""Create empty LTXV audio latents compatible with ComfyUI's audio pipeline."""
|
|
103
|
+
ltxv_empty_latent_audio_type = _get_ltxv_empty_latent_audio_type()
|
|
104
|
+
return cast(
|
|
105
|
+
dict[str, Any],
|
|
106
|
+
_unwrap_node_output(
|
|
107
|
+
ltxv_empty_latent_audio_type.execute(
|
|
108
|
+
frames_number=frames_number,
|
|
109
|
+
frame_rate=frame_rate,
|
|
110
|
+
batch_size=batch_size,
|
|
111
|
+
audio_vae=audio_vae,
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def encode_ace_step_15_audio(
|
|
118
|
+
clip: _AceStep15Clip,
|
|
119
|
+
tags: str,
|
|
120
|
+
lyrics: str = "",
|
|
121
|
+
seed: int = 0,
|
|
122
|
+
bpm: int = 120,
|
|
123
|
+
duration: float = 120.0,
|
|
124
|
+
timesignature: str = "4",
|
|
125
|
+
language: str = "en",
|
|
126
|
+
keyscale: str = "C major",
|
|
127
|
+
generate_audio_codes: bool = True,
|
|
128
|
+
cfg_scale: float = 2.0,
|
|
129
|
+
temperature: float = 0.85,
|
|
130
|
+
top_p: float = 0.9,
|
|
131
|
+
top_k: int = 0,
|
|
132
|
+
min_p: float = 0.0,
|
|
133
|
+
) -> Any:
|
|
134
|
+
"""Encode ACE Step 1.5 text/audio metadata conditioning."""
|
|
135
|
+
tokens = clip.tokenize(
|
|
136
|
+
tags,
|
|
137
|
+
lyrics=lyrics,
|
|
138
|
+
bpm=bpm,
|
|
139
|
+
duration=duration,
|
|
140
|
+
timesignature=int(timesignature),
|
|
141
|
+
language=language,
|
|
142
|
+
keyscale=keyscale,
|
|
143
|
+
seed=seed,
|
|
144
|
+
generate_audio_codes=generate_audio_codes,
|
|
145
|
+
cfg_scale=cfg_scale,
|
|
146
|
+
temperature=temperature,
|
|
147
|
+
top_p=top_p,
|
|
148
|
+
top_k=top_k,
|
|
149
|
+
min_p=min_p,
|
|
150
|
+
)
|
|
151
|
+
return clip.encode_from_tokens_scheduled(tokens)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def empty_ace_step_15_latent_audio(seconds: float, batch_size: int = 1) -> dict[str, Any]:
|
|
155
|
+
"""Create empty ACE Step 1.5 latents used as sampler noise input."""
|
|
156
|
+
torch, model_management = _get_ace_step_15_latent_audio_dependencies()
|
|
157
|
+
length = round(seconds * 48000 / 1920)
|
|
158
|
+
latent = torch.zeros([batch_size, 64, length], device=model_management.intermediate_device())
|
|
159
|
+
return {"samples": latent, "type": "audio"}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
__all__ = [
|
|
163
|
+
"ltxv_audio_vae_encode",
|
|
164
|
+
"ltxv_audio_vae_decode",
|
|
165
|
+
"ltxv_empty_latent_audio",
|
|
166
|
+
"encode_ace_step_15_audio",
|
|
167
|
+
"empty_ace_step_15_latent_audio",
|
|
168
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Prompt conditioning helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Protocol
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _ClipTextEncoder(Protocol):
|
|
9
|
+
def tokenize(self, text: str) -> Any: ...
|
|
10
|
+
|
|
11
|
+
def encode_from_tokens_scheduled(self, tokens: Any) -> Any: ...
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def encode_prompt(clip: _ClipTextEncoder, text: str) -> Any:
|
|
15
|
+
"""Encode prompt text with a ComfyUI-compatible CLIP object.
|
|
16
|
+
|
|
17
|
+
Positive and negative prompts use the same encoding path; prompt
|
|
18
|
+
semantics are owned by the caller.
|
|
19
|
+
"""
|
|
20
|
+
normalized_text = " " if text == "" else text
|
|
21
|
+
tokens = clip.tokenize(normalized_text)
|
|
22
|
+
return clip.encode_from_tokens_scheduled(tokens)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
__all__ = ["encode_prompt"]
|
comfy_diffusion/lora.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""LoRA application helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, cast
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def apply_lora(
|
|
10
|
+
model: Any,
|
|
11
|
+
clip: Any,
|
|
12
|
+
path: str | Path,
|
|
13
|
+
strength_model: float,
|
|
14
|
+
strength_clip: float,
|
|
15
|
+
) -> tuple[Any, Any]:
|
|
16
|
+
"""Apply a LoRA file to a model/CLIP pair and return patched copies.
|
|
17
|
+
|
|
18
|
+
The returned pair can be passed back into ``apply_lora`` to stack
|
|
19
|
+
multiple LoRAs by chaining calls.
|
|
20
|
+
"""
|
|
21
|
+
from ._runtime import ensure_comfyui_on_path
|
|
22
|
+
|
|
23
|
+
ensure_comfyui_on_path()
|
|
24
|
+
|
|
25
|
+
import comfy.sd
|
|
26
|
+
import comfy.utils
|
|
27
|
+
|
|
28
|
+
lora_path = str(Path(path))
|
|
29
|
+
lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
|
|
30
|
+
patched = comfy.sd.load_lora_for_models(model, clip, lora, strength_model, strength_clip)
|
|
31
|
+
return cast(tuple[Any, Any], patched)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
__all__ = ["apply_lora"]
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""Model management public API.
|
|
2
|
+
|
|
3
|
+
This module must stay import-safe in CPU-only environments. It intentionally avoids
|
|
4
|
+
importing ComfyUI loaders at module import time.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from ._runtime import ensure_comfyui_on_path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class CheckpointResult:
|
|
18
|
+
"""Container for objects produced by a ComfyUI checkpoint load."""
|
|
19
|
+
|
|
20
|
+
model: Any
|
|
21
|
+
clip: Any | None
|
|
22
|
+
vae: Any | None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ModelManager:
|
|
26
|
+
"""Entry point for model-loading operations.
|
|
27
|
+
|
|
28
|
+
The implementation is intentionally deferred to later user stories; this class
|
|
29
|
+
exists now to provide a stable, side-effect-free import surface.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, models_dir: str | Path) -> None:
|
|
33
|
+
"""Store and validate the models directory used by future load operations."""
|
|
34
|
+
path = Path(models_dir)
|
|
35
|
+
|
|
36
|
+
if not path.exists():
|
|
37
|
+
raise ValueError(f"models_dir does not exist: {path}")
|
|
38
|
+
if not path.is_dir():
|
|
39
|
+
raise ValueError(f"models_dir is not a directory: {path}")
|
|
40
|
+
|
|
41
|
+
self.models_dir = path
|
|
42
|
+
|
|
43
|
+
ensure_comfyui_on_path()
|
|
44
|
+
import folder_paths
|
|
45
|
+
|
|
46
|
+
folder_paths.add_model_folder_path(
|
|
47
|
+
"checkpoints", str(self.models_dir / "checkpoints"), is_default=True
|
|
48
|
+
)
|
|
49
|
+
folder_paths.add_model_folder_path(
|
|
50
|
+
"embeddings", str(self.models_dir / "embeddings"), is_default=True
|
|
51
|
+
)
|
|
52
|
+
folder_paths.add_model_folder_path(
|
|
53
|
+
"diffusion_models", str(self.models_dir / "unet"), is_default=True
|
|
54
|
+
)
|
|
55
|
+
folder_paths.add_model_folder_path(
|
|
56
|
+
"diffusion_models", str(self.models_dir / "diffusion_models"), is_default=False
|
|
57
|
+
)
|
|
58
|
+
folder_paths.add_model_folder_path(
|
|
59
|
+
"text_encoders", str(self.models_dir / "text_encoders"), is_default=True
|
|
60
|
+
)
|
|
61
|
+
folder_paths.add_model_folder_path(
|
|
62
|
+
"text_encoders", str(self.models_dir / "clip"), is_default=False
|
|
63
|
+
)
|
|
64
|
+
folder_paths.add_model_folder_path(
|
|
65
|
+
"vae", str(self.models_dir / "vae"), is_default=True
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def load_checkpoint(self, filename: str) -> CheckpointResult:
|
|
69
|
+
"""Load a checkpoint by filename from the configured checkpoints directory."""
|
|
70
|
+
ensure_comfyui_on_path()
|
|
71
|
+
|
|
72
|
+
requested_path = (self.models_dir / "checkpoints" / filename).resolve()
|
|
73
|
+
if not requested_path.is_file():
|
|
74
|
+
raise FileNotFoundError(f"checkpoint file not found: {requested_path}")
|
|
75
|
+
|
|
76
|
+
import folder_paths
|
|
77
|
+
from comfy import sd as comfy_sd
|
|
78
|
+
|
|
79
|
+
checkpoint_path = folder_paths.get_full_path_or_raise("checkpoints", filename)
|
|
80
|
+
loaded = comfy_sd.load_checkpoint_guess_config(
|
|
81
|
+
checkpoint_path,
|
|
82
|
+
output_vae=True,
|
|
83
|
+
output_clip=True,
|
|
84
|
+
embedding_directory=folder_paths.get_folder_paths("embeddings"),
|
|
85
|
+
)
|
|
86
|
+
model, clip, vae = loaded[:3]
|
|
87
|
+
return CheckpointResult(model=model, clip=clip, vae=vae)
|
|
88
|
+
|
|
89
|
+
def load_vae(self, path: str | Path) -> Any:
|
|
90
|
+
"""Load a standalone VAE from a path or filename.
|
|
91
|
+
|
|
92
|
+
If ``path`` is an absolute path to an existing file, that file is loaded.
|
|
93
|
+
Otherwise ``path`` is treated as a filename under the ``vae`` folder.
|
|
94
|
+
"""
|
|
95
|
+
ensure_comfyui_on_path()
|
|
96
|
+
|
|
97
|
+
import folder_paths
|
|
98
|
+
from comfy import sd as comfy_sd
|
|
99
|
+
from comfy import utils as comfy_utils
|
|
100
|
+
|
|
101
|
+
p = Path(path)
|
|
102
|
+
if p.is_absolute() and p.is_file():
|
|
103
|
+
vae_path = str(p.resolve())
|
|
104
|
+
elif p.is_absolute():
|
|
105
|
+
raise FileNotFoundError(f"vae file not found: {p}")
|
|
106
|
+
else:
|
|
107
|
+
name = path if isinstance(path, str) else p.name
|
|
108
|
+
vae_path = folder_paths.get_full_path_or_raise("vae", name)
|
|
109
|
+
|
|
110
|
+
state_dict, metadata = comfy_utils.load_torch_file(
|
|
111
|
+
vae_path, return_metadata=True
|
|
112
|
+
)
|
|
113
|
+
vae = comfy_sd.VAE(sd=state_dict, metadata=metadata)
|
|
114
|
+
vae.throw_exception_if_invalid()
|
|
115
|
+
return vae
|
|
116
|
+
|
|
117
|
+
def load_clip(
|
|
118
|
+
self,
|
|
119
|
+
path: str | Path,
|
|
120
|
+
*,
|
|
121
|
+
clip_type: str = "stable_diffusion",
|
|
122
|
+
) -> Any:
|
|
123
|
+
"""Load a standalone text encoder (CLIP) from a path or filename.
|
|
124
|
+
|
|
125
|
+
If ``path`` is an absolute path to an existing file, that file is loaded.
|
|
126
|
+
Otherwise ``path`` is treated as a filename under ``text_encoders`` / ``clip``.
|
|
127
|
+
|
|
128
|
+
``clip_type`` selects the encoder architecture (e.g. ``"wan"`` for Wan / UMT5-XXL,
|
|
129
|
+
``"stable_diffusion"``, ``"sd3"``, ``"flux"``). Must match the model weights.
|
|
130
|
+
"""
|
|
131
|
+
ensure_comfyui_on_path()
|
|
132
|
+
|
|
133
|
+
import folder_paths
|
|
134
|
+
from comfy import sd as comfy_sd
|
|
135
|
+
|
|
136
|
+
p = Path(path)
|
|
137
|
+
if p.is_absolute() and p.is_file():
|
|
138
|
+
full_path = str(p.resolve())
|
|
139
|
+
elif p.is_absolute():
|
|
140
|
+
raise FileNotFoundError(f"clip file not found: {p}")
|
|
141
|
+
else:
|
|
142
|
+
name = path if isinstance(path, str) else p.name
|
|
143
|
+
full_path = folder_paths.get_full_path_or_raise("text_encoders", name)
|
|
144
|
+
|
|
145
|
+
clip_type_enum = getattr(
|
|
146
|
+
comfy_sd.CLIPType,
|
|
147
|
+
clip_type.upper(),
|
|
148
|
+
comfy_sd.CLIPType.STABLE_DIFFUSION,
|
|
149
|
+
)
|
|
150
|
+
return comfy_sd.load_clip(
|
|
151
|
+
ckpt_paths=[full_path],
|
|
152
|
+
embedding_directory=folder_paths.get_folder_paths("embeddings"),
|
|
153
|
+
clip_type=clip_type_enum,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def load_unet(self, path: str | Path) -> Any:
|
|
157
|
+
"""Load a standalone diffusion model (UNet) from a path or filename.
|
|
158
|
+
|
|
159
|
+
If ``path`` is an absolute path to an existing file, that file is loaded.
|
|
160
|
+
Otherwise ``path`` is treated as a filename and resolved under the
|
|
161
|
+
``diffusion_models`` / ``unet`` folders (see ComfyUI folder layout).
|
|
162
|
+
"""
|
|
163
|
+
ensure_comfyui_on_path()
|
|
164
|
+
|
|
165
|
+
import folder_paths
|
|
166
|
+
from comfy import sd as comfy_sd
|
|
167
|
+
|
|
168
|
+
p = Path(path)
|
|
169
|
+
if p.is_absolute() and p.is_file():
|
|
170
|
+
full_path = str(p.resolve())
|
|
171
|
+
elif p.is_absolute():
|
|
172
|
+
raise FileNotFoundError(f"unet file not found: {p}")
|
|
173
|
+
else:
|
|
174
|
+
name = path if isinstance(path, str) else p.name
|
|
175
|
+
full_path = folder_paths.get_full_path_or_raise("diffusion_models", name)
|
|
176
|
+
|
|
177
|
+
return comfy_sd.load_diffusion_model(full_path)
|
|
178
|
+
|
|
179
|
+
def load_ltxv_audio_vae(self, path: str | Path) -> object:
|
|
180
|
+
"""Load an LTXV audio VAE checkpoint from a path or filename.
|
|
181
|
+
|
|
182
|
+
If ``path`` is an absolute path to an existing file, that file is loaded.
|
|
183
|
+
Otherwise ``path`` is treated as a filename under the ``checkpoints`` folder.
|
|
184
|
+
"""
|
|
185
|
+
ensure_comfyui_on_path()
|
|
186
|
+
|
|
187
|
+
import folder_paths
|
|
188
|
+
from comfy import utils as comfy_utils
|
|
189
|
+
from comfy.ldm.lightricks.vae.audio_vae import AudioVAE
|
|
190
|
+
|
|
191
|
+
p = Path(path)
|
|
192
|
+
if p.is_absolute() and p.is_file():
|
|
193
|
+
checkpoint_path = str(p.resolve())
|
|
194
|
+
elif p.is_absolute():
|
|
195
|
+
raise FileNotFoundError(f"ltxv audio vae file not found: {p}")
|
|
196
|
+
else:
|
|
197
|
+
name = path if isinstance(path, str) else p.name
|
|
198
|
+
checkpoint_path = folder_paths.get_full_path_or_raise("checkpoints", name)
|
|
199
|
+
|
|
200
|
+
state_dict, metadata = comfy_utils.load_torch_file(
|
|
201
|
+
checkpoint_path, return_metadata=True
|
|
202
|
+
)
|
|
203
|
+
return AudioVAE(state_dict, metadata)
|
|
204
|
+
|
|
205
|
+
def load_ltxav_text_encoder(
|
|
206
|
+
self, text_encoder_path: str | Path, checkpoint_path: str | Path
|
|
207
|
+
) -> object:
|
|
208
|
+
"""Load an LTXAV text encoder from two separate files.
|
|
209
|
+
|
|
210
|
+
``text_encoder_path`` is the text encoder file (from ``text_encoders/``).
|
|
211
|
+
``checkpoint_path`` is the companion checkpoint file (from ``checkpoints/``).
|
|
212
|
+
Both can be absolute paths to existing files or relative filenames resolved
|
|
213
|
+
via folder_paths.
|
|
214
|
+
"""
|
|
215
|
+
ensure_comfyui_on_path()
|
|
216
|
+
|
|
217
|
+
import folder_paths
|
|
218
|
+
from comfy import sd as comfy_sd
|
|
219
|
+
|
|
220
|
+
te_p = Path(text_encoder_path)
|
|
221
|
+
if te_p.is_absolute() and te_p.is_file():
|
|
222
|
+
resolved_te = str(te_p.resolve())
|
|
223
|
+
elif te_p.is_absolute():
|
|
224
|
+
raise FileNotFoundError(f"ltxav text encoder file not found: {te_p}")
|
|
225
|
+
else:
|
|
226
|
+
name = text_encoder_path if isinstance(text_encoder_path, str) else te_p.name
|
|
227
|
+
resolved_te = folder_paths.get_full_path_or_raise("text_encoders", name)
|
|
228
|
+
|
|
229
|
+
ckpt_p = Path(checkpoint_path)
|
|
230
|
+
if ckpt_p.is_absolute() and ckpt_p.is_file():
|
|
231
|
+
resolved_ckpt = str(ckpt_p.resolve())
|
|
232
|
+
elif ckpt_p.is_absolute():
|
|
233
|
+
raise FileNotFoundError(f"ltxav checkpoint file not found: {ckpt_p}")
|
|
234
|
+
else:
|
|
235
|
+
name = checkpoint_path if isinstance(checkpoint_path, str) else ckpt_p.name
|
|
236
|
+
resolved_ckpt = folder_paths.get_full_path_or_raise("checkpoints", name)
|
|
237
|
+
|
|
238
|
+
return comfy_sd.load_clip(
|
|
239
|
+
ckpt_paths=[resolved_te, resolved_ckpt],
|
|
240
|
+
embedding_directory=folder_paths.get_folder_paths("embeddings"),
|
|
241
|
+
clip_type=comfy_sd.CLIPType.LTXV,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
__all__ = ["CheckpointResult", "ModelManager"]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Runtime diagnostics for comfy_diffusion."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _python_version() -> str:
|
|
11
|
+
return ".".join(str(part) for part in sys.version_info[:3])
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _runtime_not_found(python_version: str, detail: str = "") -> dict[str, Any]:
|
|
15
|
+
msg = "ComfyUI runtime not found. Run: git submodule update --init"
|
|
16
|
+
if detail:
|
|
17
|
+
msg += f" (or install missing deps). Cause: {detail}"
|
|
18
|
+
return {
|
|
19
|
+
"error": msg,
|
|
20
|
+
"comfyui_version": None,
|
|
21
|
+
"device": None,
|
|
22
|
+
"vram_total_mb": None,
|
|
23
|
+
"vram_free_mb": None,
|
|
24
|
+
"python_version": python_version,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _runtime_not_responsive(python_version: str, message: str) -> dict[str, Any]:
|
|
29
|
+
return {
|
|
30
|
+
"error": f"ComfyUI runtime is not responsive: {message}",
|
|
31
|
+
"comfyui_version": None,
|
|
32
|
+
"device": None,
|
|
33
|
+
"vram_total_mb": None,
|
|
34
|
+
"vram_free_mb": None,
|
|
35
|
+
"python_version": python_version,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _bytes_to_mb(value: int) -> int:
|
|
40
|
+
return value // (1024 * 1024)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def check_runtime() -> dict[str, Any]:
|
|
44
|
+
"""Return structured runtime diagnostics for the current Python process."""
|
|
45
|
+
python_version = _python_version()
|
|
46
|
+
|
|
47
|
+
from ._runtime import ensure_comfyui_on_path
|
|
48
|
+
ensure_comfyui_on_path()
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
comfyui_version_module = importlib.import_module("comfyui_version")
|
|
52
|
+
model_management = importlib.import_module("comfy.model_management")
|
|
53
|
+
except Exception as exc:
|
|
54
|
+
return _runtime_not_found(python_version, str(exc))
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
device = model_management.get_torch_device()
|
|
58
|
+
except Exception as exc:
|
|
59
|
+
return _runtime_not_responsive(python_version, str(exc))
|
|
60
|
+
|
|
61
|
+
comfyui_version = str(getattr(comfyui_version_module, "__version__", "unknown"))
|
|
62
|
+
device_str = str(device)
|
|
63
|
+
device_type = getattr(device, "type", "")
|
|
64
|
+
|
|
65
|
+
if device_type == "cpu" or device_str == "cpu":
|
|
66
|
+
return {
|
|
67
|
+
"comfyui_version": comfyui_version,
|
|
68
|
+
"device": "cpu",
|
|
69
|
+
"vram_total_mb": 0,
|
|
70
|
+
"vram_free_mb": 0,
|
|
71
|
+
"python_version": python_version,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
total_memory_bytes = model_management.get_total_memory(device)
|
|
76
|
+
free_memory_bytes = model_management.get_free_memory(device)
|
|
77
|
+
except Exception as exc:
|
|
78
|
+
return _runtime_not_responsive(python_version, str(exc))
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
"comfyui_version": comfyui_version,
|
|
82
|
+
"device": device_str,
|
|
83
|
+
"vram_total_mb": _bytes_to_mb(int(total_memory_bytes)),
|
|
84
|
+
"vram_free_mb": _bytes_to_mb(int(free_memory_bytes)),
|
|
85
|
+
"python_version": python_version,
|
|
86
|
+
}
|