83 Commits
Author SHA1 Message Date
yanlang0123 1fe5f0b67f 循环嵌套优化 2024-08-17 13:57:22 +08:00
yanlang0123 43ecd83262 嵌套内循环问题修改 2024-08-17 00:01:00 +08:00
yanlang0123 357ccd2b43 计次循环优化升级 2024-08-16 16:04:45 +08:00
yanlang0123 ba7194836a update js/litegraphCore.js.
中文搜索闪退问题修改

Signed-off-by: yanlang0123 <yanlang0123@gmail.com>
2024-08-12 05:33:51 +00:00
严浪 7e88221dfc 添加漏的包 2024-07-14 15:47:27 +08:00
严浪 17a4350230 获取不到插件路径问题修改 2024-07-14 15:40:35 +08:00
严浪 cc16c74e2e 说明修改 2024-07-04 17:15:29 +08:00
严浪 6c841b9785 xiug 2024-07-04 16:46:04 +08:00
严浪 5bb167364c 文件自动修改,多余文件删除 2024-07-04 16:07:07 +08:00
严浪 7295cd63ca redis实现集群负载 2024-07-03 19:16:36 +08:00
严浪 9455d5d2d8 功能优化 2024-05-28 17:02:49 +08:00
严浪 b6a580dc64 功能优化 2024-05-28 16:52:29 +08:00
严浪 ce7571a0a8 新增判断循环功能,功能优化BUG修改 2024-05-27 10:44:39 +08:00
严浪 2e23311d93 优化修改 2024-05-22 14:30:08 +08:00
严浪 d3c4a7f5a7 备注修改 2024-05-19 22:27:04 +08:00
严浪 a5b86e8295 功能优化,添加部分节点使用说明 2024-05-19 22:21:38 +08:00
严浪 86fd7a155b 宽高自适应 2024-05-17 09:34:48 +08:00
严浪 a87a5b69c5 姿态编辑部添加提示词填写 2024-05-15 17:36:47 +08:00
严浪 5c86e1db4e 功能优化 2024-05-15 17:33:40 +08:00
严浪 c4d1d89216 功能优化修改 2024-05-10 11:37:31 +08:00
严浪 a60f88e1c7 修改说明 2024-05-05 15:00:56 +08:00
严浪 67ab97db66 插件优化升级 2024-05-03 21:50:03 +08:00
严浪 5ae8f15cd1 新增多IPAdapter多参考图遮罩功能节点 2024-04-24 22:10:54 +08:00
严浪 70b758753a 修改名称 2024-04-23 14:23:00 +08:00
严浪 c1b3cb7a49 功能优化 2024-04-23 09:02:16 +08:00
严浪 5e67c1ad70 BUG修改 2024-04-22 16:42:32 +08:00
严浪 1ec31bc90d 微信公众号应用开发,支持微信公众号,下发指令生图,和网页版 2024-04-21 17:24:39 +08:00
yanlang0123 396dd1a2cb !21 add tags/角色列表.yml.
Merge pull request !21 from 吴嘚嘚/N/A
2024-04-16 07:26:05 +00:00
严浪 e251f21a31 BUG修改 2024-04-16 13:56:16 +08:00
严浪 7f28088ffc BUG修改 2024-04-14 15:59:53 +08:00
严浪 877ff5df9e 修改循环下标 2024-04-13 16:57:57 +08:00
严浪 60c66b5ada 新增节点 功能优化 2024-04-13 13:13:28 +08:00
严浪 bd24491d8f BUG修改 2024-04-08 15:00:40 +08:00
严浪 5d360d1801 修改execution.py说明 2024-04-08 09:32:34 +08:00
严浪 fa6888994e 循环插件修改,风格节点优化 2024-04-08 09:19:20 +08:00
严浪 ac46a7161c 添加姿态编辑配套节点,修改BUG 2024-04-06 22:19:35 +08:00
吴嘚嘚 aebc08230e add tags/角色列表.yml.
添加animagine-xl-v3.1的角色列表,不过是基本没翻译的

Signed-off-by: 吴嘚嘚 <13742125+wu-deidei@user.noreply.gitee.com>
2024-04-06 07:10:57 +00:00
严浪 84693474b5 Merge branch 'cu121' of https://gitee.com/yanlang0123/ComfyUI_Lam into cu121 2024-04-04 14:34:29 +08:00
严浪 6c1f6cc1a1 删除多余按钮 2024-04-04 14:34:24 +08:00
yanlang0123 230b5cc340 删除文件 tags/十八禁.yml 2024-04-01 05:51:12 +00:00
严浪 b482183255 修改BUG 2024-04-01 13:45:21 +08:00
严浪 dcb89c0080 姿态添加手 2024-04-01 13:41:15 +08:00
严浪 e41bfc5b15 功能优化 2024-03-29 16:15:23 +08:00
严浪 2999d2ee9d 功能优化 2024-03-29 14:55:50 +08:00
严浪 c89f08d49c 修改目录 2024-03-22 16:35:24 +08:00
严浪 332b2315cb 脸部识别 2024-03-22 11:09:38 +08:00
严浪 779223ead3 多余删除 2024-03-22 11:07:20 +08:00
严浪 1e4f7ec11e 添加姿态编辑节点 2024-03-22 11:06:46 +08:00
严浪 5a90359bc1 插件升级 2024-03-09 21:24:05 +08:00
yanlang0123 c173a7bcba !16 提示词选择改成可操作权重
Merge pull request !16 from 吴嘚嘚/N/A ,感谢PR
2024-03-07 09:30:05 +00:00
严浪 adc20028c1 添加使用说明 2024-03-07 17:17:53 +08:00
严浪 4cc6621277 添加内循环插件,及几个辅助插件 2024-03-06 18:19:31 +08:00
吴嘚嘚 3ed6e5292a 提示词选择改成可操作权重
Signed-off-by: 吴嘚嘚 <13742125+wu-deidei@user.noreply.gitee.com>
2024-03-05 07:18:15 +00:00
严浪 ceeef4bd9f 文字修改 2024-03-04 15:07:22 +08:00
严浪 f1a5316b91 功能优化 2024-03-04 14:37:20 +08:00
严浪 60e12c34f3 多GLIGEN文本框应用 2024-02-26 15:09:23 +08:00
严浪 714b4d3285 BUG修改 2024-01-26 22:19:20 +08:00
严浪 f684744205 优化 2024-01-19 15:24:40 +08:00
严浪 a5e39a5145 插件优化 2024-01-19 13:32:06 +08:00
严浪 495b315eeb 插件升级,添加百度翻译和本地翻译 2024-01-18 22:25:16 +08:00
严浪 2d2536b9bc 风格选择示例效果展示 2024-01-15 09:28:45 +08:00
严浪 69d83f27be s 2024-01-02 15:24:47 +08:00
严浪 56918fbe42 去除打印代码 2024-01-02 15:23:18 +08:00
严浪 7310adcc94 说明修改 2023-12-23 16:18:06 +08:00
严浪 81b72434b6 中文搜索实现 2023-12-23 15:41:45 +08:00
严浪 28507d4444 功能优化 2023-12-16 23:06:40 +08:00
严浪 bb97be8746 功能优化 2023-12-15 20:33:41 +08:00
严浪 4797ed2435 组节点保存功能开发 2023-12-15 15:45:29 +08:00
严浪 b6bdba5fb2 功能优化 2023-12-08 22:05:12 +08:00
严浪 482628c701 BUG修改 2023-12-07 22:11:25 +08:00
严浪 89f359c5c8 修改部分风格没有prompt报错问题 2023-12-07 22:06:01 +08:00
严浪 fe9e459ba2 安装文件修改 2023-12-01 20:21:39 +08:00
严浪 3b59044c29 优化插件 2023-11-30 21:54:07 +08:00
严浪 d2d4c6aa56 样式修改 2023-11-29 13:15:04 +08:00
严浪 8a6a7872b5 功能优化修改 2023-11-29 12:15:39 +08:00
严浪 8ceae5ccf8 删除print 2023-11-28 17:31:28 +08:00
严浪 3d7e2707f2 新增风格选择工具,删除多余文件 2023-11-28 17:29:27 +08:00
严浪 dbfe688a89 删除多余文件 2023-11-26 16:16:50 +08:00
严浪 71822ca854 文件目录机构调整,添加提示词选择插件功能 2023-11-25 22:38:04 +08:00
yanlang0123 4c66b01a27 删除文件 py/__pycache__ 2023-11-23 00:47:15 +00:00
严浪 4f8904f665 修改说明 2023-11-22 10:35:44 +08:00
严浪 43e4cbf41c 为适应cu121新版本安装失败问题修改 2023-11-22 10:07:09 +08:00
yanlang0123 e050de7ed8 !1 合并修改
Merge pull request !1 from yanlang0123/master
2023-11-22 01:56:14 +00:00
671 changed files with 295267 additions and 28123 deletions
+21
View File
@@ -0,0 +1,21 @@
# Mac
.DS_Store
**/.DS_Store
# vim/vi
*.swp
# JavaScript
node_modules/
.node_modules/
.eslintcache
unpackage/dist/build/
unpackage/dist/dev/
# python
*.pyc
config/baidu.json
ckpts/*
config/lamWeChat.db
config/weChat.yaml
config/settings_nodes.json
+13 -34
View File
@@ -8,41 +8,20 @@ Download and place in the plugin directory of comfyUI, as shown below:
![Alt text](解压存放路径及名称.png)
##### Special Note for this version, execute the latest version "ComfyUI_windows_portable_nvidia_cu118_or_cpu.7z" with cu118 inside, the address is as follows:
https://github.com/comfyanonymous/ComfyUI/releases/download/latest/ComfyUI_windows_portable_nvidia_cu118_or_cpu.7z
##### Special Note: For this version, execute the latest version "ComfyUI_windows_portable_nvidia_cu121_or_cpu.7z" with cu121 inside. The version address is as follows:
https://github.com/comfyanonymous/ComfyUI/releases/download/latest/ComfyUI_windows_portable_nvidia_cu121_or_cpu.7z
1. Unzip according to the image to the specified plugin directory and name. Extract insightface.rar to the directory ..\ComfyUI_windows_portable\python_embeded\Lib\site-packages. Extract venv.rar to the directory ..\ComfyUI_windows_portable\python_embeded\Lib\site-packages. Then run the install.bat file, and if there are no errors, it's ready.
Then run the install.bat file, and if there are no errors, it's ready.
2. Model addresses and storage paths:
Lama model:
https://huggingface.co/lllyasviel/Annotators/resolve/main/ControlNetLama.pth ..\ComfyUI\models\lama\ControlNetLama.pth
SadTalker models:
https://github.com/OpenTalker/SadTalker/releases/download/v0.0.2-rc/mapping_00109-model.pth.tar ..\ComfyUI\models\SadTalker\mapping_00109-model.pth.tar
https://github.com/OpenTalker/SadTalker/releases/download/v0.0.2-rc/mapping_00229-model.pth.tar ..\ComfyUI\models\SadTalker\mapping_00229-model.pth.tar
https://github.com/OpenTalker/SadTalker/releases/download/v0.0.2-rc/SadTalker_V0.0.2_256.safetensors ..\ComfyUI\models\SadTalker\SadTalker_V0.0.2_256.safetensors
https://github.com/OpenTalker/SadTalker/releases/download/v0.0.2-rc/SadTalker_V0.0.2_512.safetensors ..\ComfyUI\models\SadTalker\SadTalker_V0.0.2_512.safetensors
1. Model addresses and storage paths:
- Lama model:
https://huggingface.co/lllyasviel/Annotators/resolve/main/ControlNetLama.pth ..\ComfyUI\models\lama\ControlNetLama.pth
3. Gfpgan is based on the startup directory, follow the prompts accordingly.
https://github.com/xinntao/facexlib/releases/download/v0.1.0/alignment_WFLW_4HG.pth ..\ComfyUI_windows_portable\gfpgan\weights\alignment_WFLW_4HG.pth
https://github.com/xinntao/facexlib/releases/download/v0.1.0/detection_Resnet50_Final.pth ..\ComfyUI_windows_portable\gfpgan\weights\detection_Resnet50_Final.pth
https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth ..\ComfyUI_windows_portable\gfpgan\weights\GFPGANv1.4.pth
https://github.com/xinntao/facexlib/releases/download/v0.2.2/parsing_parsenet.pth ..\ComfyUI_windows_portable\gfpgan\weights\parsing_parsenet.pth
2. Image-face-fusion model (Face swapping model)
- Link: https://pan.baidu.com/s/19DOgJQ_RHNAjfNrzSr2uTQ?pwd=gf0p
- Extraction Code: gf0p
- Extract to the directory ..\ComfyUI\models\image-face-fusion
4. Image-face-fusion model (Face swapping model)
Link: https://pan.baidu.com/s/19DOgJQ_RHNAjfNrzSr2uTQ?pwd=gf0p
Extraction Code: gf0p
Extract to the directory ..\ComfyUI\models\image-face-fusion
5. Roop-face-swap model (Face swapping model)
Link: https://pan.baidu.com/s/1cJeRtqgdeNW21Hljwuv-tA?pwd=b4yy
Extraction Code: b4yy
Extract to the directory ..\ComfyUI\models\roop-face-swap
6. Modify the execution.py file in the comfyUI root directory. Modify the content, search for "Return type mismatch between linked nodes," find the corresponding line, and replace
```python
if r[val[1]] != type_input:
```
with
```python
if type_input !="*" and r[val[1]] != type_input:
```
3. 执行修改脚本
windows 执行“修改文件.bat”文件,未报错后就可以了
linux 执行“修改文件.sh”文件,未报错后就可以了
+7 -76
View File
@@ -8,90 +8,21 @@
![Alt text](解压存放路径及名称.png)
##### 特别说明该版本执行最新版“ComfyUI_windows_portable_nvidia_cu118_or_cpu.7z” 里面带cu118的版本地址如下:
https://github.com/comfyanonymous/ComfyUI/releases/download/latest/ComfyUI_windows_portable_nvidia_cu118_or_cpu.7z
##### 特别说明该版本执行最新版“ComfyUI_windows_portable_nvidia_cu121_or_cpu.7z” 里面带cu121的版本地址如下:
https://github.com/comfyanonymous/ComfyUI/releases/download/latest/ComfyUI_windows_portable_nvidia_cu121_or_cpu.7z
1. 根据图片解压到指定插件目录及名称,
将insightface.rar解压到..\ComfyUI_windows_portable\python_embeded\Lib\site-packages目录
将venv.rar解压到..\ComfyUI_windows_portable\python_embeded\Lib\site-packages目录
然后运行install.bat文件,未报错后就可以了,
2. 模型地址,及存放路径:
1. 模型地址,及存放路径:
lama模型:
https://huggingface.co/lllyasviel/Annotators/resolve/main/ControlNetLama.pth ..\ComfyUI\models\lama\ControlNetLama.pth
SadTalker模型:
https://github.com/OpenTalker/SadTalker/releases/download/v0.0.2-rc/mapping_00109-model.pth.tar ..\ComfyUI\models\SadTalker\mapping_00109-model.pth.tar
https://github.com/OpenTalker/SadTalker/releases/download/v0.0.2-rc/mapping_00229-model.pth.tar ..\ComfyUI\models\SadTalker\mapping_00229-model.pth.tar
https://github.com/OpenTalker/SadTalker/releases/download/v0.0.2-rc/SadTalker_V0.0.2_256.safetensors ..\ComfyUI\models\SadTalker\SadTalker_V0.0.2_256.safetensors
https://github.com/OpenTalker/SadTalker/releases/download/v0.0.2-rc/SadTalker_V0.0.2_512.safetensors ..\ComfyUI\models\SadTalker\SadTalker_V0.0.2_512.safetensors
3. gfpgan是根据启动目录来的,根据提示对应
https://github.com/xinntao/facexlib/releases/download/v0.1.0/alignment_WFLW_4HG.pth ..\ComfyUI_windows_portable\gfpgan\weights\alignment_WFLW_4HG.pth
https://github.com/xinntao/facexlib/releases/download/v0.1.0/detection_Resnet50_Final.pth ..\ComfyUI_windows_portable\gfpgan\weights\detection_Resnet50_Final.pth
https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth ..\ComfyUI_windows_portable\gfpgan\weights\GFPGANv1.4.pth
https://github.com/xinntao/facexlib/releases/download/v0.2.2/parsing_parsenet.pth ..\ComfyUI_windows_portable\gfpgan\weights\parsing_parsenet.pth
4. image-face-fusion模型换脸模型
2. image-face-fusion模型换脸模型
链接:https://pan.baidu.com/s/19DOgJQ_RHNAjfNrzSr2uTQ?pwd=gf0p
提取码:gf0p
解压到 ..\ComfyUI\models\image-face-fusion 目录
5. roop-face-swap模型换脸模型
链接:https://pan.baidu.com/s/1cJeRtqgdeNW21Hljwuv-tA?pwd=b4yy
提取码:b4yy
解压到 ..\ComfyUI\models\roop-face-swap 目录
6. 修改comfyUI根目录下的execution.py文件,修改内容为,搜索“Return type mismatch between linked nodes”找到对应行,
替换
```python
if r[val[1]] != type_input:
```
为
```python
if type_input !="*" and r[val[1]] != type_input:
```
搜索 “def recursive_execute”
位置参考图如下:
![Alt text](修改位置.png)
新增内容:
```python
#循环添加代码----------开始----------
def get_del_keys(key, prompt):
keys=[]
for k,v in prompt.items():
for k1,v1 in v['inputs'].items():
if type(v1)==list and v1[0]==key:
keys.append(k)
keys=keys+get_del_keys(k,prompt)
return keys
#循环添加代码-----------结束---------
```
```python
#循环添加代码----------开始----------
startNum=None
delKeys=[]
if class_type=='ForInnerEnd':
startNum=prompt[unique_id]['inputs']['total'][0]
inputNum=prompt[unique_id]['inputs']['images'][0]
startInput=prompt[startNum]['inputs']
maxKey = int(sorted(list(prompt.keys()), key=lambda x: int(x))[-1])
delKeys=list(set(get_del_keys(startNum,prompt)))
delKeys.append(startNum)
delKeys = list(filter(lambda x: x != inputNum, delKeys))
for i in range(startInput['i']+1,startInput['total'],startInput['stop']):
prompt[str(maxKey+i)]=prompt[inputNum]
prompt[unique_id]['inputs']['images'+str(i)]=[str(maxKey+i),prompt[unique_id]['inputs']['images'][-1]]
#循环添加代码-----------结束---------
```
```python
#循环添加代码----------开始----------
if class_type=='ForInnerEnd' and x !='images' and x.startswith('images'):
if startNum!=None:
prompt[startNum]['inputs']['i']=prompt[startNum]['inputs']['i']+prompt[startNum]['inputs']['stop']
for key in delKeys:
outputs.pop(key, None)
#循环添加代码-----------结束---------
```
3. 执行修改脚本
windows 执行“修改文件.bat”文件,未报错后就可以了
linux 执行“修改文件.sh”文件,未报错后就可以了
+2 -1
View File
@@ -7,6 +7,7 @@ from .lam import init, get_ext_dir
repo_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, repo_dir)
original_modules = sys.modules.copy()
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
@@ -21,5 +22,5 @@ if init():
NODE_CLASS_MAPPINGS = {**NODE_CLASS_MAPPINGS, **imported_module.NODE_CLASS_MAPPINGS}
NODE_DISPLAY_NAME_MAPPINGS = {**NODE_DISPLAY_NAME_MAPPINGS, **imported_module.NODE_DISPLAY_NAME_MAPPINGS}
WEB_DIRECTORY = "./js"
WEB_DIRECTORY = "./js"
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS","WEB_DIRECTORY"]
+854
View File
@@ -0,0 +1,854 @@
import sys
import copy
import logging
import threading
import heapq
import traceback
import inspect
from typing import List, Literal, NamedTuple, Optional
import torch
import nodes
import comfy.model_management
def get_input_data(inputs, class_def, unique_id, outputs={}, prompt={}, extra_data={}):
valid_inputs = class_def.INPUT_TYPES()
input_data_all = {}
for x in inputs:
input_data = inputs[x]
if isinstance(input_data, list):
input_unique_id = input_data[0]
output_index = input_data[1]
if input_unique_id not in outputs:
input_data_all[x] = (None,)
continue
obj = outputs[input_unique_id][output_index]
input_data_all[x] = obj
else:
if ("required" in valid_inputs and x in valid_inputs["required"]) or ("optional" in valid_inputs and x in valid_inputs["optional"]):
input_data_all[x] = [input_data]
if "hidden" in valid_inputs:
h = valid_inputs["hidden"]
for x in h:
if h[x] == "PROMPT":
input_data_all[x] = [prompt]
if h[x] == "EXTRA_PNGINFO":
input_data_all[x] = [extra_data.get('extra_pnginfo', None)]
if h[x] == "UNIQUE_ID":
input_data_all[x] = [unique_id]
return input_data_all
def map_node_over_list(obj, input_data_all, func, allow_interrupt=False):
# check if node wants the lists
input_is_list = False
if hasattr(obj, "INPUT_IS_LIST"):
input_is_list = obj.INPUT_IS_LIST
if len(input_data_all) == 0:
max_len_input = 0
else:
max_len_input = max([len(x) for x in input_data_all.values()])
# get a slice of inputs, repeat last input when list isn't long enough
def slice_dict(d, i):
d_new = dict()
for k,v in d.items():
d_new[k] = v[i if len(v) > i else -1]
return d_new
results = []
if input_is_list:
if allow_interrupt:
nodes.before_node_execution()
results.append(getattr(obj, func)(**input_data_all))
elif max_len_input == 0:
if allow_interrupt:
nodes.before_node_execution()
results.append(getattr(obj, func)())
else:
for i in range(max_len_input):
if allow_interrupt:
nodes.before_node_execution()
results.append(getattr(obj, func)(**slice_dict(input_data_all, i)))
return results
def get_output_data(obj, input_data_all):
results = []
uis = []
return_values = map_node_over_list(obj, input_data_all, obj.FUNCTION, allow_interrupt=True)
for r in return_values:
if isinstance(r, dict):
if 'ui' in r:
uis.append(r['ui'])
if 'result' in r:
results.append(r['result'])
else:
results.append(r)
output = []
if len(results) > 0:
# check which outputs need concatenating
output_is_list = [False] * len(results[0])
if hasattr(obj, "OUTPUT_IS_LIST"):
output_is_list = obj.OUTPUT_IS_LIST
# merge node execution results
for i, is_list in zip(range(len(results[0])), output_is_list):
if is_list:
output.append([x for o in results for x in o[i]])
else:
output.append([o[i] for o in results])
ui = dict()
if len(uis) > 0:
ui = {k: [y for x in uis for y in x[k]] for k in uis[0].keys()}
return output, ui
def format_value(x):
if x is None:
return None
elif isinstance(x, (int, float, bool, str)):
return x
else:
return str(x)
def recursive_execute(server, prompt, outputs, current_item, extra_data, executed, prompt_id, outputs_ui, object_storage):
unique_id = current_item
inputs = prompt[unique_id]['inputs']
class_type = prompt[unique_id]['class_type']
class_def = nodes.NODE_CLASS_MAPPINGS[class_type]
if unique_id in outputs:
return (True, None, None)
for x in inputs:
input_data = inputs[x]
if isinstance(input_data, list):
input_unique_id = input_data[0]
output_index = input_data[1]
if input_unique_id not in outputs:
result = recursive_execute(server, prompt, outputs, input_unique_id, extra_data, executed, prompt_id, outputs_ui, object_storage)
if result[0] is not True:
# Another node failed further upstream
return result
input_data_all = None
try:
input_data_all = get_input_data(inputs, class_def, unique_id, outputs, prompt, extra_data)
if server.client_id is not None:
server.last_node_id = unique_id
server.send_sync("executing", { "node": unique_id, "prompt_id": prompt_id }, server.client_id)
obj = object_storage.get((unique_id, class_type), None)
if obj is None:
obj = class_def()
object_storage[(unique_id, class_type)] = obj
output_data, output_ui = get_output_data(obj, input_data_all)
outputs[unique_id] = output_data
if len(output_ui) > 0:
outputs_ui[unique_id] = output_ui
if server.client_id is not None:
server.send_sync("executed", { "node": unique_id, "output": output_ui, "prompt_id": prompt_id }, server.client_id)
except comfy.model_management.InterruptProcessingException as iex:
logging.info("Processing interrupted")
# skip formatting inputs/outputs
error_details = {
"node_id": unique_id,
}
return (False, error_details, iex)
except Exception as ex:
typ, _, tb = sys.exc_info()
exception_type = full_type_name(typ)
input_data_formatted = {}
if input_data_all is not None:
input_data_formatted = {}
for name, inputs in input_data_all.items():
input_data_formatted[name] = [format_value(x) for x in inputs]
output_data_formatted = {}
for node_id, node_outputs in outputs.items():
output_data_formatted[node_id] = [[format_value(x) for x in l] for l in node_outputs]
logging.error(f"!!! Exception during processing!!! {ex}")
logging.error(traceback.format_exc())
error_details = {
"node_id": unique_id,
"exception_message": str(ex),
"exception_type": exception_type,
"traceback": traceback.format_tb(tb),
"current_inputs": input_data_formatted,
"current_outputs": output_data_formatted
}
return (False, error_details, ex)
executed.add(unique_id)
return (True, None, None)
def recursive_will_execute(prompt, outputs, current_item, memo={}):
unique_id = current_item
if unique_id in memo:
return memo[unique_id]
inputs = prompt[unique_id]['inputs']
will_execute = []
if unique_id in outputs:
return []
for x in inputs:
input_data = inputs[x]
if isinstance(input_data, list):
input_unique_id = input_data[0]
output_index = input_data[1]
if input_unique_id not in outputs:
will_execute += recursive_will_execute(prompt, outputs, input_unique_id, memo)
memo[unique_id] = will_execute + [unique_id]
return memo[unique_id]
def recursive_output_delete_if_changed(prompt, old_prompt, outputs, current_item):
unique_id = current_item
inputs = prompt[unique_id]['inputs']
class_type = prompt[unique_id]['class_type']
class_def = nodes.NODE_CLASS_MAPPINGS[class_type]
is_changed_old = ''
is_changed = ''
to_delete = False
if hasattr(class_def, 'IS_CHANGED'):
if unique_id in old_prompt and 'is_changed' in old_prompt[unique_id]:
is_changed_old = old_prompt[unique_id]['is_changed']
if 'is_changed' not in prompt[unique_id]:
input_data_all = get_input_data(inputs, class_def, unique_id, outputs)
if input_data_all is not None:
try:
#is_changed = class_def.IS_CHANGED(**input_data_all)
is_changed = map_node_over_list(class_def, input_data_all, "IS_CHANGED")
prompt[unique_id]['is_changed'] = is_changed
except:
to_delete = True
else:
is_changed = prompt[unique_id]['is_changed']
if unique_id not in outputs:
return True
if not to_delete:
if is_changed != is_changed_old:
to_delete = True
elif unique_id not in old_prompt:
to_delete = True
elif inputs == old_prompt[unique_id]['inputs']:
for x in inputs:
input_data = inputs[x]
if isinstance(input_data, list):
input_unique_id = input_data[0]
output_index = input_data[1]
if input_unique_id in outputs:
to_delete = recursive_output_delete_if_changed(prompt, old_prompt, outputs, input_unique_id)
else:
to_delete = True
if to_delete:
break
else:
to_delete = True
if to_delete:
d = outputs.pop(unique_id)
del d
return to_delete
class PromptExecutor:
def __init__(self, server):
self.server = server
self.reset()
def reset(self):
self.outputs = {}
self.object_storage = {}
self.outputs_ui = {}
self.status_messages = []
self.success = True
self.old_prompt = {}
def add_message(self, event, data, broadcast: bool):
self.status_messages.append((event, data))
if self.server.client_id is not None or broadcast:
self.server.send_sync(event, data, self.server.client_id)
def handle_execution_error(self, prompt_id, prompt, current_outputs, executed, error, ex):
node_id = error["node_id"]
class_type = prompt[node_id]["class_type"]
# First, send back the status to the frontend depending
# on the exception type
if isinstance(ex, comfy.model_management.InterruptProcessingException):
mes = {
"prompt_id": prompt_id,
"node_id": node_id,
"node_type": class_type,
"executed": list(executed),
}
self.add_message("execution_interrupted", mes, broadcast=True)
else:
mes = {
"prompt_id": prompt_id,
"node_id": node_id,
"node_type": class_type,
"executed": list(executed),
"exception_message": error["exception_message"],
"exception_type": error["exception_type"],
"traceback": error["traceback"],
"current_inputs": error["current_inputs"],
"current_outputs": error["current_outputs"],
}
self.add_message("execution_error", mes, broadcast=False)
# Next, remove the subsequent outputs since they will not be executed
to_delete = []
for o in self.outputs:
if (o not in current_outputs) and (o not in executed):
to_delete += [o]
if o in self.old_prompt:
d = self.old_prompt.pop(o)
del d
for o in to_delete:
d = self.outputs.pop(o)
del d
def execute(self, prompt, prompt_id, extra_data={}, execute_outputs=[]):
nodes.interrupt_processing(False)
if "client_id" in extra_data:
self.server.client_id = extra_data["client_id"]
else:
self.server.client_id = None
self.status_messages = []
self.add_message("execution_start", { "prompt_id": prompt_id}, broadcast=False)
with torch.inference_mode():
#delete cached outputs if nodes don't exist for them
to_delete = []
for o in self.outputs:
if o not in prompt:
to_delete += [o]
for o in to_delete:
d = self.outputs.pop(o)
del d
to_delete = []
for o in self.object_storage:
if o[0] not in prompt:
to_delete += [o]
else:
p = prompt[o[0]]
if o[1] != p['class_type']:
to_delete += [o]
for o in to_delete:
d = self.object_storage.pop(o)
del d
for x in prompt:
recursive_output_delete_if_changed(prompt, self.old_prompt, self.outputs, x)
current_outputs = set(self.outputs.keys())
for x in list(self.outputs_ui.keys()):
if x not in current_outputs:
d = self.outputs_ui.pop(x)
del d
comfy.model_management.cleanup_models(keep_clone_weights_loaded=True)
self.add_message("execution_cached",
{ "nodes": list(current_outputs) , "prompt_id": prompt_id},
broadcast=False)
executed = set()
output_node_id = None
to_execute = []
for node_id in list(execute_outputs):
to_execute += [(0, node_id)]
while len(to_execute) > 0:
#always execute the output that depends on the least amount of unexecuted nodes first
memo = {}
to_execute = sorted(list(map(lambda a: (len(recursive_will_execute(prompt, self.outputs, a[-1], memo)), a[-1]), to_execute)))
output_node_id = to_execute.pop(0)[-1]
# This call shouldn't raise anything if there's an error deep in
# the actual SD code, instead it will report the node where the
# error was raised
self.success, error, ex = recursive_execute(self.server, prompt, self.outputs, output_node_id, extra_data, executed, prompt_id, self.outputs_ui, self.object_storage)
if self.success is not True:
self.handle_execution_error(prompt_id, prompt, current_outputs, executed, error, ex)
break
for x in executed:
self.old_prompt[x] = copy.deepcopy(prompt[x])
self.server.last_node_id = None
if comfy.model_management.DISABLE_SMART_MEMORY:
comfy.model_management.unload_all_models()
def validate_inputs(prompt, item, validated):
unique_id = item
if unique_id in validated:
return validated[unique_id]
inputs = prompt[unique_id]['inputs']
class_type = prompt[unique_id]['class_type']
obj_class = nodes.NODE_CLASS_MAPPINGS[class_type]
class_inputs = obj_class.INPUT_TYPES()
required_inputs = class_inputs['required']
errors = []
valid = True
validate_function_inputs = []
if hasattr(obj_class, "VALIDATE_INPUTS"):
validate_function_inputs = inspect.getfullargspec(obj_class.VALIDATE_INPUTS).args
for x in required_inputs:
if x not in inputs:
error = {
"type": "required_input_missing",
"message": "Required input is missing",
"details": f"{x}",
"extra_info": {
"input_name": x
}
}
errors.append(error)
continue
val = inputs[x]
info = required_inputs[x]
type_input = info[0]
if isinstance(val, list):
if len(val) != 2:
error = {
"type": "bad_linked_input",
"message": "Bad linked input, must be a length-2 list of [node_id, slot_index]",
"details": f"{x}",
"extra_info": {
"input_name": x,
"input_config": info,
"received_value": val
}
}
errors.append(error)
continue
o_id = val[0]
o_class_type = prompt[o_id]['class_type']
r = nodes.NODE_CLASS_MAPPINGS[o_class_type].RETURN_TYPES
if r[val[1]] != type_input:
received_type = r[val[1]]
details = f"{x}, {received_type} != {type_input}"
error = {
"type": "return_type_mismatch",
"message": "Return type mismatch between linked nodes",
"details": details,
"extra_info": {
"input_name": x,
"input_config": info,
"received_type": received_type,
"linked_node": val
}
}
errors.append(error)
continue
try:
r = validate_inputs(prompt, o_id, validated)
if r[0] is False:
# `r` will be set in `validated[o_id]` already
valid = False
continue
except Exception as ex:
typ, _, tb = sys.exc_info()
valid = False
exception_type = full_type_name(typ)
reasons = [{
"type": "exception_during_inner_validation",
"message": "Exception when validating inner node",
"details": str(ex),
"extra_info": {
"input_name": x,
"input_config": info,
"exception_message": str(ex),
"exception_type": exception_type,
"traceback": traceback.format_tb(tb),
"linked_node": val
}
}]
validated[o_id] = (False, reasons, o_id)
continue
else:
try:
if type_input == "INT":
val = int(val)
inputs[x] = val
if type_input == "FLOAT":
val = float(val)
inputs[x] = val
if type_input == "STRING":
val = str(val)
inputs[x] = val
except Exception as ex:
error = {
"type": "invalid_input_type",
"message": f"Failed to convert an input value to a {type_input} value",
"details": f"{x}, {val}, {ex}",
"extra_info": {
"input_name": x,
"input_config": info,
"received_value": val,
"exception_message": str(ex)
}
}
errors.append(error)
continue
if len(info) > 1:
if "min" in info[1] and val < info[1]["min"]:
error = {
"type": "value_smaller_than_min",
"message": "Value {} smaller than min of {}".format(val, info[1]["min"]),
"details": f"{x}",
"extra_info": {
"input_name": x,
"input_config": info,
"received_value": val,
}
}
errors.append(error)
continue
if "max" in info[1] and val > info[1]["max"]:
error = {
"type": "value_bigger_than_max",
"message": "Value {} bigger than max of {}".format(val, info[1]["max"]),
"details": f"{x}",
"extra_info": {
"input_name": x,
"input_config": info,
"received_value": val,
}
}
errors.append(error)
continue
if x not in validate_function_inputs:
if isinstance(type_input, list):
if val not in type_input:
input_config = info
list_info = ""
# Don't send back gigantic lists like if they're lots of
# scanned model filepaths
if len(type_input) > 20:
list_info = f"(list of length {len(type_input)})"
input_config = None
else:
list_info = str(type_input)
error = {
"type": "value_not_in_list",
"message": "Value not in list",
"details": f"{x}: '{val}' not in {list_info}",
"extra_info": {
"input_name": x,
"input_config": input_config,
"received_value": val,
}
}
errors.append(error)
continue
if len(validate_function_inputs) > 0:
input_data_all = get_input_data(inputs, obj_class, unique_id)
input_filtered = {}
for x in input_data_all:
if x in validate_function_inputs:
input_filtered[x] = input_data_all[x]
#ret = obj_class.VALIDATE_INPUTS(**input_filtered)
ret = map_node_over_list(obj_class, input_filtered, "VALIDATE_INPUTS")
for x in input_filtered:
for i, r in enumerate(ret):
if r is not True:
details = f"{x}"
if r is not False:
details += f" - {str(r)}"
error = {
"type": "custom_validation_failed",
"message": "Custom validation failed for node",
"details": details,
"extra_info": {
"input_name": x,
"input_config": info,
"received_value": val,
}
}
errors.append(error)
continue
if len(errors) > 0 or valid is not True:
ret = (False, errors, unique_id)
else:
ret = (True, [], unique_id)
validated[unique_id] = ret
return ret
def full_type_name(klass):
module = klass.__module__
if module == 'builtins':
return klass.__qualname__
return module + '.' + klass.__qualname__
def validate_prompt(prompt):
outputs = set()
for x in prompt:
if 'class_type' not in prompt[x]:
error = {
"type": "invalid_prompt",
"message": f"Cannot execute because a node is missing the class_type property.",
"details": f"Node ID '#{x}'",
"extra_info": {}
}
return (False, error, [], [])
class_type = prompt[x]['class_type']
class_ = nodes.NODE_CLASS_MAPPINGS.get(class_type, None)
if class_ is None:
error = {
"type": "invalid_prompt",
"message": f"Cannot execute because node {class_type} does not exist.",
"details": f"Node ID '#{x}'",
"extra_info": {}
}
return (False, error, [], [])
if hasattr(class_, 'OUTPUT_NODE') and class_.OUTPUT_NODE is True:
outputs.add(x)
if len(outputs) == 0:
error = {
"type": "prompt_no_outputs",
"message": "Prompt has no outputs",
"details": "",
"extra_info": {}
}
return (False, error, [], [])
good_outputs = set()
errors = []
node_errors = {}
validated = {}
for o in outputs:
valid = False
reasons = []
try:
m = validate_inputs(prompt, o, validated)
valid = m[0]
reasons = m[1]
except Exception as ex:
typ, _, tb = sys.exc_info()
valid = False
exception_type = full_type_name(typ)
reasons = [{
"type": "exception_during_validation",
"message": "Exception when validating node",
"details": str(ex),
"extra_info": {
"exception_type": exception_type,
"traceback": traceback.format_tb(tb)
}
}]
validated[o] = (False, reasons, o)
if valid is True:
good_outputs.add(o)
else:
logging.error(f"Failed to validate prompt for output {o}:")
if len(reasons) > 0:
logging.error("* (prompt):")
for reason in reasons:
logging.error(f" - {reason['message']}: {reason['details']}")
errors += [(o, reasons)]
for node_id, result in validated.items():
valid = result[0]
reasons = result[1]
# If a node upstream has errors, the nodes downstream will also
# be reported as invalid, but there will be no errors attached.
# So don't return those nodes as having errors in the response.
if valid is not True and len(reasons) > 0:
if node_id not in node_errors:
class_type = prompt[node_id]['class_type']
node_errors[node_id] = {
"errors": reasons,
"dependent_outputs": [],
"class_type": class_type
}
logging.error(f"* {class_type} {node_id}:")
for reason in reasons:
logging.error(f" - {reason['message']}: {reason['details']}")
node_errors[node_id]["dependent_outputs"].append(o)
logging.error("Output will be ignored")
if len(good_outputs) == 0:
errors_list = []
for o, errors in errors:
for error in errors:
errors_list.append(f"{error['message']}: {error['details']}")
errors_list = "\n".join(errors_list)
error = {
"type": "prompt_outputs_failed_validation",
"message": "Prompt outputs failed validation",
"details": errors_list,
"extra_info": {}
}
return (False, error, list(good_outputs), node_errors)
return (True, None, list(good_outputs), node_errors)
MAXIMUM_HISTORY_SIZE = 10000
class PromptQueue:
def __init__(self, server):
self.server = server
self.mutex = threading.RLock()
self.not_empty = threading.Condition(self.mutex)
self.task_counter = 0
self.queue = []
self.currently_running = {}
self.history = {}
self.flags = {}
server.prompt_queue = self
def put(self, item):
with self.mutex:
heapq.heappush(self.queue, item)
self.server.queue_updated()
self.not_empty.notify()
def get(self, timeout=None):
with self.not_empty:
while len(self.queue) == 0:
self.not_empty.wait(timeout=timeout)
if timeout is not None and len(self.queue) == 0:
return None
item = heapq.heappop(self.queue)
i = self.task_counter
self.currently_running[i] = copy.deepcopy(item)
self.task_counter += 1
self.server.queue_updated()
return (item, i)
class ExecutionStatus(NamedTuple):
status_str: Literal['success', 'error']
completed: bool
messages: List[str]
def task_done(self, item_id, outputs,
status: Optional['PromptQueue.ExecutionStatus']):
with self.mutex:
prompt = self.currently_running.pop(item_id)
if len(self.history) > MAXIMUM_HISTORY_SIZE:
self.history.pop(next(iter(self.history)))
status_dict: Optional[dict] = None
if status is not None:
status_dict = copy.deepcopy(status._asdict())
self.history[prompt[1]] = {
"prompt": prompt,
"outputs": copy.deepcopy(outputs),
'status': status_dict,
}
self.server.queue_updated()
def get_current_queue(self):
with self.mutex:
out = []
for x in self.currently_running.values():
out += [x]
return (out, copy.deepcopy(self.queue))
def get_tasks_remaining(self):
with self.mutex:
return len(self.queue) + len(self.currently_running)
def wipe_queue(self):
with self.mutex:
self.queue = []
self.server.queue_updated()
def delete_queue_item(self, function):
with self.mutex:
for x in range(len(self.queue)):
if function(self.queue[x]):
if len(self.queue) == 1:
self.wipe_queue()
else:
self.queue.pop(x)
heapq.heapify(self.queue)
self.server.queue_updated()
return True
return False
def get_history(self, prompt_id=None, max_items=None, offset=-1):
with self.mutex:
if prompt_id is None:
out = {}
i = 0
if offset < 0 and max_items is not None:
offset = len(self.history) - max_items
for k in self.history:
if i >= offset:
out[k] = self.history[k]
if max_items is not None and len(out) >= max_items:
break
i += 1
return out
elif prompt_id in self.history:
return {prompt_id: copy.deepcopy(self.history[prompt_id])}
else:
return {}
def wipe_history(self):
with self.mutex:
self.history = {}
def delete_history_item(self, id_to_delete):
with self.mutex:
self.history.pop(id_to_delete, None)
def set_flag(self, name, data):
with self.mutex:
self.flags[name] = data
self.not_empty.notify()
def get_flags(self, reset=True):
with self.mutex:
if reset:
ret = self.flags
self.flags = {}
return ret
else:
return self.flags.copy()
+655
View File
@@ -0,0 +1,655 @@
import os
import sys
import asyncio
import traceback
import nodes
import folder_paths
import execution
import uuid
import urllib
import json
import glob
import struct
import ssl
from PIL import Image, ImageOps
from PIL.PngImagePlugin import PngInfo
from io import BytesIO
import aiohttp
from aiohttp import web
import logging
import mimetypes
from comfy.cli_args import args
import comfy.utils
import comfy.model_management
from app.user_manager import UserManager
class BinaryEventTypes:
PREVIEW_IMAGE = 1
UNENCODED_PREVIEW_IMAGE = 2
async def send_socket_catch_exception(function, message):
try:
await function(message)
except (aiohttp.ClientError, aiohttp.ClientPayloadError, ConnectionResetError) as err:
logging.warning("send error: {}".format(err))
@web.middleware
async def cache_control(request: web.Request, handler):
response: web.Response = await handler(request)
if request.path.endswith('.js') or request.path.endswith('.css'):
response.headers.setdefault('Cache-Control', 'no-cache')
return response
def create_cors_middleware(allowed_origin: str):
@web.middleware
async def cors_middleware(request: web.Request, handler):
if request.method == "OPTIONS":
# Pre-flight request. Reply successfully:
response = web.Response()
else:
response = await handler(request)
response.headers['Access-Control-Allow-Origin'] = allowed_origin
response.headers['Access-Control-Allow-Methods'] = 'POST, GET, DELETE, PUT, OPTIONS'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
response.headers['Access-Control-Allow-Credentials'] = 'true'
return response
return cors_middleware
class PromptServer():
def __init__(self, loop):
PromptServer.instance = self
mimetypes.init()
mimetypes.types_map['.js'] = 'application/javascript; charset=utf-8'
self.user_manager = UserManager()
self.supports = ["custom_nodes_from_web"]
self.prompt_queue = None
self.loop = loop
self.messages = asyncio.Queue()
self.number = 0
middlewares = [cache_control]
if args.enable_cors_header:
middlewares.append(create_cors_middleware(args.enable_cors_header))
max_upload_size = round(args.max_upload_size * 1024 * 1024)
self.app = web.Application(client_max_size=max_upload_size, middlewares=middlewares)
self.sockets = dict()
self.web_root = os.path.join(os.path.dirname(
os.path.realpath(__file__)), "web")
routes = web.RouteTableDef()
self.routes = routes
self.last_node_id = None
self.client_id = None
self.on_prompt_handlers = []
@routes.get('/ws')
async def websocket_handler(request):
ws = web.WebSocketResponse()
await ws.prepare(request)
sid = request.rel_url.query.get('clientId', '')
if sid:
# Reusing existing session, remove old
self.sockets.pop(sid, None)
else:
sid = uuid.uuid4().hex
self.sockets[sid] = ws
try:
# Send initial state to the new client
await self.send("status", { "status": self.get_queue_info(), 'sid': sid }, sid)
# On reconnect if we are the currently executing client send the current node
if self.client_id == sid and self.last_node_id is not None:
await self.send("executing", { "node": self.last_node_id }, sid)
async for msg in ws:
if msg.type == aiohttp.WSMsgType.ERROR:
logging.warning('ws connection closed with exception %s' % ws.exception())
finally:
self.sockets.pop(sid, None)
return ws
@routes.get("/")
async def get_root(request):
return web.FileResponse(os.path.join(self.web_root, "index.html"))
@routes.get("/embeddings")
def get_embeddings(self):
embeddings = folder_paths.get_filename_list("embeddings")
return web.json_response(list(map(lambda a: os.path.splitext(a)[0], embeddings)))
@routes.get("/extensions")
async def get_extensions(request):
files = glob.glob(os.path.join(
glob.escape(self.web_root), 'extensions/**/*.js'), recursive=True)
extensions = list(map(lambda f: "/" + os.path.relpath(f, self.web_root).replace("\\", "/"), files))
for name, dir in nodes.EXTENSION_WEB_DIRS.items():
files = glob.glob(os.path.join(glob.escape(dir), '**/*.js'), recursive=True)
extensions.extend(list(map(lambda f: "/extensions/" + urllib.parse.quote(
name) + "/" + os.path.relpath(f, dir).replace("\\", "/"), files)))
return web.json_response(extensions)
def get_dir_by_type(dir_type):
if dir_type is None:
dir_type = "input"
if dir_type == "input":
type_dir = folder_paths.get_input_directory()
elif dir_type == "temp":
type_dir = folder_paths.get_temp_directory()
elif dir_type == "output":
type_dir = folder_paths.get_output_directory()
return type_dir, dir_type
def image_upload(post, image_save_function=None):
image = post.get("image")
overwrite = post.get("overwrite")
image_upload_type = post.get("type")
upload_dir, image_upload_type = get_dir_by_type(image_upload_type)
if image and image.file:
filename = image.filename
if not filename:
return web.Response(status=400)
subfolder = post.get("subfolder", "")
full_output_folder = os.path.join(upload_dir, os.path.normpath(subfolder))
filepath = os.path.abspath(os.path.join(full_output_folder, filename))
if os.path.commonpath((upload_dir, filepath)) != upload_dir:
return web.Response(status=400)
if not os.path.exists(full_output_folder):
os.makedirs(full_output_folder)
split = os.path.splitext(filename)
if overwrite is not None and (overwrite == "true" or overwrite == "1"):
pass
else:
i = 1
while os.path.exists(filepath):
filename = f"{split[0]} ({i}){split[1]}"
filepath = os.path.join(full_output_folder, filename)
i += 1
if image_save_function is not None:
image_save_function(image, post, filepath)
else:
with open(filepath, "wb") as f:
f.write(image.file.read())
return web.json_response({"name" : filename, "subfolder": subfolder, "type": image_upload_type})
else:
return web.Response(status=400)
@routes.post("/upload/image")
async def upload_image(request):
post = await request.post()
return image_upload(post)
@routes.post("/upload/mask")
async def upload_mask(request):
post = await request.post()
def image_save_function(image, post, filepath):
original_ref = json.loads(post.get("original_ref"))
filename, output_dir = folder_paths.annotated_filepath(original_ref['filename'])
# validation for security: prevent accessing arbitrary path
if filename[0] == '/' or '..' in filename:
return web.Response(status=400)
if output_dir is None:
type = original_ref.get("type", "output")
output_dir = folder_paths.get_directory_by_type(type)
if output_dir is None:
return web.Response(status=400)
if original_ref.get("subfolder", "") != "":
full_output_dir = os.path.join(output_dir, original_ref["subfolder"])
if os.path.commonpath((os.path.abspath(full_output_dir), output_dir)) != output_dir:
return web.Response(status=403)
output_dir = full_output_dir
file = os.path.join(output_dir, filename)
if os.path.isfile(file):
with Image.open(file) as original_pil:
metadata = PngInfo()
if hasattr(original_pil,'text'):
for key in original_pil.text:
metadata.add_text(key, original_pil.text[key])
original_pil = original_pil.convert('RGBA')
mask_pil = Image.open(image.file).convert('RGBA')
# alpha copy
new_alpha = mask_pil.getchannel('A')
original_pil.putalpha(new_alpha)
original_pil.save(filepath, compress_level=4, pnginfo=metadata)
return image_upload(post, image_save_function)
@routes.get("/view")
async def view_image(request):
if "filename" in request.rel_url.query:
filename = request.rel_url.query["filename"]
filename,output_dir = folder_paths.annotated_filepath(filename)
# validation for security: prevent accessing arbitrary path
if filename[0] == '/' or '..' in filename:
return web.Response(status=400)
if output_dir is None:
type = request.rel_url.query.get("type", "output")
output_dir = folder_paths.get_directory_by_type(type)
if output_dir is None:
return web.Response(status=400)
if "subfolder" in request.rel_url.query:
full_output_dir = os.path.join(output_dir, request.rel_url.query["subfolder"])
if os.path.commonpath((os.path.abspath(full_output_dir), output_dir)) != output_dir:
return web.Response(status=403)
output_dir = full_output_dir
filename = os.path.basename(filename)
file = os.path.join(output_dir, filename)
if os.path.isfile(file):
if 'preview' in request.rel_url.query:
with Image.open(file) as img:
preview_info = request.rel_url.query['preview'].split(';')
image_format = preview_info[0]
if image_format not in ['webp', 'jpeg'] or 'a' in request.rel_url.query.get('channel', ''):
image_format = 'webp'
quality = 90
if preview_info[-1].isdigit():
quality = int(preview_info[-1])
buffer = BytesIO()
if image_format in ['jpeg'] or request.rel_url.query.get('channel', '') == 'rgb':
img = img.convert("RGB")
img.save(buffer, format=image_format, quality=quality)
buffer.seek(0)
return web.Response(body=buffer.read(), content_type=f'image/{image_format}',
headers={"Content-Disposition": f"filename=\"{filename}\""})
if 'channel' not in request.rel_url.query:
channel = 'rgba'
else:
channel = request.rel_url.query["channel"]
if channel == 'rgb':
with Image.open(file) as img:
if img.mode == "RGBA":
r, g, b, a = img.split()
new_img = Image.merge('RGB', (r, g, b))
else:
new_img = img.convert("RGB")
buffer = BytesIO()
new_img.save(buffer, format='PNG')
buffer.seek(0)
return web.Response(body=buffer.read(), content_type='image/png',
headers={"Content-Disposition": f"filename=\"{filename}\""})
elif channel == 'a':
with Image.open(file) as img:
if img.mode == "RGBA":
_, _, _, a = img.split()
else:
a = Image.new('L', img.size, 255)
# alpha img
alpha_img = Image.new('RGBA', img.size)
alpha_img.putalpha(a)
alpha_buffer = BytesIO()
alpha_img.save(alpha_buffer, format='PNG')
alpha_buffer.seek(0)
return web.Response(body=alpha_buffer.read(), content_type='image/png',
headers={"Content-Disposition": f"filename=\"{filename}\""})
else:
return web.FileResponse(file, headers={"Content-Disposition": f"filename=\"{filename}\""})
return web.Response(status=404)
@routes.get("/view_metadata/{folder_name}")
async def view_metadata(request):
folder_name = request.match_info.get("folder_name", None)
if folder_name is None:
return web.Response(status=404)
if not "filename" in request.rel_url.query:
return web.Response(status=404)
filename = request.rel_url.query["filename"]
if not filename.endswith(".safetensors"):
return web.Response(status=404)
safetensors_path = folder_paths.get_full_path(folder_name, filename)
if safetensors_path is None:
return web.Response(status=404)
out = comfy.utils.safetensors_header(safetensors_path, max_size=1024*1024)
if out is None:
return web.Response(status=404)
dt = json.loads(out)
if not "__metadata__" in dt:
return web.Response(status=404)
return web.json_response(dt["__metadata__"])
@routes.get("/system_stats")
async def get_queue(request):
device = comfy.model_management.get_torch_device()
device_name = comfy.model_management.get_torch_device_name(device)
vram_total, torch_vram_total = comfy.model_management.get_total_memory(device, torch_total_too=True)
vram_free, torch_vram_free = comfy.model_management.get_free_memory(device, torch_free_too=True)
system_stats = {
"system": {
"os": os.name,
"python_version": sys.version,
"embedded_python": os.path.split(os.path.split(sys.executable)[0])[1] == "python_embeded"
},
"devices": [
{
"name": device_name,
"type": device.type,
"index": device.index,
"vram_total": vram_total,
"vram_free": vram_free,
"torch_vram_total": torch_vram_total,
"torch_vram_free": torch_vram_free,
}
]
}
return web.json_response(system_stats)
@routes.get("/prompt")
async def get_prompt(request):
return web.json_response(self.get_queue_info())
def node_info(node_class):
obj_class = nodes.NODE_CLASS_MAPPINGS[node_class]
info = {}
info['input'] = obj_class.INPUT_TYPES()
info['output'] = obj_class.RETURN_TYPES
info['output_is_list'] = obj_class.OUTPUT_IS_LIST if hasattr(obj_class, 'OUTPUT_IS_LIST') else [False] * len(obj_class.RETURN_TYPES)
info['output_name'] = obj_class.RETURN_NAMES if hasattr(obj_class, 'RETURN_NAMES') else info['output']
info['name'] = node_class
info['display_name'] = nodes.NODE_DISPLAY_NAME_MAPPINGS[node_class] if node_class in nodes.NODE_DISPLAY_NAME_MAPPINGS.keys() else node_class
info['description'] = obj_class.DESCRIPTION if hasattr(obj_class,'DESCRIPTION') else ''
info['category'] = 'sd'
if hasattr(obj_class, 'OUTPUT_NODE') and obj_class.OUTPUT_NODE == True:
info['output_node'] = True
else:
info['output_node'] = False
if hasattr(obj_class, 'CATEGORY'):
info['category'] = obj_class.CATEGORY
return info
@routes.get("/object_info")
async def get_object_info(request):
out = {}
for x in nodes.NODE_CLASS_MAPPINGS:
try:
out[x] = node_info(x)
except Exception as e:
logging.error(f"[ERROR] An error occurred while retrieving information for the '{x}' node.")
logging.error(traceback.format_exc())
return web.json_response(out)
@routes.get("/object_info/{node_class}")
async def get_object_info_node(request):
node_class = request.match_info.get("node_class", None)
out = {}
if (node_class is not None) and (node_class in nodes.NODE_CLASS_MAPPINGS):
out[node_class] = node_info(node_class)
return web.json_response(out)
@routes.get("/history")
async def get_history(request):
max_items = request.rel_url.query.get("max_items", None)
if max_items is not None:
max_items = int(max_items)
return web.json_response(self.prompt_queue.get_history(max_items=max_items))
@routes.get("/history/{prompt_id}")
async def get_history(request):
prompt_id = request.match_info.get("prompt_id", None)
return web.json_response(self.prompt_queue.get_history(prompt_id=prompt_id))
@routes.get("/queue")
async def get_queue(request):
queue_info = {}
current_queue = self.prompt_queue.get_current_queue()
queue_info['queue_running'] = current_queue[0]
queue_info['queue_pending'] = current_queue[1]
return web.json_response(queue_info)
@routes.post("/prompt")
async def post_prompt(request):
logging.info("got prompt")
resp_code = 200
out_string = ""
json_data = await request.json()
json_data = self.trigger_on_prompt(json_data)
if "number" in json_data:
number = float(json_data['number'])
else:
number = self.number
if "front" in json_data:
if json_data['front']:
number = -number
self.number += 1
if "prompt" in json_data:
prompt = json_data["prompt"]
valid = execution.validate_prompt(prompt)
extra_data = {}
if "extra_data" in json_data:
extra_data = json_data["extra_data"]
if "client_id" in json_data:
extra_data["client_id"] = json_data["client_id"]
if valid[0]:
prompt_id = str(uuid.uuid4())
outputs_to_execute = valid[2]
self.prompt_queue.put((number, prompt_id, prompt, extra_data, outputs_to_execute))
response = {"prompt_id": prompt_id, "number": number, "node_errors": valid[3]}
return web.json_response(response)
else:
logging.warning("invalid prompt: {}".format(valid[1]))
return web.json_response({"error": valid[1], "node_errors": valid[3]}, status=400)
else:
return web.json_response({"error": "no prompt", "node_errors": []}, status=400)
@routes.post("/queue")
async def post_queue(request):
json_data = await request.json()
if "clear" in json_data:
if json_data["clear"]:
self.prompt_queue.wipe_queue()
if "delete" in json_data:
to_delete = json_data['delete']
for id_to_delete in to_delete:
delete_func = lambda a: a[1] == id_to_delete
self.prompt_queue.delete_queue_item(delete_func)
return web.Response(status=200)
@routes.post("/interrupt")
async def post_interrupt(request):
nodes.interrupt_processing()
return web.Response(status=200)
@routes.post("/free")
async def post_free(request):
json_data = await request.json()
unload_models = json_data.get("unload_models", False)
free_memory = json_data.get("free_memory", False)
if unload_models:
self.prompt_queue.set_flag("unload_models", unload_models)
if free_memory:
self.prompt_queue.set_flag("free_memory", free_memory)
return web.Response(status=200)
@routes.post("/history")
async def post_history(request):
json_data = await request.json()
if "clear" in json_data:
if json_data["clear"]:
self.prompt_queue.wipe_history()
if "delete" in json_data:
to_delete = json_data['delete']
for id_to_delete in to_delete:
self.prompt_queue.delete_history_item(id_to_delete)
return web.Response(status=200)
def add_routes(self):
self.user_manager.add_routes(self.routes)
self.app.add_routes(self.routes)
for name, dir in nodes.EXTENSION_WEB_DIRS.items():
self.app.add_routes([
web.static('/extensions/' + urllib.parse.quote(name), dir),
])
self.app.add_routes([
web.static('/', self.web_root),
])
def get_queue_info(self):
prompt_info = {}
exec_info = {}
exec_info['queue_remaining'] = self.prompt_queue.get_tasks_remaining()
prompt_info['exec_info'] = exec_info
return prompt_info
async def send(self, event, data, sid=None):
if event == BinaryEventTypes.UNENCODED_PREVIEW_IMAGE:
await self.send_image(data, sid=sid)
elif isinstance(data, (bytes, bytearray)):
await self.send_bytes(event, data, sid)
else:
await self.send_json(event, data, sid)
def encode_bytes(self, event, data):
if not isinstance(event, int):
raise RuntimeError(f"Binary event types must be integers, got {event}")
packed = struct.pack(">I", event)
message = bytearray(packed)
message.extend(data)
return message
async def send_image(self, image_data, sid=None):
image_type = image_data[0]
image = image_data[1]
max_size = image_data[2]
if max_size is not None:
if hasattr(Image, 'Resampling'):
resampling = Image.Resampling.BILINEAR
else:
resampling = Image.ANTIALIAS
image = ImageOps.contain(image, (max_size, max_size), resampling)
type_num = 1
if image_type == "JPEG":
type_num = 1
elif image_type == "PNG":
type_num = 2
bytesIO = BytesIO()
header = struct.pack(">I", type_num)
bytesIO.write(header)
image.save(bytesIO, format=image_type, quality=95, compress_level=1)
preview_bytes = bytesIO.getvalue()
await self.send_bytes(BinaryEventTypes.PREVIEW_IMAGE, preview_bytes, sid=sid)
async def send_bytes(self, event, data, sid=None):
message = self.encode_bytes(event, data)
if sid is None:
sockets = list(self.sockets.values())
for ws in sockets:
await send_socket_catch_exception(ws.send_bytes, message)
elif sid in self.sockets:
await send_socket_catch_exception(self.sockets[sid].send_bytes, message)
async def send_json(self, event, data, sid=None):
message = {"type": event, "data": data}
if sid is None:
sockets = list(self.sockets.values())
for ws in sockets:
await send_socket_catch_exception(ws.send_json, message)
elif sid in self.sockets:
await send_socket_catch_exception(self.sockets[sid].send_json, message)
def send_sync(self, event, data, sid=None):
self.loop.call_soon_threadsafe(
self.messages.put_nowait, (event, data, sid))
def queue_updated(self):
self.send_sync("status", { "status": self.get_queue_info() })
async def publish_loop(self):
while True:
msg = await self.messages.get()
await self.send(*msg)
async def start(self, address, port, verbose=True, call_on_start=None):
runner = web.AppRunner(self.app, access_log=None)
await runner.setup()
ssl_ctx = None
scheme = "http"
if args.tls_keyfile and args.tls_certfile:
ssl_ctx = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_SERVER, verify_mode=ssl.CERT_NONE)
ssl_ctx.load_cert_chain(certfile=args.tls_certfile,
keyfile=args.tls_keyfile)
scheme = "https"
site = web.TCPSite(runner, address, port, ssl_context=ssl_ctx)
await site.start()
if verbose:
logging.info("Starting server\n")
logging.info("To see the GUI go to: {}://{}:{}".format(scheme, address, port))
if call_on_start is not None:
call_on_start(scheme, address, port)
def add_on_prompt_handler(self, handler):
self.on_prompt_handlers.append(handler)
def trigger_on_prompt(self, json_data):
for handler in self.on_prompt_handlers:
try:
json_data = handler(json_data)
except Exception as e:
logging.warning(f"[ERROR] An error occurred during the on_prompt_handler processing")
logging.warning(traceback.format_exc())
return json_data
+216
View File
@@ -0,0 +1,216 @@
import os,sys
#读取py文件为text字符串
editlist=[{
"name": "server.py",
"isEditStr":"elif 'prompt_id' in json_data:",
"changs":[
{
"primit":'''else:
return web.json_response({"error": "no prompt", "node_errors": []}, status=400)''',
"edit":'''elif 'prompt_id' in json_data:
return web.json_response(json_data)
else:
return web.json_response({"error": "no prompt", "node_errors": []}, status=400)'''
}
]
},{
"name": "execution.py",
"isEditStr":"def get_del_keys(key, prompt,uniqueIds)",
"changs":[
{
"primit":'''def recursive_execute''',
"edit":'''#循环添加代码----------开始----------
def get_del_keys(key, prompt,uniqueIds):
keys=[]
for k,v in prompt.items():
if k in uniqueIds :
continue
for k1,v1 in v['inputs'].items():
if type(v1)==list and v1[0]==key:
keys.append(k)
keys=keys+get_del_keys(k,prompt,uniqueIds+[k])
return keys
#循环添加代码-----------结束---------
def recursive_execute'''
},{
"primit":'''class_def = nodes.NODE_CLASS_MAPPINGS[class_type]
if unique_id in outputs:
return (True, None, None)''',
"edit":'''class_def = nodes.NODE_CLASS_MAPPINGS[class_type]
if unique_id in outputs:
return (True, None, None)
#循环添加代码----------开始----------
startNum=None
startData={}
delKeys=[]
backhaul={}
clTypes=['ForInnerEnd','IfInnerExecute','DoWhileEnd']
oldPrompt=None
if class_type in clTypes:
oldPrompt=copy.deepcopy(prompt)
if class_type=='ForInnerEnd':
startNum=prompt[unique_id]['inputs']['total'][0]
inputNum=prompt[unique_id]['inputs']['obj'][0]
maxKeyStr=sorted(list(prompt.keys()), key=lambda x: int(x.split(':')[0]))[-1]
maxKey = int(maxKeyStr.split(':')[0])
delKeys=list(set(get_del_keys(startNum,prompt,[startNum,unique_id])))
delKeys.append(startNum)
delKeys = list(filter(lambda x: x != inputNum, delKeys))
for key in delKeys:
outputs.pop(key, None)
startInput=prompt[startNum]['inputs']
if isinstance(startInput['total'],list) or isinstance(startInput['stop'],list) or isinstance(startInput['i'],list):
result = recursive_execute(server, prompt, outputs, startNum, extra_data, executed, prompt_id, outputs_ui, object_storage)
if result[0] is not True:
return result
for x in startInput:
input_data = startInput[x]
if isinstance(input_data, list):
startData[x]=outputs[input_data[0]][input_data[1]][0]
else:
startData[x]=input_data
for i in range(startData['i']+1,startData['total'],startData['stop']):
prompt[str(maxKey+i)]=prompt[inputNum]
prompt[unique_id]['inputs']['obj'+str(i)]=[str(maxKey+i),prompt[unique_id]['inputs']['obj'][-1]]
if i==startData['i']+1:
backhaul['obj'+str(i)]=prompt[unique_id]['inputs']['obj']
else:
backhaul['obj'+str(i)]=[str(maxKey+i-startData['stop']),prompt[unique_id]['inputs']['obj'][-1]]
elif class_type=='DoWhileEnd':
startNum=prompt[unique_id]['inputs']['start'][0]
inputNum=prompt[unique_id]['inputs']['ANY'][0]
delKeys=list(set(get_del_keys(startNum,prompt,[startNum,unique_id])))
delKeys.append(startNum)
delKeys.append(inputNum)
#循环添加代码-----------结束---------'''
}
,{
"primit":'''result = recursive_execute(server, prompt, outputs, input_unique_id, extra_data, executed, prompt_id, outputs_ui, object_storage)
if result[0] is not True:
# Another node failed further upstream
return result''',
"edit":'''#循环添加代码----------开始----------
if class_type=='ForInnerEnd' and x !='obj' and x.startswith('obj'):
if startNum!=None:
if isinstance(prompt[startNum]['inputs']['i'],list):
prompt[startNum]['inputs']['i']=startData['i']+startData['stop']
else:
prompt[startNum]['inputs']['i']=prompt[startNum]['inputs']['i']+startData['stop']
prompt[startNum]['inputs']['obj']=backhaul[x]
for key in delKeys:
outputs.pop(key, None)
#循环添加代码-----------结束---------
result = recursive_execute(server, prompt, outputs, input_unique_id, extra_data, executed, prompt_id, outputs_ui, object_storage)
if result[0] is not True:
# Another node failed further upstream
return result
#判断选择添加代码-----------开始---------
if class_type=='DoWhileEnd' and x == 'ANY':
any=outputs[inputNum][prompt[unique_id]['inputs']['ANY'][1]][0]
i=0
while any:
for key in delKeys:
outputs.pop(key, None)
i=i+1
outputs['i']=[[i]]
prompt[startNum]['inputs']['i']=['i',0]
result = recursive_execute(server, prompt, outputs, input_unique_id, extra_data, executed, prompt_id, outputs_ui, object_storage)
if result[0] is not True:
return result
any=outputs[inputNum][prompt[unique_id]['inputs']['ANY'][1]][0]
if class_type=='IfInnerExecute' and x=='ANY':
if outputs[input_unique_id][output_index][0]:
inputs['IF_FALSE']=oldPrompt[unique_id]['inputs']['IF_TRUE']
else:
inputs['IF_TRUE']=oldPrompt[unique_id]['inputs']['IF_FALSE']
#判断选择添加代码-----------结束---------'''
}
,{
"primit":'''output_data, output_ui = get_output_data(obj, input_data_all)
outputs[unique_id] = output_data''',
"edit":'''output_data, output_ui = get_output_data(obj, input_data_all)
outputs[unique_id] = output_data
#循环添加代码----------开始----------
if class_type=='ForInnerEnd' or class_type=='DoWhileEnd':
prompt[startNum]['inputs']=oldPrompt[startNum]['inputs']
prompt[unique_id]['inputs']=oldPrompt[unique_id]['inputs']
outputs.pop(startNum, None)
result = recursive_execute(server, prompt, outputs, startNum, extra_data, executed, prompt_id, outputs_ui, object_storage)
if result[0] is not True:
return result
if class_type=='IfInnerExecute':
prompt[unique_id]['inputs']=oldPrompt[unique_id]['inputs']
#循环添加代码-----------结束---------'''
}
,{
"primit":'''def validate_prompt(prompt):
outputs = set()''',
"edit":'''def validate_prompt(prompt):
outputs = set()
inputKeys=[]
for k,v in prompt.items():
for k1,v1 in v['inputs'].items():
if type(v1)==list and len(v1)==2:
inputKeys.append(v1[0])'''
}
,{
"primit":'''if hasattr(class_, 'OUTPUT_NODE') and class_.OUTPUT_NODE is True:''',
"edit":'''if hasattr(class_, 'OUTPUT_NODE') and class_.OUTPUT_NODE is True and x not in inputKeys: #添加and x not in inputKeys'''
}
]
}
]
filePath=os.path.dirname(os.path.abspath(__file__))
print('当前目录:',filePath)
base_path=filePath.split('custom_nodes')[0]
backupPath=os.path.join(filePath,'backup')
def read_py_file(file_path):
with open(file_path, 'r',encoding='utf-8') as file:
text = file.read()
return text
def write_py_file(file_path, text):
with open(file_path, 'w',encoding='utf-8') as file:
file.write(text)
if __name__ == '__main__':
isRestore='--res' in sys.argv
print('-------开始还原-------' if isRestore else '--------开始修改--------')
for i in editlist:
file_path=os.path.join(base_path,i['name'])
if not os.path.exists(file_path):
print(file_path+'文件不存在')
continue
if not os.path.exists(backupPath):
os.makedirs(backupPath)
if isRestore:
if not os.path.exists(os.path.join(backupPath,i['name'])):
print('备份文件'+os.path.join(backupPath,i['name'])+'不存在无法还原')
continue
text=read_py_file(os.path.join(backupPath,i['name']))
write_py_file(file_path, text)
else:
text=read_py_file(file_path)
if text.find(i['isEditStr'])!=-1:
continue
write_py_file(os.path.join(backupPath,i['name']), text)
textOld=text+''
for j in i['changs']:
if text.find(j['primit'])==-1:
print(i['name']+'未找到修改位置,修改失败')
text=textOld
break
text=text.replace(j['primit'],j['edit'])
write_py_file(file_path, text)
print('-------还原完成-------' if isRestore else '--------修改完成--------')
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+292
View File
@@ -0,0 +1,292 @@
#redis:
# host: 127.0.0.1
# port: 6379
# password:
# basePath: 192.168.1.8:8188 #comfyui地址
# isMain: true #是否主comfyUI
# modelPriority: false #是否模型优先
wechat:
appid: wxa50ed345b253964d #公众号appid
secret: 327ac9ba06d214d2fcfe2e9ed0520684 #公众号秘钥
serverAddress: http://119.123.205.233/ #服务器地址 微信公众配置地址的前缀
freeSize: 3 #免费使用次数
isEnterprise: true #是否为企业微信
adminNo: b0pOVFM2dnRsZkdLeWl2Zlk2bG9MTFNjUTNGUQ== #管理员微信编号启动会后可发送我的编号获取
adminWeChat: yanlang123456 #管理员微信号
access_token: '' #不用填系统用于保存临时值
access_token_expires_at: 0 #默认0
commands: #功能列表
简笔画:
filename: line2drawing.json
type: paint-board
params:
ckpt:
isRequired: false
keys:
- '1'
- inputs
- ckpt_name
options: #选项参数
卡通动画片SD1.5: yamer_Cartoon_xenoArcadiaCD.safetensors
冷却混合料SD1.5: chilloutmix_NiPrunedFp32Fix.safetensors #中文名称:英文名称
梦想塑造者SD1.5: dreamshaper_8.safetensors
zhName: 基础模型
prompt:
isRequired: false
keys:
- '17'
- inputs
- text_trans
zhName: 正向提示词
seed:
default: -1
isRequired: true
keys:
- '8'
- inputs
- seed
type: text
zhName: 随机种子
image:
isRequired: true
keys:
- '15'
- inputs
- image
type: text
zhName: 输入图片
水彩画:
filename: color2painting.json
type: paint-board
params:
ckpt:
isRequired: false
keys:
- '4'
- inputs
- ckpt_name
options: #选项参数
卡通动画片SD1.5: yamer_Cartoon_xenoArcadiaCD.safetensors
冷却混合料SD1.5: chilloutmix_NiPrunedFp32Fix.safetensors #中文名称:英文名称
梦想塑造者SD1.5: dreamshaper_8.safetensors
zhName: 基础模型
prompt:
isRequired: false
keys:
- '27'
- inputs
- text_trans
zhName: 正向提示词
seed:
default: -1
isRequired: true
keys:
- '3'
- inputs
- seed
type: text
zhName: 随机种子
denoise:
default: 0.65 #默认值
isRequired: false
keys:
- '3'
- inputs
- denoise
max: 100 #最大值
min: 0 #最小值
original: 1 #原始最大值(用来换算滑块比例)
type: number #类型(输入型参数目前只支持 number/text)
zhName: 重绘幅度
image:
isRequired: true
keys:
- '25'
- inputs
- image
type: text
zhName: 输入图片
图片修改:
filename: imageEdit.json
params:
image:
isRequired: true
keys:
- '48'
- inputs
- image_path
zhName: 图片
prompt:
isRequired: true
keys:
- '47'
- inputs
- text_trans
zhName: 正向提示词
seed:
default: -1
isRequired: true
keys:
- '37:1'
- inputs
- noise_seed
type: text
zhName: 随机种子
replyText: 请发送您的图片发送正向提示词用“ok”结尾开始任务,如:“漫画风格ok”
风格转绘:
filename: img2imgStyle.json
replyText: 请上传需转绘的图片和参考风格图发送“ok”开始任务,如:“ok” #指令说明回复
params:
image: #参数名称(几个固定参数名称必须如下:图片:image 随机种子:seed 正向提示词:prompt 反向提示词:negative)
isRequired: true #是否必填
keys: #参数对应json中对应的key
- '110'
- inputs
- image_path
zhName: 待转绘图
type: image
styleImage: #参数名称(几个固定参数名称必须如下:图片:image 随机种子:seed 正向提示词:prompt 反向提示词:negative)
isRequired: true #是否必填
keys: #参数对应json中对应的key
- '111'
- inputs
- image_path
zhName: 风格参考图
type: image
default: 发送图片
文生二维码:
filename: text2qrcode.json
replyText: 请发送二维码内容,和提示词发送“ok”开始任务,如:“花朵ok” #指令说明回复
params:
prompt:
isRequired: true
keys:
- '75'
- inputs
- text_trans
zhName: 正向提示词
seed:
default: -1
isRequired: true
keys:
- '3'
- inputs
- seed
type: text
zhName: 随机种子
qrText:
default: "https://u.wechat.com/EEy4mPvfXdYO-MvStE4S2m0"
isRequired: true
keys:
- '16'
- inputs
- text
type: text
zhName: 二维码内容
图生视频:
filename: img2video.json #功能流程图json文件
params: #参数列表
image: #参数名称(几个固定参数名称必须如下:图片:image 随机种子:seed 正向提示词:prompt 反向提示词:negative)
isRequired: true #是否必填
keys: #参数对应json中对应的key
- '38'
- inputs
- image_path
zhName: 图片 #参数中文名称
motion_bucket:
default: 127
isRequired: true
keys:
- '12'
- inputs
- motion_bucket_id
max: 200
min: 0
type: number
zhName: 运动量
seed:
default: -1
isRequired: true
keys:
- '3'
- inputs
- seed
type: text
zhName: 随机种子
replyText: 请发送您的图片发送“ok”开始任务,如:“ok” #指令说明回复
文生图:
filename: text2img.json
params:
ckpt:
isRequired: false
keys:
- '4'
- inputs
- ckpt_name
options: #选项参数
冷却混合料SD1.5: chilloutmix_NiPrunedFp32Fix.safetensors #中文名称:英文名称
卡通动画片SD1.5: yamer_Cartoon_xenoArcadiaCD.safetensors
无法控制的SDXL: juggernautXL_version6Rundiffusion.safetensors
梦想塑造者SD1.5: dreamshaper_8.safetensors
zhName: 基础模型
height:
default: 512 #默认值
isRequired: false
keys:
- '14'
- inputs
- height
max: 1024 #最大值
min: 512 #最小值
type: number #类型(输入型参数目前只支持 number/text)
zhName: 高度
lora:
isRequired: false
keys:
- '10'
- inputs
- lora_name
options:
SD1.5-LCM加速: lcm-lora-sdv1-5.safetensors
SDXL-LCM加速: lcm-lora-sdxl.safetensors
zhName: LoRA
negative:
isRequired: false
keys:
- '7'
- inputs
- text
zhName: 反向提示词
prompt:
isRequired: true
keys:
- '20'
- inputs
- text_trans
zhName: 正向提示词
seed:
default: -1
isRequired: true
keys:
- '3'
- inputs
- seed
type: text
zhName: 随机种子
width:
default: 512
isRequired: false
keys:
- '14'
- inputs
- width
max: 1024
min: 512
type: number
zhName: 宽度
replyText: 请发送您的中文或英文提示词用“ok”结束,如:“一只小狗ok”
query_commands: #查询指令(固定)
- 查询排队情况
- 我的编号
+538
View File
@@ -0,0 +1,538 @@
{
"3": {
"inputs": {
"seed": 2070780056835,
"steps": 8,
"cfg": 1,
"sampler_name": "euler_ancestral",
"scheduler": "normal",
"denoise": 0.65,
"model": [
"21",
0
],
"positive": [
"6",
0
],
"negative": [
"7",
0
],
"latent_image": [
"19",
0
]
},
"class_type": "KSampler",
"_meta": {
"title": "K采样器"
}
},
"4": {
"inputs": {
"ckpt_name": "yamer_Cartoon_xenoArcadiaCD.safetensors"
},
"class_type": "CheckpointLoaderSimple",
"_meta": {
"title": "Checkpoint加载器(简易)"
}
},
"6": {
"inputs": {
"text": [
"27",
0
],
"clip": [
"21",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码器"
}
},
"7": {
"inputs": {
"text": [
"28",
0
],
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码器"
}
},
"8": {
"inputs": {
"samples": [
"3",
0
],
"vae": [
"4",
2
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"10": {
"inputs": {
"lora_name": "lcm-lora-sdv1-5.safetensors",
"strength_model": 1,
"strength_clip": 1,
"model": [
"4",
0
],
"clip": [
"4",
1
]
},
"class_type": "LoraLoader",
"_meta": {
"title": "LoRA加载器"
}
},
"19": {
"inputs": {
"pixels": [
"25",
0
],
"vae": [
"23",
0
]
},
"class_type": "VAEEncode",
"_meta": {
"title": "VAE编码"
}
},
"21": {
"inputs": {
"lora_name": "可爱少女厚涂风.safetensors",
"strength_model": 0,
"strength_clip": 1,
"model": [
"10",
0
],
"clip": [
"10",
1
]
},
"class_type": "LoraLoader",
"_meta": {
"title": "LoRA加载器"
}
},
"23": {
"inputs": {
"vae_name": "clearvae_v23.safetensors"
},
"class_type": "VAELoader",
"_meta": {
"title": "VAE加载器"
}
},
"25": {
"inputs": {
"image": ""
},
"class_type": "ETN_LoadImageBase64",
"_meta": {
"title": "Load Image (Base64)"
}
},
"27": {
"inputs": {
"text_trans": ""
},
"class_type": "ZhPromptTranslator",
"_meta": {
"title": "正向提示词"
}
},
"28": {
"inputs": {
"text_trans": ""
},
"class_type": "ZhPromptTranslator",
"_meta": {
"title": "反向提示词"
}
},
"29": {
"inputs": {
"seed": 19,
"steps": 25,
"cfg": 6,
"sampler_name": "dpmpp_2m",
"scheduler": "karras",
"denoise": 0.9,
"model": [
"36",
0
],
"positive": [
"48",
0
],
"negative": [
"48",
1
],
"latent_image": [
"48",
2
]
},
"class_type": "KSampler",
"_meta": {
"title": "K采样器"
}
},
"31": {
"inputs": {
"text": [
"52",
0
],
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码器"
}
},
"32": {
"inputs": {
"text": "blurry, noisy, messy, glitch, distorted, malformed, ill, horror, naked, nipples",
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码器"
}
},
"33": {
"inputs": {
"samples": [
"29",
0
],
"vae": [
"4",
2
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"34": {
"inputs": {
"images": [
"56",
0
]
},
"class_type": "PreviewImage",
"_meta": {
"title": "预览图像"
}
},
"36": {
"inputs": {
"weight_style": 1,
"weight_composition": 1,
"expand_style": false,
"combine_embeds": "concat",
"start_at": 0,
"end_at": 1,
"embeds_scaling": "V only",
"model": [
"37",
0
],
"ipadapter": [
"37",
1
],
"image_style": [
"39",
0
],
"image_composition": [
"47",
0
]
},
"class_type": "IPAdapterStyleComposition",
"_meta": {
"title": "IPAdapter风格合成SDXL"
}
},
"37": {
"inputs": {
"preset": "STANDARD (medium strength)",
"model": [
"4",
0
]
},
"class_type": "IPAdapterUnifiedLoader",
"_meta": {
"title": "IPAdapter加载器"
}
},
"39": {
"inputs": {
"interpolation": "LANCZOS",
"crop_position": "pad",
"sharpening": 0,
"image": [
"8",
0
]
},
"class_type": "PrepImageForClipVision",
"_meta": {
"title": "CLIP视觉图像处理"
}
},
"40": {
"inputs": {
"strength": 0.55,
"start_percent": 0,
"end_percent": 0.75,
"positive": [
"31",
0
],
"negative": [
"32",
0
],
"control_net": [
"41",
0
],
"image": [
"49",
0
]
},
"class_type": "ControlNetApplyAdvanced",
"_meta": {
"title": "ControlNet应用(高级)"
}
},
"41": {
"inputs": {
"control_net_name": "t2i-adapter-lineart-sdxl-1.0.safetensors"
},
"class_type": "ControlNetLoader",
"_meta": {
"title": "ControlNet加载器"
}
},
"43": {
"inputs": {
"strength": 1,
"start_percent": 0,
"end_percent": 0.3,
"positive": [
"40",
0
],
"negative": [
"40",
1
],
"control_net": [
"44",
0
],
"image": [
"45",
0
]
},
"class_type": "ControlNetApplyAdvanced",
"_meta": {
"title": "ControlNet应用(高级)"
}
},
"44": {
"inputs": {
"control_net_name": "diffusers_xl_depth_mid.safetensors"
},
"class_type": "ControlNetLoader",
"_meta": {
"title": "ControlNet加载器"
}
},
"45": {
"inputs": {
"ckpt_name": "depth_anything_vits14.pth",
"resolution": 960,
"image": [
"55",
0
]
},
"class_type": "DepthAnythingPreprocessor",
"_meta": {
"title": "DA深度预处理器"
}
},
"47": {
"inputs": {
"interpolation": "LANCZOS",
"crop_position": "pad",
"sharpening": 0,
"image": [
"55",
0
]
},
"class_type": "PrepImageForClipVision",
"_meta": {
"title": "CLIP视觉图像处理"
}
},
"48": {
"inputs": {
"positive": [
"43",
0
],
"negative": [
"43",
1
],
"vae": [
"4",
2
],
"pixels": [
"55",
0
]
},
"class_type": "InstructPixToPixConditioning",
"_meta": {
"title": "InstructPixToPix条件"
}
},
"49": {
"inputs": {
"coarse": "disable",
"resolution": 512,
"image": [
"55",
0
]
},
"class_type": "LineArtPreprocessor",
"_meta": {
"title": "LineArt艺术线预处理器"
}
},
"52": {
"inputs": {
"model": "wd-v1-4-moat-tagger-v2",
"threshold": 0.35,
"character_threshold": 0.85,
"replace_underscore": "",
"trailing_comma": "1girl, solo, long_hair, looking_at_viewer, shirt, black_hair, upper_body, black_eyes, lips, realistic",
"exclude_tags": "",
"image": [
"55",
0
]
},
"class_type": "WD14Tagger|pysssss",
"_meta": {
"title": "WD14反推提示词"
}
},
"55": {
"inputs": {
"image_path": [
"59",
0
],
"RGBA": "false",
"filename_text_extension": "true"
},
"class_type": "LamLoadPathImage",
"_meta": {
"title": "加载网络图片或本地图片"
}
},
"56": {
"inputs": {
"ANY": [
"60",
0
],
"IF_TRUE": [
"33",
0
],
"IF_FALSE": [
"8",
0
]
},
"class_type": "IfInnerExecute",
"_meta": {
"title": "判断选择"
}
},
"59": {
"inputs": {
"text": ""
},
"class_type": "TextInput_",
"_meta": {
"title": "文本"
}
},
"60": {
"inputs": {
"expression": "len(p0)>0",
"p0": [
"59",
0
]
},
"class_type": "MultiParamFormula",
"_meta": {
"title": "多参代码表达式"
}
}
}
+262
View File
@@ -0,0 +1,262 @@
{
"4": {
"inputs": {
"ckpt_name": "cosxl_edit.safetensors"
},
"class_type": "CheckpointLoaderSimple",
"_meta": {
"title": "Checkpoint加载器(简易)"
}
},
"6": {
"inputs": {
"text": [
"47",
0
],
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码器"
}
},
"7": {
"inputs": {
"text": "",
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码器"
}
},
"8": {
"inputs": {
"samples": [
"37:8",
0
],
"vae": [
"4",
2
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"9": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"8",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "保存图像"
}
},
"41": {
"inputs": {
"width": 960,
"height": 0,
"interpolation": "nearest",
"keep_proportion": true,
"condition": "always",
"image": [
"48",
0
]
},
"class_type": "ImageResize+",
"_meta": {
"title": "图像缩放"
}
},
"42": {
"inputs": {
"image": [
"48",
0
]
},
"class_type": "GetImage_(Width&Height) _O",
"_meta": {
"title": "GetImage_(Width&Height) _O"
}
},
"44": {
"inputs": {
"expression": "p0>960",
"p0": [
"42",
0
]
},
"class_type": "MultiParamFormula",
"_meta": {
"title": "多参代码表达式"
}
},
"46": {
"inputs": {
"ANY": [
"44",
0
],
"IF_TRUE": [
"41",
0
],
"IF_FALSE": [
"48",
0
]
},
"class_type": "IfInnerExecute",
"_meta": {
"title": "判断选择"
}
},
"47": {
"inputs": {
"text_trans": "秋天"
},
"class_type": "ZhPromptTranslator",
"_meta": {
"title": "中文翻译"
}
},
"48": {
"inputs": {
"image_path": "./ComfyUI/input/example.png",
"RGBA": "false",
"filename_text_extension": "true"
},
"class_type": "LamLoadPathImage",
"_meta": {
"title": "加载网络图片或本地图片"
}
},
"37:0": {
"inputs": {
"sampler_name": "euler"
},
"class_type": "KSamplerSelect",
"_meta": {
"title": "K采样器选择"
}
},
"37:1": {
"inputs": {
"noise_seed": 173148692406184
},
"class_type": "RandomNoise",
"_meta": {
"title": "随机噪波"
}
},
"37:3": {
"inputs": {
"scheduler": "normal",
"steps": 20,
"denoise": 1,
"model": [
"4",
0
]
},
"class_type": "BasicScheduler",
"_meta": {
"title": "基础调度器"
}
},
"37:6": {
"inputs": {
"positive": [
"6",
0
],
"negative": [
"7",
0
],
"vae": [
"4",
2
],
"pixels": [
"46",
0
]
},
"class_type": "InstructPixToPixConditioning",
"_meta": {
"title": "InstructPixToPix条件"
}
},
"37:7": {
"inputs": {
"cfg_conds": 5,
"cfg_cond2_negative": 1.5,
"model": [
"4",
0
],
"cond1": [
"37:6",
0
],
"cond2": [
"37:6",
1
],
"negative": [
"7",
0
]
},
"class_type": "DualCFGGuider",
"_meta": {
"title": "双CFG引导"
}
},
"37:8": {
"inputs": {
"noise": [
"37:1",
0
],
"guider": [
"37:7",
0
],
"sampler": [
"37:0",
0
],
"sigmas": [
"37:3",
0
],
"latent_image": [
"37:6",
2
]
},
"class_type": "SamplerCustomAdvanced",
"_meta": {
"title": "自定义采样器(高级)"
}
}
}
+651
View File
@@ -0,0 +1,651 @@
{
"3": {
"inputs": {
"seed": 20,
"steps": 25,
"cfg": 6,
"sampler_name": "dpmpp_2m",
"scheduler": "karras",
"denoise": 0.9,
"model": [
"105",
0
],
"positive": [
"64",
0
],
"negative": [
"64",
1
],
"latent_image": [
"64",
2
]
},
"class_type": "KSampler",
"_meta": {
"title": "K采样器"
}
},
"4": {
"inputs": {
"ckpt_name": "dreamshaper_8.safetensors"
},
"class_type": "CheckpointLoaderSimple",
"_meta": {
"title": "Checkpoint加载器(简易)"
}
},
"6": {
"inputs": {
"text": [
"72",
0
],
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码器"
}
},
"7": {
"inputs": {
"text": "blurry, noisy, messy, glitch, distorted, malformed, ill, horror, naked, nipples",
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码器"
}
},
"8": {
"inputs": {
"samples": [
"3",
0
],
"vae": [
"4",
2
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"29": {
"inputs": {
"width": 960,
"height": 0,
"interpolation": "nearest",
"keep_proportion": true,
"condition": "always",
"multiple_of": 0,
"image": [
"110",
0
]
},
"class_type": "ImageResize+",
"_meta": {
"title": "图像缩放"
}
},
"48": {
"inputs": {
"preset": "STANDARD (medium strength)",
"model": [
"4",
0
]
},
"class_type": "IPAdapterUnifiedLoader",
"_meta": {
"title": "IPAdapter加载器"
}
},
"50": {
"inputs": {
"interpolation": "LANCZOS",
"crop_position": "pad",
"sharpening": 0,
"image": [
"111",
0
]
},
"class_type": "PrepImageForClipVision",
"_meta": {
"title": "CLIP视觉图像处理"
}
},
"51": {
"inputs": {
"strength": 0.55,
"start_percent": 0,
"end_percent": 0.75,
"positive": [
"6",
0
],
"negative": [
"7",
0
],
"control_net": [
"52",
0
],
"image": [
"83",
0
]
},
"class_type": "ControlNetApplyAdvanced",
"_meta": {
"title": "ControlNet应用(高级)"
}
},
"52": {
"inputs": {
"control_net_name": "control_v11p_sd15_lineart.pth"
},
"class_type": "ControlNetLoader",
"_meta": {
"title": "ControlNet加载器"
}
},
"55": {
"inputs": {
"strength": 1,
"start_percent": 0,
"end_percent": 0.3,
"positive": [
"51",
0
],
"negative": [
"51",
1
],
"control_net": [
"56",
0
],
"image": [
"57",
0
]
},
"class_type": "ControlNetApplyAdvanced",
"_meta": {
"title": "ControlNet应用(高级)"
}
},
"56": {
"inputs": {
"control_net_name": "control_v11f1p_sd15_depth.pth"
},
"class_type": "ControlNetLoader",
"_meta": {
"title": "ControlNet加载器"
}
},
"57": {
"inputs": {
"ckpt_name": "depth_anything_vits14.pth",
"resolution": 960,
"image": [
"71",
0
]
},
"class_type": "DepthAnythingPreprocessor",
"_meta": {
"title": "DA深度预处理器"
}
},
"63": {
"inputs": {
"interpolation": "LANCZOS",
"crop_position": "pad",
"sharpening": 0,
"image": [
"71",
0
]
},
"class_type": "PrepImageForClipVision",
"_meta": {
"title": "CLIP视觉图像处理"
}
},
"64": {
"inputs": {
"positive": [
"55",
0
],
"negative": [
"55",
1
],
"vae": [
"4",
2
],
"pixels": [
"71",
0
]
},
"class_type": "InstructPixToPixConditioning",
"_meta": {
"title": "InstructPixToPix条件"
}
},
"69": {
"inputs": {
"image": [
"110",
0
]
},
"class_type": "GetImage_(Width&Height) _O",
"_meta": {
"title": "GetImage_(Width&Height) _O"
}
},
"70": {
"inputs": {
"expression": "p0>960",
"p0": [
"69",
0
]
},
"class_type": "MultiParamFormula",
"_meta": {
"title": "多参代码表达式"
}
},
"71": {
"inputs": {
"ANY": [
"70",
0
],
"IF_TRUE": [
"29",
0
],
"IF_FALSE": [
"110",
0
]
},
"class_type": "IfInnerExecute",
"_meta": {
"title": "判断选择"
}
},
"72": {
"inputs": {
"model": "wd-v1-4-moat-tagger-v2",
"threshold": 0.35,
"character_threshold": 1,
"exclude_tags": "",
"image": [
"71",
0
]
},
"class_type": "WD14Tagger|pysssss",
"_meta": {
"title": "WD14反推提示词"
}
},
"76": {
"inputs": {
"preset": "PLUS FACE (portraits)",
"model": [
"103",
0
]
},
"class_type": "IPAdapterUnifiedLoader",
"_meta": {
"title": "IPAdapter加载器"
}
},
"83": {
"inputs": {
"coarse": "disable",
"image": [
"71",
0
]
},
"class_type": "LineArtPreprocessor",
"_meta": {
"title": "LineArt艺术线预处理器"
}
},
"86": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"8",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "保存图像"
}
},
"88": {
"inputs": {
"expression": "p0[p1:p1+1]",
"p0": [
"108",
0
],
"p1": [
"89",
1
]
},
"class_type": "MultiParamFormula",
"_meta": {
"title": "多参代码表达式"
}
},
"89": {
"inputs": {
"total": [
"90",
0
],
"stop": 1,
"i": 0
},
"class_type": "ForInnerStart",
"_meta": {
"title": "计次内循环首"
}
},
"90": {
"inputs": {
"expression": "len(p0)",
"p0": [
"108",
1
]
},
"class_type": "MultiParamFormula",
"_meta": {
"title": "多参代码表达式"
}
},
"91": {
"inputs": {
"expression": "p0[p1]",
"p0": [
"108",
1
],
"p1": [
"89",
1
]
},
"class_type": "MultiParamFormula",
"_meta": {
"title": "多参代码表达式"
}
},
"92": {
"inputs": {
"total": [
"89",
0
],
"obj": [
"97",
0
]
},
"class_type": "ForInnerEnd",
"_meta": {
"title": "计次内循环尾"
}
},
"94": {
"inputs": {
"ANY": [
"95",
0
],
"IF_TRUE": [
"96",
0
],
"IF_FALSE": [
"89",
3
]
},
"class_type": "IfInnerExecute",
"_meta": {
"title": "判断选择"
}
},
"95": {
"inputs": {
"expression": "p0==0",
"p0": [
"89",
1
]
},
"class_type": "MultiParamFormula",
"_meta": {
"title": "多参代码表达式"
}
},
"96": {
"inputs": {
"preset": "FACEID PLUS V2",
"lora_strength": 0.39,
"provider": "CPU",
"model": [
"76",
0
],
"ipadapter": [
"76",
1
]
},
"class_type": "IPAdapterUnifiedLoaderFaceID",
"_meta": {
"title": "IPAdapterFaceID加载器"
}
},
"97": {
"inputs": {
"weight": 1,
"weight_faceidv2": 2,
"weight_type": "linear",
"combine_embeds": "concat",
"start_at": 0,
"end_at": 1,
"embeds_scaling": "V only",
"model": [
"94",
0
],
"ipadapter": [
"96",
1
],
"image": [
"88",
0
],
"attn_mask": [
"91",
0
]
},
"class_type": "IPAdapterFaceID",
"_meta": {
"title": "应用IPAdapterFaceID"
}
},
"102": {
"inputs": {
"weight": 1,
"weight_type": "style transfer",
"combine_embeds": "concat",
"start_at": 0,
"end_at": 1,
"embeds_scaling": "V only",
"model": [
"48",
0
],
"ipadapter": [
"48",
1
],
"image": [
"50",
0
]
},
"class_type": "IPAdapterAdvanced",
"_meta": {
"title": "应用IPAdapter(高级)"
}
},
"103": {
"inputs": {
"weight": 1,
"weight_type": "composition",
"combine_embeds": "concat",
"start_at": 0,
"end_at": 1,
"embeds_scaling": "V only",
"model": [
"102",
0
],
"ipadapter": [
"48",
1
],
"image": [
"63",
0
]
},
"class_type": "IPAdapterAdvanced",
"_meta": {
"title": "应用IPAdapter(高级)"
}
},
"104": {
"inputs": {
"expression": "len(p0)>0",
"p0": [
"108",
1
]
},
"class_type": "MultiParamFormula",
"_meta": {
"title": "多参代码表达式"
}
},
"105": {
"inputs": {
"ANY": [
"104",
0
],
"IF_TRUE": [
"92",
1
],
"IF_FALSE": [
"103",
0
]
},
"class_type": "IfInnerExecute",
"_meta": {
"title": "判断选择"
}
},
"108": {
"inputs": {
"crop_padding_factor": 0.25,
"analysis_models": [
"109",
0
],
"image": [
"71",
0
]
},
"class_type": "ImageCropFaces",
"_meta": {
"title": "多人面部裁剪"
}
},
"109": {
"inputs": {
"library": "insightface",
"provider": "CPU"
},
"class_type": "FaceAnalysisModels",
"_meta": {
"title": "面部分析模型"
}
},
"110": {
"inputs": {
"image_path": "./ComfyUI/input/example.png",
"RGBA": "false",
"filename_text_extension": "true"
},
"class_type": "LamLoadPathImage",
"_meta": {
"title": "需转绘的图"
}
},
"111": {
"inputs": {
"image_path": "./ComfyUI/input/example.png",
"RGBA": "false",
"filename_text_extension": "true"
},
"class_type": "LamLoadPathImage",
"_meta": {
"title": "风格参考图片"
}
}
}
+142
View File
@@ -0,0 +1,142 @@
{
"3": {
"inputs": {
"seed": 801899397770458,
"steps": 4,
"cfg": 1,
"sampler_name": "lcm",
"scheduler": "karras",
"denoise": 1,
"model": [
"14",
0
],
"positive": [
"12",
0
],
"negative": [
"12",
1
],
"latent_image": [
"12",
2
]
},
"class_type": "KSampler"
},
"8": {
"inputs": {
"samples": [
"3",
0
],
"vae": [
"15",
2
]
},
"class_type": "VAEDecode"
},
"12": {
"inputs": {
"width": [
"30",
0
],
"height": [
"30",
1
],
"video_frames": 25,
"motion_bucket_id": 127,
"fps": 6,
"augmentation_level": 0,
"clip_vision": [
"15",
1
],
"init_image": [
"38",
0
],
"vae": [
"15",
2
]
},
"class_type": "SVD_img2vid_Conditioning"
},
"14": {
"inputs": {
"min_cfg": 1,
"model": [
"33",
0
]
},
"class_type": "VideoLinearCFGGuidance"
},
"15": {
"inputs": {
"ckpt_name": "AnimateLCM-SVD-xt-1-1_fp16_comfy.safetensors"
},
"class_type": "ImageOnlyCheckpointLoader"
},
"25": {
"inputs": {
"frame_rate": 8,
"loop_count": 0,
"filename_prefix": "AnimateDiff",
"format": "video/h265-mp4",
"pingpong": false,
"save_image": true,
"Sync playback": null,
"images": [
"8",
0
]
},
"class_type": "VHS_VideoCombine"
},
"30": {
"inputs": {
"image": [
"31",
0
]
},
"class_type": "Get image size"
},
"31": {
"inputs": {
"upscale_method": "nearest-exact",
"scale_by": 0.7000000000000001,
"image": [
"38",
0
]
},
"class_type": "ImageScaleBy"
},
"33": {
"inputs": {
"lora_name": "lcm-lora-sdv1-5.safetensors",
"strength_model": 1,
"model": [
"15",
0
]
},
"class_type": "LoraLoaderModelOnly"
},
"38": {
"inputs": {
"image_path": "http://127.0.0.1:8188/view?filename=ComfyUI_00402_.png&type=input&subfolder=&rand=0.9232966295130483",
"RGBA": "false",
"filename_text_extension": "true"
},
"class_type": "LamLoadPathImage"
}
}
+239
View File
@@ -0,0 +1,239 @@
{
"1": {
"inputs": {
"ckpt_name": "yamer_Cartoon_xenoArcadiaCD.safetensors"
},
"class_type": "CheckpointLoaderSimple",
"_meta": {
"title": "Checkpoint加载器(简易)"
}
},
"2": {
"inputs": {
"width": [
"16",
0
],
"height": [
"16",
1
],
"batch_size": 1
},
"class_type": "EmptyLatentImage",
"_meta": {
"title": "空Latent"
}
},
"3": {
"inputs": {
"text": [
"17",
0
],
"clip": [
"1",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码器"
}
},
"4": {
"inputs": {
"text": [
"18",
0
],
"clip": [
"1",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码器"
}
},
"6": {
"inputs": {
"control_net_name": "control_v11p_sd15_lineart.pth"
},
"class_type": "ControlNetLoader",
"_meta": {
"title": "ControlNet加载器"
}
},
"7": {
"inputs": {
"strength": 1,
"conditioning": [
"3",
0
],
"control_net": [
"6",
0
],
"image": [
"22",
0
]
},
"class_type": "ControlNetApply",
"_meta": {
"title": "ControlNet应用"
}
},
"8": {
"inputs": {
"seed": 275540587861329,
"steps": 8,
"cfg": 1,
"sampler_name": "lcm",
"scheduler": "karras",
"denoise": 1,
"model": [
"14",
0
],
"positive": [
"7",
0
],
"negative": [
"4",
0
],
"latent_image": [
"2",
0
]
},
"class_type": "KSampler",
"_meta": {
"title": "K采样器"
}
},
"9": {
"inputs": {
"samples": [
"8",
0
],
"vae": [
"1",
2
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"14": {
"inputs": {
"lora_name": "lcm-lora-sdv1-5.safetensors",
"strength_model": 1,
"model": [
"1",
0
]
},
"class_type": "LoraLoaderModelOnly",
"_meta": {
"title": "LoRA加载器(仅模型)"
}
},
"15": {
"inputs": {
"image": ""
},
"class_type": "ETN_LoadImageBase64",
"_meta": {
"title": "Load Image (Base64)"
}
},
"16": {
"inputs": {
"image": [
"22",
0
]
},
"class_type": "GetImageSize",
"_meta": {
"title": "GetImageSize"
}
},
"17": {
"inputs": {
"text_trans": ""
},
"class_type": "ZhPromptTranslator",
"_meta": {
"title": "正向提示词"
}
},
"18": {
"inputs": {
"text_trans": ""
},
"class_type": "ZhPromptTranslator",
"_meta": {
"title": "反向提示词"
}
},
"19": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"9",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "保存图像"
}
},
"20": {
"inputs": {
"channel": "red",
"image": [
"15",
0
]
},
"class_type": "ImageToMask",
"_meta": {
"title": "图像到遮罩"
}
},
"21": {
"inputs": {
"mask": [
"20",
0
]
},
"class_type": "InvertMask",
"_meta": {
"title": "遮罩反转"
}
},
"22": {
"inputs": {
"mask": [
"21",
0
]
},
"class_type": "MaskToImage",
"_meta": {
"title": "遮罩到图像"
}
}
}
+125
View File
@@ -0,0 +1,125 @@
{
"3": {
"inputs": {
"seed": 1068764177227380,
"steps": 5,
"cfg": 1,
"sampler_name": "lcm",
"scheduler": "normal",
"denoise": 1,
"model": [
"10",
0
],
"positive": [
"6",
0
],
"negative": [
"7",
0
],
"latent_image": [
"5",
0
]
},
"class_type": "KSampler"
},
"4": {
"inputs": {
"ckpt_name": "dreamshaper_8.safetensors"
},
"class_type": "CheckpointLoaderSimple"
},
"5": {
"inputs": {
"width": [
"14",
0
],
"height": [
"14",
1
],
"batch_size": 1
},
"class_type": "EmptyLatentImage"
},
"6": {
"inputs": {
"text": [
"20",
0
],
"clip": [
"10",
1
]
},
"class_type": "CLIPTextEncode"
},
"7": {
"inputs": {
"text": "undefined",
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode"
},
"8": {
"inputs": {
"samples": [
"3",
0
],
"vae": [
"4",
2
]
},
"class_type": "VAEDecode"
},
"9": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"8",
0
]
},
"class_type": "SaveImage"
},
"10": {
"inputs": {
"lora_name": "lcm-lora-sdv1-5.safetensors",
"strength_model": 1,
"strength_clip": 1,
"model": [
"4",
0
],
"clip": [
"4",
1
]
},
"class_type": "LoraLoader"
},
"14": {
"inputs": {
"aspect_ratio": "1152×896 ∣ 9:7",
"width": 512,
"height": 512
},
"class_type": "AspectRatio"
},
"20": {
"inputs": {
"text_trans": "一只小狗"
},
"class_type": "ZhPromptTranslator"
}
}
+235
View File
@@ -0,0 +1,235 @@
{
"3": {
"inputs": {
"seed": 1092890374757226,
"steps": 8,
"cfg": 1,
"sampler_name": "lcm",
"scheduler": "normal",
"denoise": 1,
"model": [
"74",
0
],
"positive": [
"73",
0
],
"negative": [
"73",
1
],
"latent_image": [
"42",
0
]
},
"class_type": "KSampler",
"_meta": {
"title": "K采样器"
}
},
"4": {
"inputs": {
"ckpt_name": "dreamshaper_8.safetensors"
},
"class_type": "CheckpointLoaderSimple",
"_meta": {
"title": "Checkpoint加载器(简易)"
}
},
"6": {
"inputs": {
"text": [
"75",
0
],
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码器"
}
},
"7": {
"inputs": {
"text": "",
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码器"
}
},
"8": {
"inputs": {
"samples": [
"3",
0
],
"vae": [
"4",
2
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"9": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"8",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "保存图像"
}
},
"16": {
"inputs": {
"text": "https://u.wechat.com/EEy4mPvfXdYO-MvStE4S2m0",
"version": 2,
"box_size": 30,
"border": 5
},
"class_type": "QRCode",
"_meta": {
"title": "二维码生成"
}
},
"35": {
"inputs": {
"image": [
"16",
0
]
},
"class_type": "GetImage_(Width&Height) _O",
"_meta": {
"title": "GetImage_(Width&Height) _O"
}
},
"41": {
"inputs": {
"control_net_name": "明暗控制controlNet.safetensors"
},
"class_type": "ControlNetLoader",
"_meta": {
"title": "ControlNet加载器"
}
},
"42": {
"inputs": {
"width": [
"35",
0
],
"height": [
"35",
1
],
"batch_size": 1
},
"class_type": "EmptyLatentImage",
"_meta": {
"title": "空Latent"
}
},
"47": {
"inputs": {
"control_net_name": "control_v1p_sd15_qrcode_monster_v2.safetensors"
},
"class_type": "ControlNetLoader",
"_meta": {
"title": "ControlNet加载器"
}
},
"72": {
"inputs": {
"strength": 0.3,
"start_percent": 0,
"end_percent": 1,
"positive": [
"6",
0
],
"negative": [
"7",
0
],
"control_net": [
"47",
0
],
"image": [
"16",
0
]
},
"class_type": "ControlNetApplyAdvanced",
"_meta": {
"title": "ControlNet应用(高级)"
}
},
"73": {
"inputs": {
"strength": 0.3,
"start_percent": 0.1,
"end_percent": 0.9,
"positive": [
"72",
0
],
"negative": [
"72",
1
],
"control_net": [
"41",
0
],
"image": [
"16",
0
]
},
"class_type": "ControlNetApplyAdvanced",
"_meta": {
"title": "ControlNet应用(高级)"
}
},
"74": {
"inputs": {
"lora_name": "lcm-lora-sdv1-5.safetensors",
"strength_model": 1,
"model": [
"4",
0
]
},
"class_type": "LoraLoaderModelOnly",
"_meta": {
"title": "LoRA加载器(仅模型)"
}
},
"75": {
"inputs": {
"text_trans": "汽车修理店,车轮"
},
"class_type": "ZhPromptTranslator",
"_meta": {
"title": "中文翻译"
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Some files were not shown because too many files have changed in this diff Show More