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.
Files changed (176) hide show
  1. comfy_ui_api_workflow/__about__.py +1 -0
  2. comfy_ui_api_workflow/__init__.py +9 -0
  3. comfy_ui_api_workflow/_impl/__init__.py +0 -0
  4. comfy_ui_api_workflow/_impl/data_structure.py +182 -0
  5. comfy_ui_api_workflow/_impl/enums.py +13 -0
  6. comfy_ui_api_workflow/_impl/errors/__init__.py +3 -0
  7. comfy_ui_api_workflow/_impl/errors/data_structure.py +34 -0
  8. comfy_ui_api_workflow/_impl/errors/node.py +63 -0
  9. comfy_ui_api_workflow/_impl/errors/output.py +5 -0
  10. comfy_ui_api_workflow/_impl/errors/pipeline.py +14 -0
  11. comfy_ui_api_workflow/_impl/errors/workflow.py +13 -0
  12. comfy_ui_api_workflow/_impl/exceptions.py +22 -0
  13. comfy_ui_api_workflow/_impl/lazy.py +71 -0
  14. comfy_ui_api_workflow/_impl/nodes/__init__.py +3 -0
  15. comfy_ui_api_workflow/_impl/nodes/advanced/__init__.py +3 -0
  16. comfy_ui_api_workflow/_impl/nodes/advanced/debug.py +103 -0
  17. comfy_ui_api_workflow/_impl/nodes/advanced/guidance.py +281 -0
  18. comfy_ui_api_workflow/_impl/nodes/advanced/hooks/__init__.py +29 -0
  19. comfy_ui_api_workflow/_impl/nodes/advanced/hooks/clip.py +35 -0
  20. comfy_ui_api_workflow/_impl/nodes/advanced/hooks/combine.py +108 -0
  21. comfy_ui_api_workflow/_impl/nodes/advanced/hooks/cond_pair.py +203 -0
  22. comfy_ui_api_workflow/_impl/nodes/advanced/hooks/cond_single.py +143 -0
  23. comfy_ui_api_workflow/_impl/nodes/advanced/hooks/create.py +130 -0
  24. comfy_ui_api_workflow/_impl/nodes/advanced/hooks/scheduling.py +167 -0
  25. comfy_ui_api_workflow/_impl/nodes/advanced/multigpu.py +78 -0
  26. comfy_ui_api_workflow/_impl/nodes/audio.py +260 -0
  27. comfy_ui_api_workflow/_impl/nodes/base.py +214 -0
  28. comfy_ui_api_workflow/_impl/nodes/experimental/__init__.py +283 -0
  29. comfy_ui_api_workflow/_impl/nodes/experimental/attention_experiments.py +156 -0
  30. comfy_ui_api_workflow/_impl/nodes/experimental/stable_cascade.py +16 -0
  31. comfy_ui_api_workflow/_impl/nodes/image/__init__.py +601 -0
  32. comfy_ui_api_workflow/_impl/nodes/image/adjustments.py +46 -0
  33. comfy_ui_api_workflow/_impl/nodes/image/background_removal.py +16 -0
  34. comfy_ui_api_workflow/_impl/nodes/image/batch.py +236 -0
  35. comfy_ui_api_workflow/_impl/nodes/image/color.py +104 -0
  36. comfy_ui_api_workflow/_impl/nodes/image/compositing.py +128 -0
  37. comfy_ui_api_workflow/_impl/nodes/image/detection.py +689 -0
  38. comfy_ui_api_workflow/_impl/nodes/image/filters.py +215 -0
  39. comfy_ui_api_workflow/_impl/nodes/image/geometry_estimation.py +307 -0
  40. comfy_ui_api_workflow/_impl/nodes/image/mask.py +291 -0
  41. comfy_ui_api_workflow/_impl/nodes/image/post_processors.py +45 -0
  42. comfy_ui_api_workflow/_impl/nodes/image/pre_processors.py +13 -0
  43. comfy_ui_api_workflow/_impl/nodes/image/transform.py +385 -0
  44. comfy_ui_api_workflow/_impl/nodes/image/upscaling.py +188 -0
  45. comfy_ui_api_workflow/_impl/nodes/image/video.py +66 -0
  46. comfy_ui_api_workflow/_impl/nodes/model/__init__.py +3 -0
  47. comfy_ui_api_workflow/_impl/nodes/model/conditioning/__init__.py +395 -0
  48. comfy_ui_api_workflow/_impl/nodes/model/conditioning/ace.py +232 -0
  49. comfy_ui_api_workflow/_impl/nodes/model/conditioning/autoregressive.py +53 -0
  50. comfy_ui_api_workflow/_impl/nodes/model/conditioning/controlnet.py +153 -0
  51. comfy_ui_api_workflow/_impl/nodes/model/conditioning/cosmos.py +108 -0
  52. comfy_ui_api_workflow/_impl/nodes/model/conditioning/flux.py +121 -0
  53. comfy_ui_api_workflow/_impl/nodes/model/conditioning/gligen.py +57 -0
  54. comfy_ui_api_workflow/_impl/nodes/model/conditioning/hidream.py +31 -0
  55. comfy_ui_api_workflow/_impl/nodes/model/conditioning/hunyuan_3d.py +44 -0
  56. comfy_ui_api_workflow/_impl/nodes/model/conditioning/hunyuan_image.py +21 -0
  57. comfy_ui_api_workflow/_impl/nodes/model/conditioning/hunyuan_video.py +243 -0
  58. comfy_ui_api_workflow/_impl/nodes/model/conditioning/instructpix2pix.py +28 -0
  59. comfy_ui_api_workflow/_impl/nodes/model/conditioning/kandinsky.py +78 -0
  60. comfy_ui_api_workflow/_impl/nodes/model/conditioning/lotus.py +8 -0
  61. comfy_ui_api_workflow/_impl/nodes/model/conditioning/ltxv.py +504 -0
  62. comfy_ui_api_workflow/_impl/nodes/model/conditioning/lumina.py +39 -0
  63. comfy_ui_api_workflow/_impl/nodes/model/conditioning/minimax.py +110 -0
  64. comfy_ui_api_workflow/_impl/nodes/model/conditioning/minimax_music.py +53 -0
  65. comfy_ui_api_workflow/_impl/nodes/model/conditioning/photomaker.py +28 -0
  66. comfy_ui_api_workflow/_impl/nodes/model/conditioning/pixart.py +34 -0
  67. comfy_ui_api_workflow/_impl/nodes/model/conditioning/qwen_image.py +101 -0
  68. comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_audio.py +34 -0
  69. comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_cascade.py +16 -0
  70. comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_diffusion.py +141 -0
  71. comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_diffusion_upscaler.py +37 -0
  72. comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_video.py +63 -0
  73. comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_video_3d.py +53 -0
  74. comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_zero123.py +128 -0
  75. comfy_ui_api_workflow/_impl/nodes/model/conditioning/transform.py +341 -0
  76. comfy_ui_api_workflow/_impl/nodes/model/conditioning/trellis.py +168 -0
  77. comfy_ui_api_workflow/_impl/nodes/model/conditioning/triposplat.py +54 -0
  78. comfy_ui_api_workflow/_impl/nodes/model/conditioning/void.py +61 -0
  79. comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/__init__.py +184 -0
  80. comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/animate.py +255 -0
  81. comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/camera.py +150 -0
  82. comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/dancer.py +102 -0
  83. comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/fun_control.py +129 -0
  84. comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/fun_inpaint.py +68 -0
  85. comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/humo.py +63 -0
  86. comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/move.py +332 -0
  87. comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/phantom_subject.py +58 -0
  88. comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/scail.py +161 -0
  89. comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/sound.py +128 -0
  90. comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/vace.py +73 -0
  91. comfy_ui_api_workflow/_impl/nodes/model/conditioning/yue2.py +189 -0
  92. comfy_ui_api_workflow/_impl/nodes/model/conditioning/z_image.py +59 -0
  93. comfy_ui_api_workflow/_impl/nodes/model/latent/__init__.py +510 -0
  94. comfy_ui_api_workflow/_impl/nodes/model/latent/ace.py +49 -0
  95. comfy_ui_api_workflow/_impl/nodes/model/latent/advanced/__init__.py +210 -0
  96. comfy_ui_api_workflow/_impl/nodes/model/latent/advanced/operations.py +113 -0
  97. comfy_ui_api_workflow/_impl/nodes/model/latent/autoregressive.py +36 -0
  98. comfy_ui_api_workflow/_impl/nodes/model/latent/batch.py +93 -0
  99. comfy_ui_api_workflow/_impl/nodes/model/latent/chroma_radiance.py +28 -0
  100. comfy_ui_api_workflow/_impl/nodes/model/latent/cosmos.py +36 -0
  101. comfy_ui_api_workflow/_impl/nodes/model/latent/flux.py +28 -0
  102. comfy_ui_api_workflow/_impl/nodes/model/latent/hidream.py +28 -0
  103. comfy_ui_api_workflow/_impl/nodes/model/latent/hunyuan_3d.py +57 -0
  104. comfy_ui_api_workflow/_impl/nodes/model/latent/hunyuan_image.py +28 -0
  105. comfy_ui_api_workflow/_impl/nodes/model/latent/hunyuan_video.py +130 -0
  106. comfy_ui_api_workflow/_impl/nodes/model/latent/ltxv.py +162 -0
  107. comfy_ui_api_workflow/_impl/nodes/model/latent/minimax.py +28 -0
  108. comfy_ui_api_workflow/_impl/nodes/model/latent/minimax_music.py +25 -0
  109. comfy_ui_api_workflow/_impl/nodes/model/latent/mochi.py +36 -0
  110. comfy_ui_api_workflow/_impl/nodes/model/latent/qwen.py +36 -0
  111. comfy_ui_api_workflow/_impl/nodes/model/latent/seedvr.py +16 -0
  112. comfy_ui_api_workflow/_impl/nodes/model/latent/stable_cascade.py +60 -0
  113. comfy_ui_api_workflow/_impl/nodes/model/latent/stable_diffusion.py +28 -0
  114. comfy_ui_api_workflow/_impl/nodes/model/latent/transform.py +92 -0
  115. comfy_ui_api_workflow/_impl/nodes/model/latent/trellis.py +91 -0
  116. comfy_ui_api_workflow/_impl/nodes/model/latent/triposplat.py +86 -0
  117. comfy_ui_api_workflow/_impl/nodes/model/latent/void.py +54 -0
  118. comfy_ui_api_workflow/_impl/nodes/model/latent/yue2.py +25 -0
  119. comfy_ui_api_workflow/_impl/nodes/model/loaders.py +820 -0
  120. comfy_ui_api_workflow/_impl/nodes/model/merging/__init__.py +290 -0
  121. comfy_ui_api_workflow/_impl/nodes/model/merging/model_specific.py +4370 -0
  122. comfy_ui_api_workflow/_impl/nodes/model/patch/__init__.py +463 -0
  123. comfy_ui_api_workflow/_impl/nodes/model/patch/anima.py +53 -0
  124. comfy_ui_api_workflow/_impl/nodes/model/patch/chroma_radiance.py +50 -0
  125. comfy_ui_api_workflow/_impl/nodes/model/patch/flux.py +63 -0
  126. comfy_ui_api_workflow/_impl/nodes/model/patch/hidream.py +72 -0
  127. comfy_ui_api_workflow/_impl/nodes/model/patch/ltxv.py +125 -0
  128. comfy_ui_api_workflow/_impl/nodes/model/patch/minimax.py +92 -0
  129. comfy_ui_api_workflow/_impl/nodes/model/patch/qwen.py +57 -0
  130. comfy_ui_api_workflow/_impl/nodes/model/patch/sensenova.py +21 -0
  131. comfy_ui_api_workflow/_impl/nodes/model/patch/stable_cascade.py +21 -0
  132. comfy_ui_api_workflow/_impl/nodes/model/patch/stable_diffusion.py +21 -0
  133. comfy_ui_api_workflow/_impl/nodes/model/patch/supir.py +57 -0
  134. comfy_ui_api_workflow/_impl/nodes/model/patch/unet.py +281 -0
  135. comfy_ui_api_workflow/_impl/nodes/model/patch/wan.py +143 -0
  136. comfy_ui_api_workflow/_impl/nodes/model/sampling/__init__.py +228 -0
  137. comfy_ui_api_workflow/_impl/nodes/model/sampling/custom.py +123 -0
  138. comfy_ui_api_workflow/_impl/nodes/model/sampling/guiders.py +252 -0
  139. comfy_ui_api_workflow/_impl/nodes/model/sampling/noise.py +52 -0
  140. comfy_ui_api_workflow/_impl/nodes/model/sampling/samplers.py +531 -0
  141. comfy_ui_api_workflow/_impl/nodes/model/sampling/schedulers.py +462 -0
  142. comfy_ui_api_workflow/_impl/nodes/model/sampling/sigmas.py +163 -0
  143. comfy_ui_api_workflow/_impl/nodes/model/training.py +249 -0
  144. comfy_ui_api_workflow/_impl/nodes/partner/__init__.py +3 -0
  145. comfy_ui_api_workflow/_impl/nodes/partner/audio/__init__.py +3 -0
  146. comfy_ui_api_workflow/_impl/nodes/partner/audio/elevenlabs.py +47 -0
  147. comfy_ui_api_workflow/_impl/nodes/partner/image/__init__.py +3 -0
  148. comfy_ui_api_workflow/_impl/nodes/partner/image/bytedance.py +16 -0
  149. comfy_ui_api_workflow/_impl/nodes/partner/image/krea.py +31 -0
  150. comfy_ui_api_workflow/_impl/nodes/partner/image/luma.py +27 -0
  151. comfy_ui_api_workflow/_impl/nodes/partner/image/recraft.py +220 -0
  152. comfy_ui_api_workflow/_impl/nodes/partner/text/__init__.py +3 -0
  153. comfy_ui_api_workflow/_impl/nodes/partner/text/gemini.py +25 -0
  154. comfy_ui_api_workflow/_impl/nodes/partner/text/openai.py +77 -0
  155. comfy_ui_api_workflow/_impl/nodes/partner/video/__init__.py +3 -0
  156. comfy_ui_api_workflow/_impl/nodes/partner/video/bytedance.py +16 -0
  157. comfy_ui_api_workflow/_impl/nodes/partner/video/pixverse.py +27 -0
  158. comfy_ui_api_workflow/_impl/nodes/text.py +506 -0
  159. comfy_ui_api_workflow/_impl/nodes/three_d/__init__.py +563 -0
  160. comfy_ui_api_workflow/_impl/nodes/three_d/mesh.py +183 -0
  161. comfy_ui_api_workflow/_impl/nodes/three_d/splat.py +262 -0
  162. comfy_ui_api_workflow/_impl/nodes/three_d/texturing.py +254 -0
  163. comfy_ui_api_workflow/_impl/nodes/utilities/__init__.py +404 -0
  164. comfy_ui_api_workflow/_impl/nodes/utilities/logic.py +44 -0
  165. comfy_ui_api_workflow/_impl/nodes/utilities/primitive.py +107 -0
  166. comfy_ui_api_workflow/_impl/nodes/validation.py +126 -0
  167. comfy_ui_api_workflow/_impl/nodes/video/__init__.py +440 -0
  168. comfy_ui_api_workflow/_impl/nodes/video/batch.py +45 -0
  169. comfy_ui_api_workflow/_impl/nodes/video/preprocessors.py +24 -0
  170. comfy_ui_api_workflow/_impl/nodes/video/transform.py +52 -0
  171. comfy_ui_api_workflow/_impl/pipeline.py +67 -0
  172. comfy_ui_api_workflow/_impl/workflow.py +539 -0
  173. comfy_ui_api_workflow-0.0.1.dist-info/METADATA +123 -0
  174. comfy_ui_api_workflow-0.0.1.dist-info/RECORD +176 -0
  175. comfy_ui_api_workflow-0.0.1.dist-info/WHEEL +4 -0
  176. comfy_ui_api_workflow-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,440 @@
1
+ from typing import Any, Literal, get_args
2
+
3
+ from ...errors.node import InvalidNodeParameterValue
4
+ from ..base import BaseNode
5
+ from ..validation import (
6
+ validate_bool,
7
+ validate_choice,
8
+ validate_not_empty,
9
+ validate_range,
10
+ )
11
+ from ...lazy import submodules_of
12
+
13
+
14
+ class LoadVideoDataSetFromFolder(BaseNode):
15
+ __slots__ = ("folder",)
16
+ CATEGORY = "video"
17
+ OUTPUTS = {"videos": 0}
18
+ RETURN_TYPES = ("VIDEO",)
19
+ INPUT_SOCKET_TYPES = {"folder": "COMBO"}
20
+
21
+ def __init__(self, node_id: str, folder: str | tuple[str, int]) -> None:
22
+ super().__init__(node_id=node_id)
23
+ self.folder = folder
24
+ self.__validate()
25
+
26
+ def __validate(self) -> None:
27
+ validate_not_empty("folder", self.folder)
28
+
29
+
30
+ class LoadVideoTextDataSetFromFolder(BaseNode):
31
+ __slots__ = ("folder",)
32
+ CATEGORY = "video"
33
+ OUTPUTS = {"videos": 0, "texts": 1}
34
+ RETURN_TYPES = ("VIDEO", "STRING")
35
+ INPUT_SOCKET_TYPES = {"folder": "COMBO"}
36
+
37
+ def __init__(self, node_id: str, folder: str | tuple[str, int]) -> None:
38
+ super().__init__(node_id=node_id)
39
+ self.folder = folder
40
+ self.__validate()
41
+
42
+ def __validate(self) -> None:
43
+ validate_not_empty("folder", self.folder)
44
+
45
+
46
+ VideoFrameSample_Strategies = Literal["uniform", "head", "tail", "random"]
47
+ VIDEOFRAMESAMPLE_STRATEGIES = frozenset(get_args(VideoFrameSample_Strategies))
48
+
49
+
50
+ class VideoFrameSample(BaseNode):
51
+ __slots__ = ("video", "num_frames", "strategy", "seed")
52
+ CATEGORY = "video"
53
+ OUTPUTS = {"video": 0}
54
+ RETURN_TYPES = ("VIDEO",)
55
+ INPUT_SOCKET_TYPES = {
56
+ "video": "VIDEO",
57
+ "num_frames": "INT",
58
+ "strategy": "COMBO",
59
+ "seed": "INT",
60
+ }
61
+
62
+ def __init__(
63
+ self,
64
+ node_id: str,
65
+ video: tuple[str, int],
66
+ num_frames: int | tuple[str, int] = 16,
67
+ strategy: VideoFrameSample_Strategies | tuple[str, int] = "uniform",
68
+ seed: int | tuple[str, int] = 0,
69
+ ) -> None:
70
+ super().__init__(node_id=node_id)
71
+ self.video = video
72
+ self.num_frames = num_frames
73
+ self.strategy = strategy
74
+ self.seed = seed
75
+ self.__validate()
76
+
77
+ def __validate(self) -> None:
78
+ validate_range("num_frames", self.num_frames, 1, 9999)
79
+ validate_choice("strategy", self.strategy, VIDEOFRAMESAMPLE_STRATEGIES)
80
+ validate_range("seed", self.seed, 0, 18_446_744_073_709_551_615)
81
+
82
+
83
+ SaveWEBM_Codecs = Literal["vp9", "av1"]
84
+ SAVEWEBM_CODECS = frozenset(get_args(SaveWEBM_Codecs))
85
+
86
+
87
+ class SaveWEBM(BaseNode):
88
+ __slots__ = ("images", "codec", "filename_prefix", "fps", "crf")
89
+ CATEGORY = "video"
90
+ OUTPUTS = {"images": 0}
91
+ RETURN_TYPES = ("IMAGE",)
92
+ INPUT_SOCKET_TYPES = {
93
+ "images": "IMAGE",
94
+ "filename_prefix": "STRING",
95
+ "codec": "COMBO",
96
+ "fps": "FLOAT",
97
+ "crf": "FLOAT",
98
+ }
99
+
100
+ def __init__(
101
+ self,
102
+ node_id: str,
103
+ images: tuple[str, int],
104
+ codec: SaveWEBM_Codecs | tuple[str, int],
105
+ filename_prefix: str | tuple[str, int] = "ComfyUI",
106
+ fps: float | tuple[str, int] = 24.0,
107
+ crf: float | tuple[str, int] = 32.0,
108
+ ) -> None:
109
+ super().__init__(node_id=node_id)
110
+ self.images = images
111
+ self.codec = codec
112
+ self.filename_prefix = filename_prefix
113
+ self.fps = fps
114
+ self.crf = crf
115
+ self.__validate()
116
+
117
+ def __validate(self) -> None:
118
+ validate_choice("codec", self.codec, SAVEWEBM_CODECS)
119
+ validate_range("fps", self.fps, 0.01, 1000.0)
120
+ validate_range("crf", self.crf, 0, 63.0)
121
+
122
+
123
+ CreateVideo_Bit_Depths = Literal["auto", 8, 10]
124
+ CreateVideo_Color_Spaces = Literal["sRGB", "HDR", "HDR PQ"]
125
+ CreateVideo_Codecs = Literal["none", "auto", "h264", "av1"]
126
+ CREATEVIDEO_BIT_DEPTHS = frozenset(get_args(CreateVideo_Bit_Depths))
127
+ CREATEVIDEO_COLOR_SPACES = frozenset(get_args(CreateVideo_Color_Spaces))
128
+ CREATEVIDEO_CODECS = frozenset(get_args(CreateVideo_Codecs))
129
+
130
+
131
+ class CreateVideo(BaseNode):
132
+ __slots__ = ("images", "fps", "audio", "bit_depth", "color_space", "codec")
133
+ CATEGORY = "video"
134
+ OUTPUTS = {"VIDEO": 0}
135
+ RETURN_TYPES = ("VIDEO",)
136
+ INPUT_SOCKET_TYPES = {
137
+ "images": "IMAGE",
138
+ "fps": "FLOAT",
139
+ "audio": "AUDIO",
140
+ "bit_depth": "COMBO",
141
+ "color_space": "COMBO",
142
+ "codec": "COMBO",
143
+ }
144
+
145
+ def __init__(
146
+ self,
147
+ node_id: str,
148
+ images: tuple[str, int],
149
+ fps: float | tuple[str, int] = 30.0,
150
+ audio: tuple[str, int] | None = None,
151
+ bit_depth: CreateVideo_Bit_Depths | tuple[str, int] = "auto",
152
+ color_space: CreateVideo_Color_Spaces | tuple[str, int] = "sRGB",
153
+ codec: CreateVideo_Codecs | tuple[str, int] = "none",
154
+ ) -> None:
155
+ super().__init__(node_id=node_id)
156
+ self.images = images
157
+ self.fps = fps
158
+ if audio is not None:
159
+ self.audio = audio
160
+ self.bit_depth = bit_depth
161
+ self.color_space = color_space
162
+ self.codec = codec
163
+ self.__validate()
164
+
165
+ def __validate(self) -> None:
166
+ validate_range("fps", self.fps, 1.0, 120.0)
167
+ validate_choice("bit_depth", self.bit_depth, CREATEVIDEO_BIT_DEPTHS)
168
+ validate_choice("color_space", self.color_space, CREATEVIDEO_COLOR_SPACES)
169
+ validate_choice("codec", self.codec, CREATEVIDEO_CODECS)
170
+
171
+
172
+ class GetVideoComponents(BaseNode):
173
+ __slots__ = ("video",)
174
+ CATEGORY = "video"
175
+ OUTPUTS = {"images": 0, "audio": 1, "fps": 2, "bit_depth": 3, "color_space": 4}
176
+ RETURN_TYPES = ("IMAGE", "AUDIO", "FLOAT", "COMBO", "COMBO")
177
+ INPUT_SOCKET_TYPES = {"video": "VIDEO"}
178
+
179
+ def __init__(self, node_id: str, video: tuple[str, int]) -> None:
180
+ super().__init__(node_id=node_id)
181
+ self.video = video
182
+
183
+
184
+ class LoadVideo(BaseNode):
185
+ __slots__ = ("file",)
186
+ CATEGORY = "video"
187
+ OUTPUTS = {"VIDEO": 0}
188
+ RETURN_TYPES = ("VIDEO",)
189
+ INPUT_SOCKET_TYPES = {"file": "COMBO"}
190
+
191
+ def __init__(self, node_id: str, file: str | tuple[str, int]) -> None:
192
+ super().__init__(node_id=node_id)
193
+ self.file = file
194
+ self.__validate()
195
+
196
+ def __validate(self) -> None:
197
+ validate_not_empty("file", self.file)
198
+
199
+
200
+ class VideoSlice(BaseNode):
201
+ __slots__ = ("video", "start_time", "duration", "strict_duration")
202
+ CATEGORY = "video"
203
+ CLASS_TYPE = "Video Slice"
204
+ OUTPUTS = {"VIDEO": 0}
205
+ RETURN_TYPES = ("VIDEO",)
206
+ INPUT_SOCKET_TYPES = {
207
+ "video": "VIDEO",
208
+ "start_time": "FLOAT",
209
+ "duration": "FLOAT",
210
+ "strict_duration": "BOOLEAN",
211
+ }
212
+
213
+ def __init__(
214
+ self,
215
+ node_id: str,
216
+ video: tuple[str, int],
217
+ start_time: float | tuple[str, int] = 0.0,
218
+ duration: float | tuple[str, int] = 0.0,
219
+ strict_duration: bool | tuple[str, int] = False,
220
+ ) -> None:
221
+ super().__init__(node_id=node_id)
222
+ self.video = video
223
+ self.start_time = start_time
224
+ self.duration = duration
225
+ self.strict_duration = strict_duration
226
+ self.__validate()
227
+
228
+ def __validate(self) -> None:
229
+ validate_range("start_time", self.start_time, -100000.0, 100000.0)
230
+ validate_range("duration", self.duration, 0.0)
231
+ validate_bool("strict_duration", self.strict_duration)
232
+
233
+
234
+ class VideoTrim(BaseNode):
235
+ __slots__ = ("video", "trim", "strict_duration")
236
+ CATEGORY = "video"
237
+ OUTPUTS = {"VIDEO": 0}
238
+ RETURN_TYPES = ("VIDEO",)
239
+ INPUT_SOCKET_TYPES = {
240
+ "video": "VIDEO",
241
+ "trim": "VIDEO_EDIT",
242
+ "strict_duration": "BOOLEAN",
243
+ }
244
+
245
+ def __init__(
246
+ self,
247
+ node_id: str,
248
+ video: tuple[str, int],
249
+ trim: Any = None,
250
+ strict_duration: bool | tuple[str, int] = False,
251
+ ) -> None:
252
+ super().__init__(node_id=node_id)
253
+ self.video = video
254
+ self.trim = (
255
+ {"trim": {"start_time": 0.0, "duration": 0.0}} if trim is None else trim
256
+ )
257
+ self.strict_duration = strict_duration
258
+ self.__validate()
259
+
260
+ def __validate(self) -> None:
261
+ validate_bool("strict_duration", self.strict_duration)
262
+
263
+
264
+ class VideoCrop(BaseNode):
265
+ __slots__ = ("video", "crop")
266
+ CATEGORY = "video"
267
+ OUTPUTS = {"VIDEO": 0}
268
+ RETURN_TYPES = ("VIDEO",)
269
+ INPUT_SOCKET_TYPES = {"video": "VIDEO", "crop": "VIDEO_EDIT"}
270
+
271
+ def __init__(self, node_id: str, video: tuple[str, int], crop: Any = None) -> None:
272
+ super().__init__(node_id=node_id)
273
+ self.video = video
274
+ self.crop = (
275
+ {"crop": {"x": 0, "y": 0, "width": 0, "height": 0}}
276
+ if crop is None
277
+ else crop
278
+ )
279
+
280
+
281
+ class FrameInterpolate(BaseNode):
282
+ __slots__ = ("interp_model", "images", "multiplier")
283
+ CATEGORY = "video"
284
+ OUTPUTS = {"IMAGE": 0}
285
+ RETURN_TYPES = ("IMAGE",)
286
+ INPUT_SOCKET_TYPES = {
287
+ "interp_model": "INTERP_MODEL",
288
+ "images": "IMAGE",
289
+ "multiplier": "INT",
290
+ }
291
+
292
+ def __init__(
293
+ self,
294
+ node_id: str,
295
+ interp_model: tuple[str, int],
296
+ images: tuple[str, int],
297
+ multiplier: int | tuple[str, int] = 2,
298
+ ) -> None:
299
+ super().__init__(node_id=node_id)
300
+ self.interp_model = interp_model
301
+ self.images = images
302
+ self.multiplier = multiplier
303
+ self.__validate()
304
+
305
+ def __validate(self) -> None:
306
+ validate_range("multiplier", self.multiplier, 2, 16)
307
+
308
+
309
+ SaveVideo_Formats = Literal["auto", "mp4", "mkv", "webm"]
310
+ SaveVideo_Codecs = Literal["auto", "h264", "av1"]
311
+ SaveVideo_Encodings = Literal["auto", "re-encode"]
312
+ SAVEVIDEO_FORMATS = frozenset(get_args(SaveVideo_Formats))
313
+ SAVEVIDEO_CODECS = frozenset(get_args(SaveVideo_Codecs))
314
+ SAVEVIDEO_ENCODINGS = frozenset(get_args(SaveVideo_Encodings))
315
+ # The codecs each format offers and the crf range of each codec, as in object_info.
316
+ SAVEVIDEO_FORMAT_CODECS = {
317
+ "auto": SAVEVIDEO_CODECS,
318
+ "mp4": SAVEVIDEO_CODECS,
319
+ "mkv": SAVEVIDEO_CODECS,
320
+ "webm": frozenset({"auto", "av1"}),
321
+ }
322
+ SAVEVIDEO_CRF_RANGES = {"h264": (0.0, 51.0), "av1": (0.0, 63.0)}
323
+
324
+
325
+ def _validate_codec_branch(prefix: str, codec: Any, encoding: Any, crf: Any) -> None:
326
+ # Shared by the format.codec branch and the flat codec group of SaveVideo:
327
+ # encoding only exists for h264 and av1, and crf only for "re-encode".
328
+ encoding_name = f"{prefix}_encoding"
329
+ crf_name = f"{encoding_name}_crf"
330
+ if encoding is not None and codec not in SAVEVIDEO_CRF_RANGES:
331
+ raise InvalidNodeParameterValue(
332
+ f"{encoding_name} only applies when {prefix} is h264 or av1, not {codec!r}"
333
+ )
334
+ if encoding is not None:
335
+ validate_choice(encoding_name, encoding, SAVEVIDEO_ENCODINGS)
336
+ if encoding == "re-encode":
337
+ if crf is None:
338
+ raise InvalidNodeParameterValue(
339
+ f"{crf_name} is required when {encoding_name} is 're-encode'"
340
+ )
341
+ validate_range(crf_name, crf, *SAVEVIDEO_CRF_RANGES[codec])
342
+ elif crf is not None:
343
+ raise InvalidNodeParameterValue(
344
+ f"{crf_name} only applies when {encoding_name} is 're-encode'"
345
+ )
346
+
347
+
348
+ # The dynamic combo "format" opens "format.codec", then "format.codec.encoding",
349
+ # then "format.codec.encoding.crf"; each level is an attribute sent with its
350
+ # dotted name. The flat "codec" group follows the same tree.
351
+ class SaveVideo(BaseNode):
352
+ __slots__ = (
353
+ "video",
354
+ "filename_prefix",
355
+ "format",
356
+ "format_codec",
357
+ "format_codec_encoding",
358
+ "format_codec_encoding_crf",
359
+ "codec",
360
+ "codec_encoding",
361
+ "codec_encoding_crf",
362
+ )
363
+ CATEGORY = "video"
364
+ OUTPUTS = {"video": 0}
365
+ RETURN_TYPES = ("VIDEO",)
366
+ INPUT_NAMES = {
367
+ "format_codec": "format.codec",
368
+ "format_codec_encoding": "format.codec.encoding",
369
+ "format_codec_encoding_crf": "format.codec.encoding.crf",
370
+ "codec_encoding": "codec.encoding",
371
+ "codec_encoding_crf": "codec.encoding.crf",
372
+ }
373
+ INPUT_SOCKET_TYPES = {
374
+ "video": "VIDEO",
375
+ "filename_prefix": "STRING",
376
+ "format": "COMFY_DYNAMICCOMBO_V3",
377
+ "format_codec": "COMFY_DYNAMICCOMBO_V3",
378
+ "format_codec_encoding": "COMFY_DYNAMICCOMBO_V3",
379
+ "format_codec_encoding_crf": "FLOAT",
380
+ "codec": "COMFY_DYNAMICCOMBO_V3",
381
+ "codec_encoding": "COMFY_DYNAMICCOMBO_V3",
382
+ "codec_encoding_crf": "FLOAT",
383
+ }
384
+
385
+ def __init__(
386
+ self,
387
+ node_id: str,
388
+ video: tuple[str, int],
389
+ filename_prefix: str | tuple[str, int] = "video/ComfyUI",
390
+ format: SaveVideo_Formats = "auto",
391
+ format_codec: SaveVideo_Codecs = "auto",
392
+ format_codec_encoding: SaveVideo_Encodings | None = None,
393
+ format_codec_encoding_crf: float | tuple[str, int] | None = None,
394
+ codec: SaveVideo_Codecs | None = None,
395
+ codec_encoding: SaveVideo_Encodings | None = None,
396
+ codec_encoding_crf: float | tuple[str, int] | None = None,
397
+ ) -> None:
398
+ super().__init__(node_id=node_id)
399
+ self.video = video
400
+ self.filename_prefix = filename_prefix
401
+ self.format = format
402
+ self.format_codec = format_codec
403
+ if format_codec_encoding is not None:
404
+ self.format_codec_encoding = format_codec_encoding
405
+ if format_codec_encoding_crf is not None:
406
+ self.format_codec_encoding_crf = format_codec_encoding_crf
407
+ if codec is not None:
408
+ self.codec = codec
409
+ if codec_encoding is not None:
410
+ self.codec_encoding = codec_encoding
411
+ if codec_encoding_crf is not None:
412
+ self.codec_encoding_crf = codec_encoding_crf
413
+ self.__validate()
414
+
415
+ def __validate(self) -> None:
416
+ validate_choice("format", self.format, SAVEVIDEO_FORMATS)
417
+ codecs = SAVEVIDEO_FORMAT_CODECS[self.format]
418
+ if self.format_codec not in codecs:
419
+ raise InvalidNodeParameterValue(
420
+ f"format_codec {self.format_codec!r} is not available for format "
421
+ f"{self.format!r}; use one of {sorted(codecs)}"
422
+ )
423
+ _validate_codec_branch(
424
+ "format_codec",
425
+ self.format_codec,
426
+ getattr(self, "format_codec_encoding", None),
427
+ getattr(self, "format_codec_encoding_crf", None),
428
+ )
429
+ codec = getattr(self, "codec", None)
430
+ if codec is not None:
431
+ validate_choice("codec", codec, SAVEVIDEO_CODECS)
432
+ _validate_codec_branch(
433
+ "codec",
434
+ codec,
435
+ getattr(self, "codec_encoding", None),
436
+ getattr(self, "codec_encoding_crf", None),
437
+ )
438
+
439
+
440
+ __getattr__ = submodules_of(__name__)
@@ -0,0 +1,45 @@
1
+ from ..base import BaseNode
2
+ from ..validation import validate_range
3
+
4
+
5
+ class ShuffleVideoDataset(BaseNode):
6
+ __slots__ = ("videos", "seed")
7
+ CATEGORY = "video/batch"
8
+ OUTPUTS = {"videos": 0}
9
+ RETURN_TYPES = ("VIDEO",)
10
+ INPUT_SOCKET_TYPES = {"videos": "VIDEO", "seed": "INT"}
11
+
12
+ def __init__(
13
+ self, node_id: str, videos: tuple[str, int], seed: int | tuple[str, int] = 0
14
+ ) -> None:
15
+ super().__init__(node_id=node_id)
16
+ self.videos = videos
17
+ self.seed = seed
18
+ self.__validate()
19
+
20
+ def __validate(self) -> None:
21
+ validate_range("seed", self.seed, 0, 18_446_744_073_709_551_615)
22
+
23
+
24
+ class ShuffleVideoTextDataset(BaseNode):
25
+ __slots__ = ("videos", "texts", "seed")
26
+ CATEGORY = "video/batch"
27
+ OUTPUTS = {"videos": 0, "texts": 1}
28
+ RETURN_TYPES = ("VIDEO", "STRING")
29
+ INPUT_SOCKET_TYPES = {"videos": "VIDEO", "texts": "STRING", "seed": "INT"}
30
+
31
+ def __init__(
32
+ self,
33
+ node_id: str,
34
+ videos: tuple[str, int],
35
+ texts: str | tuple[str, int],
36
+ seed: int | tuple[str, int] = 0,
37
+ ) -> None:
38
+ super().__init__(node_id=node_id)
39
+ self.videos = videos
40
+ self.texts = texts
41
+ self.seed = seed
42
+ self.__validate()
43
+
44
+ def __validate(self) -> None:
45
+ validate_range("seed", self.seed, 0, 18_446_744_073_709_551_615)
@@ -0,0 +1,24 @@
1
+ from ..base import BaseNode
2
+ from ..validation import validate_range
3
+
4
+
5
+ class LTXVPreprocess(BaseNode):
6
+ __slots__ = ("image", "img_compression")
7
+ CATEGORY = "video/preprocessors"
8
+ OUTPUTS = {"output_image": 0}
9
+ RETURN_TYPES = ("IMAGE",)
10
+ INPUT_SOCKET_TYPES = {"image": "IMAGE", "img_compression": "INT"}
11
+
12
+ def __init__(
13
+ self,
14
+ node_id: str,
15
+ image: tuple[str, int],
16
+ img_compression: int | tuple[str, int] = 35,
17
+ ) -> None:
18
+ super().__init__(node_id=node_id)
19
+ self.image = image
20
+ self.img_compression = img_compression
21
+ self.__validate()
22
+
23
+ def __validate(self) -> None:
24
+ validate_range("img_compression", self.img_compression, 0, 100)
@@ -0,0 +1,52 @@
1
+ from ..base import BaseNode
2
+ from ..validation import validate_range
3
+
4
+
5
+ class VideoTemporalCrop(BaseNode):
6
+ __slots__ = ("video", "start_frame", "length")
7
+ CATEGORY = "video/transform"
8
+ OUTPUTS = {"video": 0}
9
+ RETURN_TYPES = ("VIDEO",)
10
+ INPUT_SOCKET_TYPES = {"video": "VIDEO", "start_frame": "INT", "length": "INT"}
11
+
12
+ def __init__(
13
+ self,
14
+ node_id: str,
15
+ video: tuple[str, int],
16
+ start_frame: int | tuple[str, int] = 0,
17
+ length: int | tuple[str, int] = 16,
18
+ ) -> None:
19
+ super().__init__(node_id=node_id)
20
+ self.video = video
21
+ self.start_frame = start_frame
22
+ self.length = length
23
+ self.__validate()
24
+
25
+ def __validate(self) -> None:
26
+ validate_range("start_frame", self.start_frame, 0, 99_999)
27
+ validate_range("length", self.length, 1, 99_999)
28
+
29
+
30
+ class VideoRandomTemporalCrop(BaseNode):
31
+ __slots__ = ("video", "length", "seed")
32
+ CATEGORY = "video/transform"
33
+ OUTPUTS = {"video": 0}
34
+ RETURN_TYPES = ("VIDEO",)
35
+ INPUT_SOCKET_TYPES = {"video": "VIDEO", "length": "INT", "seed": "INT"}
36
+
37
+ def __init__(
38
+ self,
39
+ node_id: str,
40
+ video: tuple[str, int],
41
+ length: int | tuple[str, int] = 16,
42
+ seed: int | tuple[str, int] = 0,
43
+ ) -> None:
44
+ super().__init__(node_id=node_id)
45
+ self.video = video
46
+ self.length = length
47
+ self.seed = seed
48
+ self.__validate()
49
+
50
+ def __validate(self) -> None:
51
+ validate_range("length", self.length, 1, 99_999)
52
+ validate_range("seed", self.seed, 0, 18_446_744_073_709_551_615)
@@ -0,0 +1,67 @@
1
+ from typing import TYPE_CHECKING, Any, TypeVar
2
+
3
+ from .errors.node import quote_names
4
+ from .errors.output import OutputNotFoundError
5
+ from .errors.pipeline import PipelineError
6
+ from .nodes.base import BaseNode
7
+
8
+ if TYPE_CHECKING:
9
+ from .workflow import Workflow
10
+
11
+ NodeType = TypeVar("NodeType", bound=BaseNode)
12
+
13
+
14
+ class PipelineOutput:
15
+ __slots__ = ("pipeline", "outputs")
16
+
17
+ def __init__(self, pipeline: str, outputs: dict[str, tuple[str, int]]) -> None:
18
+ self.pipeline = pipeline
19
+ self.outputs = outputs
20
+
21
+ def __getitem__(self, name: str) -> tuple[str, int]:
22
+ if name in self.outputs:
23
+ return self.outputs[name]
24
+ message = f"Pipeline {self.pipeline!r} has no output {name!r}."
25
+ if self.outputs:
26
+ message += f" Its outputs are: {quote_names(list(self.outputs))}."
27
+ raise OutputNotFoundError(message)
28
+
29
+
30
+ class Pipeline:
31
+ def __init__(self) -> None:
32
+ self.nodes: dict[str, BaseNode] = {}
33
+ self.__workflow: "Workflow | None" = None
34
+ self.__outputs: dict[str, tuple[str, int]] = {}
35
+ self.__rejection: tuple[int, type, Exception] | None = None
36
+
37
+ @property
38
+ def output(self) -> PipelineOutput:
39
+ return PipelineOutput(type(self).__name__, self.__outputs)
40
+
41
+ def build(self, **parameters: Any) -> dict[str, tuple[str, int]] | None:
42
+ raise NotImplementedError
43
+
44
+ def add_node(self, node_type: type[NodeType], **kwargs: Any) -> NodeType:
45
+ if self.__workflow is None:
46
+ reason = (
47
+ "add_node only works inside build, "
48
+ "while Workflow.add_pipeline adds the pipeline"
49
+ )
50
+ raise PipelineError(type(self).__name__, reason)
51
+ try:
52
+ node = self.__workflow.add_node(node_type, **kwargs)
53
+ except Exception as error:
54
+ if self.__rejection is None:
55
+ self.__rejection = (len(self.nodes) + 1, node_type, error)
56
+ raise
57
+ self.nodes[node.node_id] = node
58
+ return node
59
+
60
+ def _bind(self, workflow: "Workflow | None") -> None:
61
+ self.__workflow = workflow
62
+
63
+ def _rejection(self) -> tuple[int, type, Exception] | None:
64
+ return self.__rejection
65
+
66
+ def _set_outputs(self, outputs: dict[str, tuple[str, int]]) -> None:
67
+ self.__outputs = outputs