comfy-ui-api-workflow 0.0.1__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_ui_api_workflow/__about__.py +1 -0
- comfy_ui_api_workflow/__init__.py +9 -0
- comfy_ui_api_workflow/_impl/__init__.py +0 -0
- comfy_ui_api_workflow/_impl/data_structure.py +182 -0
- comfy_ui_api_workflow/_impl/enums.py +13 -0
- comfy_ui_api_workflow/_impl/errors/__init__.py +3 -0
- comfy_ui_api_workflow/_impl/errors/data_structure.py +34 -0
- comfy_ui_api_workflow/_impl/errors/node.py +63 -0
- comfy_ui_api_workflow/_impl/errors/output.py +5 -0
- comfy_ui_api_workflow/_impl/errors/pipeline.py +14 -0
- comfy_ui_api_workflow/_impl/errors/workflow.py +13 -0
- comfy_ui_api_workflow/_impl/exceptions.py +22 -0
- comfy_ui_api_workflow/_impl/lazy.py +71 -0
- comfy_ui_api_workflow/_impl/nodes/__init__.py +3 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/__init__.py +3 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/debug.py +103 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/guidance.py +281 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/hooks/__init__.py +29 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/hooks/clip.py +35 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/hooks/combine.py +108 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/hooks/cond_pair.py +203 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/hooks/cond_single.py +143 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/hooks/create.py +130 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/hooks/scheduling.py +167 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/multigpu.py +78 -0
- comfy_ui_api_workflow/_impl/nodes/audio.py +260 -0
- comfy_ui_api_workflow/_impl/nodes/base.py +214 -0
- comfy_ui_api_workflow/_impl/nodes/experimental/__init__.py +283 -0
- comfy_ui_api_workflow/_impl/nodes/experimental/attention_experiments.py +156 -0
- comfy_ui_api_workflow/_impl/nodes/experimental/stable_cascade.py +16 -0
- comfy_ui_api_workflow/_impl/nodes/image/__init__.py +601 -0
- comfy_ui_api_workflow/_impl/nodes/image/adjustments.py +46 -0
- comfy_ui_api_workflow/_impl/nodes/image/background_removal.py +16 -0
- comfy_ui_api_workflow/_impl/nodes/image/batch.py +236 -0
- comfy_ui_api_workflow/_impl/nodes/image/color.py +104 -0
- comfy_ui_api_workflow/_impl/nodes/image/compositing.py +128 -0
- comfy_ui_api_workflow/_impl/nodes/image/detection.py +689 -0
- comfy_ui_api_workflow/_impl/nodes/image/filters.py +215 -0
- comfy_ui_api_workflow/_impl/nodes/image/geometry_estimation.py +307 -0
- comfy_ui_api_workflow/_impl/nodes/image/mask.py +291 -0
- comfy_ui_api_workflow/_impl/nodes/image/post_processors.py +45 -0
- comfy_ui_api_workflow/_impl/nodes/image/pre_processors.py +13 -0
- comfy_ui_api_workflow/_impl/nodes/image/transform.py +385 -0
- comfy_ui_api_workflow/_impl/nodes/image/upscaling.py +188 -0
- comfy_ui_api_workflow/_impl/nodes/image/video.py +66 -0
- comfy_ui_api_workflow/_impl/nodes/model/__init__.py +3 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/__init__.py +395 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/ace.py +232 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/autoregressive.py +53 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/controlnet.py +153 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/cosmos.py +108 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/flux.py +121 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/gligen.py +57 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/hidream.py +31 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/hunyuan_3d.py +44 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/hunyuan_image.py +21 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/hunyuan_video.py +243 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/instructpix2pix.py +28 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/kandinsky.py +78 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/lotus.py +8 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/ltxv.py +504 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/lumina.py +39 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/minimax.py +110 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/minimax_music.py +53 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/photomaker.py +28 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/pixart.py +34 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/qwen_image.py +101 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_audio.py +34 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_cascade.py +16 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_diffusion.py +141 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_diffusion_upscaler.py +37 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_video.py +63 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_video_3d.py +53 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_zero123.py +128 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/transform.py +341 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/trellis.py +168 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/triposplat.py +54 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/void.py +61 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/__init__.py +184 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/animate.py +255 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/camera.py +150 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/dancer.py +102 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/fun_control.py +129 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/fun_inpaint.py +68 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/humo.py +63 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/move.py +332 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/phantom_subject.py +58 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/scail.py +161 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/sound.py +128 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/vace.py +73 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/yue2.py +189 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/z_image.py +59 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/__init__.py +510 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/ace.py +49 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/advanced/__init__.py +210 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/advanced/operations.py +113 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/autoregressive.py +36 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/batch.py +93 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/chroma_radiance.py +28 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/cosmos.py +36 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/flux.py +28 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/hidream.py +28 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/hunyuan_3d.py +57 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/hunyuan_image.py +28 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/hunyuan_video.py +130 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/ltxv.py +162 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/minimax.py +28 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/minimax_music.py +25 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/mochi.py +36 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/qwen.py +36 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/seedvr.py +16 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/stable_cascade.py +60 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/stable_diffusion.py +28 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/transform.py +92 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/trellis.py +91 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/triposplat.py +86 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/void.py +54 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/yue2.py +25 -0
- comfy_ui_api_workflow/_impl/nodes/model/loaders.py +820 -0
- comfy_ui_api_workflow/_impl/nodes/model/merging/__init__.py +290 -0
- comfy_ui_api_workflow/_impl/nodes/model/merging/model_specific.py +4370 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/__init__.py +463 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/anima.py +53 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/chroma_radiance.py +50 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/flux.py +63 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/hidream.py +72 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/ltxv.py +125 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/minimax.py +92 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/qwen.py +57 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/sensenova.py +21 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/stable_cascade.py +21 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/stable_diffusion.py +21 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/supir.py +57 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/unet.py +281 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/wan.py +143 -0
- comfy_ui_api_workflow/_impl/nodes/model/sampling/__init__.py +228 -0
- comfy_ui_api_workflow/_impl/nodes/model/sampling/custom.py +123 -0
- comfy_ui_api_workflow/_impl/nodes/model/sampling/guiders.py +252 -0
- comfy_ui_api_workflow/_impl/nodes/model/sampling/noise.py +52 -0
- comfy_ui_api_workflow/_impl/nodes/model/sampling/samplers.py +531 -0
- comfy_ui_api_workflow/_impl/nodes/model/sampling/schedulers.py +462 -0
- comfy_ui_api_workflow/_impl/nodes/model/sampling/sigmas.py +163 -0
- comfy_ui_api_workflow/_impl/nodes/model/training.py +249 -0
- comfy_ui_api_workflow/_impl/nodes/partner/__init__.py +3 -0
- comfy_ui_api_workflow/_impl/nodes/partner/audio/__init__.py +3 -0
- comfy_ui_api_workflow/_impl/nodes/partner/audio/elevenlabs.py +47 -0
- comfy_ui_api_workflow/_impl/nodes/partner/image/__init__.py +3 -0
- comfy_ui_api_workflow/_impl/nodes/partner/image/bytedance.py +16 -0
- comfy_ui_api_workflow/_impl/nodes/partner/image/krea.py +31 -0
- comfy_ui_api_workflow/_impl/nodes/partner/image/luma.py +27 -0
- comfy_ui_api_workflow/_impl/nodes/partner/image/recraft.py +220 -0
- comfy_ui_api_workflow/_impl/nodes/partner/text/__init__.py +3 -0
- comfy_ui_api_workflow/_impl/nodes/partner/text/gemini.py +25 -0
- comfy_ui_api_workflow/_impl/nodes/partner/text/openai.py +77 -0
- comfy_ui_api_workflow/_impl/nodes/partner/video/__init__.py +3 -0
- comfy_ui_api_workflow/_impl/nodes/partner/video/bytedance.py +16 -0
- comfy_ui_api_workflow/_impl/nodes/partner/video/pixverse.py +27 -0
- comfy_ui_api_workflow/_impl/nodes/text.py +506 -0
- comfy_ui_api_workflow/_impl/nodes/three_d/__init__.py +563 -0
- comfy_ui_api_workflow/_impl/nodes/three_d/mesh.py +183 -0
- comfy_ui_api_workflow/_impl/nodes/three_d/splat.py +262 -0
- comfy_ui_api_workflow/_impl/nodes/three_d/texturing.py +254 -0
- comfy_ui_api_workflow/_impl/nodes/utilities/__init__.py +404 -0
- comfy_ui_api_workflow/_impl/nodes/utilities/logic.py +44 -0
- comfy_ui_api_workflow/_impl/nodes/utilities/primitive.py +107 -0
- comfy_ui_api_workflow/_impl/nodes/validation.py +126 -0
- comfy_ui_api_workflow/_impl/nodes/video/__init__.py +440 -0
- comfy_ui_api_workflow/_impl/nodes/video/batch.py +45 -0
- comfy_ui_api_workflow/_impl/nodes/video/preprocessors.py +24 -0
- comfy_ui_api_workflow/_impl/nodes/video/transform.py +52 -0
- comfy_ui_api_workflow/_impl/pipeline.py +67 -0
- comfy_ui_api_workflow/_impl/workflow.py +539 -0
- comfy_ui_api_workflow-0.0.1.dist-info/METADATA +123 -0
- comfy_ui_api_workflow-0.0.1.dist-info/RECORD +176 -0
- comfy_ui_api_workflow-0.0.1.dist-info/WHEEL +4 -0
- comfy_ui_api_workflow-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
from typing import Any, Literal, get_args
|
|
2
|
+
|
|
3
|
+
from .base import BaseNode
|
|
4
|
+
from .validation import validate_choice, validate_not_empty, validate_range
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class LoadAudio(BaseNode):
|
|
8
|
+
__slots__ = ("audio",)
|
|
9
|
+
CATEGORY = "audio"
|
|
10
|
+
OUTPUTS = {"AUDIO": 0}
|
|
11
|
+
RETURN_TYPES = ("AUDIO",)
|
|
12
|
+
INPUT_SOCKET_TYPES = {"audio": "COMBO"}
|
|
13
|
+
|
|
14
|
+
def __init__(self, node_id: str, audio: str | tuple[str, int]) -> None:
|
|
15
|
+
super().__init__(node_id=node_id)
|
|
16
|
+
self.audio = audio
|
|
17
|
+
self.__validate()
|
|
18
|
+
|
|
19
|
+
def __validate(self) -> None:
|
|
20
|
+
validate_not_empty("audio", self.audio)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PreviewAudio(BaseNode):
|
|
24
|
+
__slots__ = ("audio",)
|
|
25
|
+
CATEGORY = "audio"
|
|
26
|
+
OUTPUTS = {"audio": 0}
|
|
27
|
+
RETURN_TYPES = ("AUDIO",)
|
|
28
|
+
INPUT_SOCKET_TYPES = {"audio": "AUDIO"}
|
|
29
|
+
|
|
30
|
+
def __init__(self, node_id: str, audio: tuple[str, int]) -> None:
|
|
31
|
+
super().__init__(node_id=node_id)
|
|
32
|
+
self.audio = audio
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class RecordAudio(BaseNode):
|
|
36
|
+
__slots__ = ("audio",)
|
|
37
|
+
CATEGORY = "audio"
|
|
38
|
+
OUTPUTS = {"AUDIO": 0}
|
|
39
|
+
RETURN_TYPES = ("AUDIO",)
|
|
40
|
+
INPUT_SOCKET_TYPES = {"audio": "AUDIO_RECORD"}
|
|
41
|
+
|
|
42
|
+
def __init__(self, node_id: str, audio: Any) -> None:
|
|
43
|
+
super().__init__(node_id=node_id)
|
|
44
|
+
self.audio = audio
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TrimAudioDuration(BaseNode):
|
|
48
|
+
__slots__ = ("audio", "start_index", "duration")
|
|
49
|
+
CATEGORY = "audio"
|
|
50
|
+
OUTPUTS = {"AUDIO": 0}
|
|
51
|
+
RETURN_TYPES = ("AUDIO",)
|
|
52
|
+
INPUT_SOCKET_TYPES = {"audio": "AUDIO", "start_index": "FLOAT", "duration": "FLOAT"}
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
node_id: str,
|
|
57
|
+
audio: tuple[str, int],
|
|
58
|
+
start_index: float | tuple[str, int] = 0.0,
|
|
59
|
+
duration: float | tuple[str, int] = 60.0,
|
|
60
|
+
) -> None:
|
|
61
|
+
super().__init__(node_id=node_id)
|
|
62
|
+
self.audio = audio
|
|
63
|
+
self.start_index = start_index
|
|
64
|
+
self.duration = duration
|
|
65
|
+
self.__validate()
|
|
66
|
+
|
|
67
|
+
def __validate(self) -> None:
|
|
68
|
+
validate_range(
|
|
69
|
+
"start_index",
|
|
70
|
+
self.start_index,
|
|
71
|
+
-18_446_744_073_709_551_615,
|
|
72
|
+
18_446_744_073_709_551_615,
|
|
73
|
+
)
|
|
74
|
+
validate_range("duration", self.duration, 0.0)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class SplitAudioChannels(BaseNode):
|
|
78
|
+
__slots__ = ("audio",)
|
|
79
|
+
CATEGORY = "audio"
|
|
80
|
+
OUTPUTS = {"left": 0, "right": 1}
|
|
81
|
+
RETURN_TYPES = ("AUDIO", "AUDIO")
|
|
82
|
+
INPUT_SOCKET_TYPES = {"audio": "AUDIO"}
|
|
83
|
+
|
|
84
|
+
def __init__(self, node_id: str, audio: tuple[str, int]) -> None:
|
|
85
|
+
super().__init__(node_id=node_id)
|
|
86
|
+
self.audio = audio
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class JoinAudioChannels(BaseNode):
|
|
90
|
+
__slots__ = ("audio_left", "audio_right")
|
|
91
|
+
CATEGORY = "audio"
|
|
92
|
+
OUTPUTS = {"audio": 0}
|
|
93
|
+
RETURN_TYPES = ("AUDIO",)
|
|
94
|
+
INPUT_SOCKET_TYPES = {"audio_left": "AUDIO", "audio_right": "AUDIO"}
|
|
95
|
+
|
|
96
|
+
def __init__(
|
|
97
|
+
self, node_id: str, audio_left: tuple[str, int], audio_right: tuple[str, int]
|
|
98
|
+
) -> None:
|
|
99
|
+
super().__init__(node_id=node_id)
|
|
100
|
+
self.audio_left = audio_left
|
|
101
|
+
self.audio_right = audio_right
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
AudioConcat_Directions = Literal["after", "before"]
|
|
105
|
+
AUDIOCONCAT_DIRECTIONS = frozenset(get_args(AudioConcat_Directions))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class AudioConcat(BaseNode):
|
|
109
|
+
__slots__ = ("audio1", "audio2", "direction")
|
|
110
|
+
CATEGORY = "audio"
|
|
111
|
+
OUTPUTS = {"AUDIO": 0}
|
|
112
|
+
RETURN_TYPES = ("AUDIO",)
|
|
113
|
+
INPUT_SOCKET_TYPES = {"audio1": "AUDIO", "audio2": "AUDIO", "direction": "COMBO"}
|
|
114
|
+
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
node_id: str,
|
|
118
|
+
audio1: tuple[str, int],
|
|
119
|
+
audio2: tuple[str, int],
|
|
120
|
+
direction: AudioConcat_Directions | tuple[str, int] = "after",
|
|
121
|
+
) -> None:
|
|
122
|
+
super().__init__(node_id=node_id)
|
|
123
|
+
self.audio1 = audio1
|
|
124
|
+
self.audio2 = audio2
|
|
125
|
+
self.direction = direction
|
|
126
|
+
self.__validate()
|
|
127
|
+
|
|
128
|
+
def __validate(self) -> None:
|
|
129
|
+
validate_choice("direction", self.direction, AUDIOCONCAT_DIRECTIONS)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
AudioMerge_Merge_Methods = Literal["add", "mean", "subtract", "multiply"]
|
|
133
|
+
AUDIOMERGE_MERGE_METHODS = frozenset(get_args(AudioMerge_Merge_Methods))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class AudioMerge(BaseNode):
|
|
137
|
+
__slots__ = ("audio1", "audio2", "merge_method")
|
|
138
|
+
CATEGORY = "audio"
|
|
139
|
+
OUTPUTS = {"AUDIO": 0}
|
|
140
|
+
RETURN_TYPES = ("AUDIO",)
|
|
141
|
+
INPUT_SOCKET_TYPES = {"audio1": "AUDIO", "audio2": "AUDIO", "merge_method": "COMBO"}
|
|
142
|
+
|
|
143
|
+
def __init__(
|
|
144
|
+
self,
|
|
145
|
+
node_id: str,
|
|
146
|
+
audio1: tuple[str, int],
|
|
147
|
+
audio2: tuple[str, int],
|
|
148
|
+
merge_method: AudioMerge_Merge_Methods | tuple[str, int],
|
|
149
|
+
) -> None:
|
|
150
|
+
super().__init__(node_id=node_id)
|
|
151
|
+
self.audio1 = audio1
|
|
152
|
+
self.audio2 = audio2
|
|
153
|
+
self.merge_method = merge_method
|
|
154
|
+
self.__validate()
|
|
155
|
+
|
|
156
|
+
def __validate(self) -> None:
|
|
157
|
+
validate_choice("merge_method", self.merge_method, AUDIOMERGE_MERGE_METHODS)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class AudioAdjustVolume(BaseNode):
|
|
161
|
+
__slots__ = ("audio", "volume")
|
|
162
|
+
CATEGORY = "audio"
|
|
163
|
+
OUTPUTS = {"AUDIO": 0}
|
|
164
|
+
RETURN_TYPES = ("AUDIO",)
|
|
165
|
+
INPUT_SOCKET_TYPES = {"audio": "AUDIO", "volume": "INT"}
|
|
166
|
+
|
|
167
|
+
def __init__(
|
|
168
|
+
self, node_id: str, audio: tuple[str, int], volume: int | tuple[str, int] = 1
|
|
169
|
+
) -> None:
|
|
170
|
+
super().__init__(node_id=node_id)
|
|
171
|
+
self.audio = audio
|
|
172
|
+
self.volume = volume
|
|
173
|
+
self.__validate()
|
|
174
|
+
|
|
175
|
+
def __validate(self) -> None:
|
|
176
|
+
validate_range("volume", self.volume, -100, 100)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class EmptyAudio(BaseNode):
|
|
180
|
+
__slots__ = ("duration", "sample_rate", "channels")
|
|
181
|
+
CATEGORY = "audio"
|
|
182
|
+
OUTPUTS = {"AUDIO": 0}
|
|
183
|
+
RETURN_TYPES = ("AUDIO",)
|
|
184
|
+
INPUT_SOCKET_TYPES = {"duration": "FLOAT", "sample_rate": "INT", "channels": "INT"}
|
|
185
|
+
|
|
186
|
+
def __init__(
|
|
187
|
+
self,
|
|
188
|
+
node_id: str,
|
|
189
|
+
duration: float | tuple[str, int] = 60.0,
|
|
190
|
+
sample_rate: int | tuple[str, int] = 44_100,
|
|
191
|
+
channels: int | tuple[str, int] = 2,
|
|
192
|
+
) -> None:
|
|
193
|
+
super().__init__(node_id=node_id)
|
|
194
|
+
self.duration = duration
|
|
195
|
+
self.sample_rate = sample_rate
|
|
196
|
+
self.channels = channels
|
|
197
|
+
self.__validate()
|
|
198
|
+
|
|
199
|
+
def __validate(self) -> None:
|
|
200
|
+
validate_range("duration", self.duration, 0.0, 18_446_744_073_709_551_615)
|
|
201
|
+
validate_range("sample_rate", self.sample_rate, 1, 192_000)
|
|
202
|
+
validate_range("channels", self.channels, 1, 2)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class AudioEqualizer3Band(BaseNode):
|
|
206
|
+
__slots__ = (
|
|
207
|
+
"audio",
|
|
208
|
+
"low_gain_dB",
|
|
209
|
+
"low_freq",
|
|
210
|
+
"mid_gain_dB",
|
|
211
|
+
"mid_freq",
|
|
212
|
+
"mid_q",
|
|
213
|
+
"high_gain_dB",
|
|
214
|
+
"high_freq",
|
|
215
|
+
)
|
|
216
|
+
CATEGORY = "audio"
|
|
217
|
+
OUTPUTS = {"AUDIO": 0}
|
|
218
|
+
RETURN_TYPES = ("AUDIO",)
|
|
219
|
+
INPUT_SOCKET_TYPES = {
|
|
220
|
+
"audio": "AUDIO",
|
|
221
|
+
"low_gain_dB": "FLOAT",
|
|
222
|
+
"low_freq": "INT",
|
|
223
|
+
"mid_gain_dB": "FLOAT",
|
|
224
|
+
"mid_freq": "INT",
|
|
225
|
+
"mid_q": "FLOAT",
|
|
226
|
+
"high_gain_dB": "FLOAT",
|
|
227
|
+
"high_freq": "INT",
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
def __init__(
|
|
231
|
+
self,
|
|
232
|
+
node_id: str,
|
|
233
|
+
audio: tuple[str, int],
|
|
234
|
+
low_gain_dB: float | tuple[str, int] = 0.0,
|
|
235
|
+
low_freq: int | tuple[str, int] = 100,
|
|
236
|
+
mid_gain_dB: float | tuple[str, int] = 0.0,
|
|
237
|
+
mid_freq: int | tuple[str, int] = 1000,
|
|
238
|
+
mid_q: float | tuple[str, int] = 0.707,
|
|
239
|
+
high_gain_dB: float | tuple[str, int] = 0.0,
|
|
240
|
+
high_freq: int | tuple[str, int] = 5000,
|
|
241
|
+
) -> None:
|
|
242
|
+
super().__init__(node_id=node_id)
|
|
243
|
+
self.audio = audio
|
|
244
|
+
self.low_gain_dB = low_gain_dB
|
|
245
|
+
self.low_freq = low_freq
|
|
246
|
+
self.mid_gain_dB = mid_gain_dB
|
|
247
|
+
self.mid_freq = mid_freq
|
|
248
|
+
self.mid_q = mid_q
|
|
249
|
+
self.high_gain_dB = high_gain_dB
|
|
250
|
+
self.high_freq = high_freq
|
|
251
|
+
self.__validate()
|
|
252
|
+
|
|
253
|
+
def __validate(self) -> None:
|
|
254
|
+
validate_range("low_gain_dB", self.low_gain_dB, -24.0, 24.0)
|
|
255
|
+
validate_range("low_freq", self.low_freq, 20, 500)
|
|
256
|
+
validate_range("mid_gain_dB", self.mid_gain_dB, -24.0, 24.0)
|
|
257
|
+
validate_range("mid_freq", self.mid_freq, 200, 4000)
|
|
258
|
+
validate_range("mid_q", self.mid_q, 0.1, 10.0)
|
|
259
|
+
validate_range("high_gain_dB", self.high_gain_dB, -24.0, 24.0)
|
|
260
|
+
validate_range("high_freq", self.high_freq, 1000, 15_000)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import inspect
|
|
3
|
+
from collections.abc import Callable, Iterator
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from typing import Any, ClassVar, NamedTuple
|
|
6
|
+
|
|
7
|
+
from ..errors.node import InvalidNodeParameterValue
|
|
8
|
+
from ..errors.output import OutputNotFoundError
|
|
9
|
+
from .validation import is_link, validate_type
|
|
10
|
+
|
|
11
|
+
_UNSET = object()
|
|
12
|
+
_INPUT_KINDS = (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY)
|
|
13
|
+
_STATE = ("_on_change", "_built", "_batch")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _Signature(NamedTuple):
|
|
17
|
+
annotations: dict[str, Any]
|
|
18
|
+
required: frozenset[str]
|
|
19
|
+
rebuildable: bool
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@functools.cache
|
|
23
|
+
def _signature(node_type: type) -> _Signature:
|
|
24
|
+
parameters = inspect.signature(node_type).parameters.values()
|
|
25
|
+
named = {
|
|
26
|
+
parameter.name: parameter
|
|
27
|
+
for parameter in parameters
|
|
28
|
+
if parameter.kind in _INPUT_KINDS
|
|
29
|
+
}
|
|
30
|
+
annotations = {
|
|
31
|
+
name: parameter.annotation
|
|
32
|
+
for name, parameter in named.items()
|
|
33
|
+
if parameter.annotation is not parameter.empty
|
|
34
|
+
}
|
|
35
|
+
annotations["node_id"] = str
|
|
36
|
+
classes = node_type.__mro__
|
|
37
|
+
slots = {
|
|
38
|
+
name
|
|
39
|
+
for cls in classes[: classes.index(BaseNode)]
|
|
40
|
+
for name in cls.__dict__.get("__slots__", ())
|
|
41
|
+
}
|
|
42
|
+
takes_any = any(parameter.kind is parameter.VAR_KEYWORD for parameter in parameters)
|
|
43
|
+
required = {
|
|
44
|
+
name
|
|
45
|
+
for name, parameter in named.items()
|
|
46
|
+
if parameter.default is parameter.empty
|
|
47
|
+
}
|
|
48
|
+
return _Signature(
|
|
49
|
+
annotations=annotations,
|
|
50
|
+
required=frozenset(required),
|
|
51
|
+
# The node can only be checked again by building it from its inputs.
|
|
52
|
+
rebuildable=(takes_any or slots <= set(named))
|
|
53
|
+
and required - {"node_id"} <= slots,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _tracks_construction(init: Callable[..., None]) -> Callable[..., None]:
|
|
58
|
+
# Only the outermost __init__ marks the node as built, so a subclass that
|
|
59
|
+
# sets inputs after super().__init__() is not checked half built.
|
|
60
|
+
@functools.wraps(init)
|
|
61
|
+
def wrapper(self: "BaseNode", *args: Any, **kwargs: Any) -> None:
|
|
62
|
+
outermost = not hasattr(self, "_built")
|
|
63
|
+
if outermost:
|
|
64
|
+
object.__setattr__(self, "_built", False)
|
|
65
|
+
init(self, *args, **kwargs)
|
|
66
|
+
if outermost:
|
|
67
|
+
object.__setattr__(self, "_built", True)
|
|
68
|
+
|
|
69
|
+
return wrapper
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _api_value(value: Any) -> Any:
|
|
73
|
+
# ComfyUI reads every list as a link, so a list widget value goes wrapped.
|
|
74
|
+
if isinstance(value, list) and not is_link(value):
|
|
75
|
+
return {"__value__": value}
|
|
76
|
+
return value
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class NodeOutput:
|
|
80
|
+
__slots__ = ("node_id", "outputs")
|
|
81
|
+
|
|
82
|
+
def __init__(self, node_id: str, outputs: dict[str, int]) -> None:
|
|
83
|
+
self.node_id = node_id
|
|
84
|
+
self.outputs = outputs
|
|
85
|
+
|
|
86
|
+
def __getitem__(self, name: str) -> tuple[str, int]:
|
|
87
|
+
try:
|
|
88
|
+
return (self.node_id, self.outputs[name])
|
|
89
|
+
except KeyError as error:
|
|
90
|
+
raise OutputNotFoundError(
|
|
91
|
+
f"node: {self.node_id} don't have the output: {name}"
|
|
92
|
+
) from error
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class BaseNode:
|
|
96
|
+
__slots__ = ("node_id", "_on_change", "_built", "_batch")
|
|
97
|
+
CATEGORY: ClassVar[str]
|
|
98
|
+
CLASS_TYPE: ClassVar[str]
|
|
99
|
+
OUTPUTS: ClassVar[dict[str, int]] = {}
|
|
100
|
+
INPUT_NAMES: ClassVar[dict[str, str]] = {}
|
|
101
|
+
# ComfyUI socket types, as in object_info: RETURN_TYPES by output index and
|
|
102
|
+
# INPUT_SOCKET_TYPES by attribute name. A node without them is not type checked.
|
|
103
|
+
# A V1 combo, whose type in object_info is its list of options, is "V1 COMBO":
|
|
104
|
+
# no output has that type, so as in ComfyUI only * and match type outputs fit.
|
|
105
|
+
RETURN_TYPES: ClassVar[tuple[str, ...]] = ()
|
|
106
|
+
INPUT_SOCKET_TYPES: ClassVar[dict[str, str]] = {}
|
|
107
|
+
|
|
108
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
109
|
+
super().__init_subclass__(**kwargs)
|
|
110
|
+
# Nodes whose ComfyUI name is not an identifier, like "Epsilon Scaling",
|
|
111
|
+
# declare CLASS_TYPE; every other node uses its class name.
|
|
112
|
+
if "CLASS_TYPE" not in cls.__dict__:
|
|
113
|
+
cls.CLASS_TYPE = cls.__name__
|
|
114
|
+
if "__init__" in cls.__dict__:
|
|
115
|
+
cls.__init__ = _tracks_construction(cls.__dict__["__init__"])
|
|
116
|
+
|
|
117
|
+
@_tracks_construction
|
|
118
|
+
def __init__(self, node_id: str) -> None:
|
|
119
|
+
self.node_id = node_id
|
|
120
|
+
self._on_change: Callable[[BaseNode], None] | None = None
|
|
121
|
+
|
|
122
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
123
|
+
# The type check runs before the value is stored, so a wrong type never
|
|
124
|
+
# gets in, whether at construction or in a later change.
|
|
125
|
+
annotation = _signature(type(self)).annotations.get(name)
|
|
126
|
+
if annotation is not None:
|
|
127
|
+
validate_type(name, value, annotation)
|
|
128
|
+
if name in _STATE or not getattr(self, "_built", False):
|
|
129
|
+
super().__setattr__(name, value)
|
|
130
|
+
return
|
|
131
|
+
previous = getattr(self, name, _UNSET)
|
|
132
|
+
super().__setattr__(name, value)
|
|
133
|
+
try:
|
|
134
|
+
self.__changed()
|
|
135
|
+
except Exception:
|
|
136
|
+
# A rejected change is undone, so the node stays as it was.
|
|
137
|
+
if previous is _UNSET:
|
|
138
|
+
super().__delattr__(name)
|
|
139
|
+
else:
|
|
140
|
+
super().__setattr__(name, previous)
|
|
141
|
+
raise
|
|
142
|
+
|
|
143
|
+
def __delattr__(self, name: str) -> None:
|
|
144
|
+
if name in _STATE or not getattr(self, "_built", False):
|
|
145
|
+
super().__delattr__(name)
|
|
146
|
+
return
|
|
147
|
+
if name in _signature(type(self)).required:
|
|
148
|
+
raise InvalidNodeParameterValue(
|
|
149
|
+
f"{name} cannot be removed because it is not optional"
|
|
150
|
+
)
|
|
151
|
+
previous = getattr(self, name)
|
|
152
|
+
super().__delattr__(name)
|
|
153
|
+
try:
|
|
154
|
+
self.__changed(removed=name)
|
|
155
|
+
except Exception:
|
|
156
|
+
super().__setattr__(name, previous)
|
|
157
|
+
raise
|
|
158
|
+
|
|
159
|
+
def __changed(self, removed: str | None = None) -> None:
|
|
160
|
+
if not getattr(self, "_batch", False):
|
|
161
|
+
rebuilt = self._validate()
|
|
162
|
+
# If the constructor fills a removed input back in, the class always
|
|
163
|
+
# sends it, like a default or a dict that None stands for.
|
|
164
|
+
if (
|
|
165
|
+
removed is not None
|
|
166
|
+
and rebuilt is not None
|
|
167
|
+
and removed in rebuilt.inputs()
|
|
168
|
+
):
|
|
169
|
+
raise InvalidNodeParameterValue(
|
|
170
|
+
f"{removed} cannot be removed because it is not optional"
|
|
171
|
+
)
|
|
172
|
+
on_change = getattr(self, "_on_change", None)
|
|
173
|
+
if on_change is not None:
|
|
174
|
+
on_change(self)
|
|
175
|
+
|
|
176
|
+
def _validate(self) -> "BaseNode | None":
|
|
177
|
+
# Building a node runs every check of its class, so a copy built from the
|
|
178
|
+
# current inputs tells if they are still valid together.
|
|
179
|
+
if _signature(type(self)).rebuildable:
|
|
180
|
+
return type(self)(node_id=self.node_id, **self.inputs())
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
@contextmanager
|
|
184
|
+
def _changing(self) -> Iterator[None]:
|
|
185
|
+
# Inside the block each change is still type checked and linked, but the
|
|
186
|
+
# node is only validated again by the caller, once, at the end.
|
|
187
|
+
batch = getattr(self, "_batch", False)
|
|
188
|
+
object.__setattr__(self, "_batch", True)
|
|
189
|
+
try:
|
|
190
|
+
yield
|
|
191
|
+
finally:
|
|
192
|
+
object.__setattr__(self, "_batch", batch)
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def output(self) -> NodeOutput:
|
|
196
|
+
return NodeOutput(node_id=self.node_id, outputs=type(self).OUTPUTS)
|
|
197
|
+
|
|
198
|
+
def inputs(self) -> dict[str, Any]:
|
|
199
|
+
inputs: dict[str, Any] = {}
|
|
200
|
+
node_classes = type(self).__mro__
|
|
201
|
+
# Every slot declared below BaseNode is a ComfyUI input.
|
|
202
|
+
for cls in reversed(node_classes[: node_classes.index(BaseNode)]):
|
|
203
|
+
for name in cls.__dict__.get("__slots__", ()):
|
|
204
|
+
if hasattr(self, name):
|
|
205
|
+
inputs[name] = getattr(self, name)
|
|
206
|
+
return inputs
|
|
207
|
+
|
|
208
|
+
def get_comfy_ui_workflow_format(self) -> dict[str, Any]:
|
|
209
|
+
node_type = type(self)
|
|
210
|
+
inputs = {
|
|
211
|
+
node_type.INPUT_NAMES.get(name, name): _api_value(value)
|
|
212
|
+
for name, value in self.inputs().items()
|
|
213
|
+
}
|
|
214
|
+
return {self.node_id: {"class_type": node_type.CLASS_TYPE, "inputs": inputs}}
|