Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1fe5f0b67f | ||
|
|
43ecd83262 | ||
|
|
357ccd2b43 | ||
|
|
ba7194836a | ||
|
|
7e88221dfc | ||
|
|
17a4350230 | ||
|
|
cc16c74e2e | ||
|
|
6c841b9785 | ||
|
|
5bb167364c | ||
|
|
7295cd63ca | ||
|
|
9455d5d2d8 | ||
|
|
b6a580dc64 | ||
|
|
ce7571a0a8 | ||
|
|
2e23311d93 | ||
|
|
d3c4a7f5a7 | ||
|
|
a5b86e8295 | ||
|
|
86fd7a155b | ||
|
|
a87a5b69c5 | ||
|
|
5c86e1db4e | ||
|
|
c4d1d89216 | ||
|
|
a60f88e1c7 | ||
|
|
67ab97db66 | ||
|
|
5ae8f15cd1 | ||
|
|
70b758753a | ||
|
|
c1b3cb7a49 | ||
|
|
5e67c1ad70 | ||
|
|
1ec31bc90d | ||
|
|
396dd1a2cb | ||
|
|
e251f21a31 | ||
|
|
7f28088ffc | ||
|
|
877ff5df9e | ||
|
|
60c66b5ada | ||
|
|
bd24491d8f | ||
|
|
5d360d1801 | ||
|
|
fa6888994e | ||
|
|
ac46a7161c | ||
|
|
aebc08230e | ||
|
|
84693474b5 | ||
|
|
6c1f6cc1a1 | ||
|
|
230b5cc340 | ||
|
|
b482183255 | ||
|
|
dcb89c0080 | ||
|
|
e41bfc5b15 | ||
|
|
2999d2ee9d | ||
|
|
c89f08d49c | ||
|
|
332b2315cb | ||
|
|
779223ead3 | ||
|
|
1e4f7ec11e | ||
|
|
5a90359bc1 | ||
|
|
c173a7bcba | ||
|
|
adc20028c1 | ||
|
|
4cc6621277 | ||
|
|
3ed6e5292a | ||
|
|
ceeef4bd9f | ||
|
|
f1a5316b91 | ||
|
|
60e12c34f3 | ||
|
|
714b4d3285 | ||
|
|
f684744205 | ||
|
|
a5e39a5145 | ||
|
|
495b315eeb | ||
|
|
2d2536b9bc | ||
|
|
69d83f27be | ||
|
|
56918fbe42 | ||
|
|
7310adcc94 | ||
|
|
81b72434b6 | ||
|
|
28507d4444 | ||
|
|
bb97be8746 | ||
|
|
4797ed2435 | ||
|
|
b6bdba5fb2 | ||
|
|
482628c701 | ||
|
|
89f359c5c8 | ||
|
|
fe9e459ba2 | ||
|
|
3b59044c29 | ||
|
|
d2d4c6aa56 | ||
|
|
8a6a7872b5 | ||
|
|
8ceae5ccf8 | ||
|
|
3d7e2707f2 | ||
|
|
dbfe688a89 | ||
|
|
71822ca854 | ||
|
|
4c66b01a27 | ||
|
|
4f8904f665 | ||
|
|
43e4cbf41c | ||
|
|
e050de7ed8 |
@@ -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
|
||||
@@ -8,41 +8,20 @@ Download and place in the plugin directory of comfyUI, as shown below:
|
||||
|
||||

|
||||
|
||||
##### 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”文件,未报错后就可以了
|
||||
@@ -8,90 +8,21 @@
|
||||
|
||||

|
||||
|
||||
##### 特别说明该版本执行最新版“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”
|
||||
位置参考图如下:
|
||||

|
||||
新增内容:
|
||||
```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”文件,未报错后就可以了
|
||||
@@ -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"]
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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 '--------修改完成--------')
|
||||
|
||||
@@ -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: #查询指令(固定)
|
||||
- 查询排队情况
|
||||
- 我的编号
|
||||
@@ -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": "多参代码表达式"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": "自定义采样器(高级)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": "风格参考图片"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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": "遮罩到图像"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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": "中文翻译"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 148 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 149 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
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 |