Compare commits

..
21 Commits
Author SHA1 Message Date
yolain 3f37ba9491 Bump Version 2025-08-29 15:26:37 +08:00
yolain a72a4dc40f Removed the definition of the CSS class name gird-cols-1 #859 2025-08-27 14:59:08 +08:00
yolain 11794f7d71 Fix lock seed not working in easy promptAwait 2025-08-19 16:30:00 +08:00
yolain 74226d8b2a Rename the nodes map 2025-08-17 13:00:50 +08:00
yolain f550ce83a8 Fix image selector output error type #845 2025-08-09 12:42:20 +08:00
yolain 34882bca10 Bump Version 2025-08-08 19:01:55 +08:00
yolain 43b94be806 Update ImageChooser frontend code 2025-08-08 18:58:59 +08:00
yolain 0349d81694 Revamp ImageChooser and removed Preview&Choose in easy samplers #838 2025-08-08 18:54:24 +08:00
yolain 93254a4c07 Can replace the default fooocus_styles with files of the same name under styles dir. 2025-08-06 17:13:22 +08:00
yolain 8485447325 Fix globalSeed to work with Partial Execution #844 2025-08-06 17:12:06 +08:00
yolain 717092a3ce Add easy loraPromptApply 2025-07-26 19:39:01 +08:00
yolain 14a1121860 Add easy loraSwitcher 2025-07-26 17:20:39 +08:00
yolain 8c1eec2858 Fix front-end v1.24.2 and later failed to display the latent preview image in easy kSamplers during initial sampling. 2025-07-24 19:04:51 +08:00
yolain b6bb4a3055 Fixed segformer_b3_clothes download link error #831 2025-07-17 14:34:16 +08:00
yolain 6873492872 Fixed getStylesList value transfer error 2025-07-15 18:24:18 +08:00
yolain 2d71b3e647 Update StylesSelector 2025-07-15 18:02:12 +08:00
yolain e7320ec0c4 Remove easy showAnything error messages #776 2025-07-12 21:13:15 +08:00
yolainandyolain 560be6aee7 Update HumanSegmentation (#826)
* Change to new mask components on humanSegmentation

* Change segformer index

* Add segformer_b3_clothes and fashion

* Add face_parsing

* Fix face_parsing output error images

---------

Co-authored-by: yolain <me@yolain.com>
2025-07-10 18:32:06 +08:00
yolain 54614079ca Fixed makeImageForIcLora issue that occurred when the heights of two images were the same during image stitching on. 2025-07-05 13:48:08 +08:00
yolain b0cd0bcb5b Add easy joyCaption3API 2025-07-05 00:03:12 +08:00
yolain e46f8a45d0 Add easy promptAwait node (#818)
* Add submodule

* Set submodule branch to main

* Add PromptAwait node

* Fix loop has started for the second time, but the prompt word has not output new content

* Add input_1

* Change select widget to toolbar

* Modify some field names and displays

* Change select max-width

* Add output random seed in promptAwait
2025-06-30 01:22:13 +08:00
37 changed files with 2668 additions and 2035 deletions
-1
View File
@@ -9,7 +9,6 @@ workflow/**
autocomplete/**
web_beta/**
web_version/dev/**
ComfyUI-Easy-Use-Frontend/
docs/**
.vscode/
.idea/
+4
View File
@@ -0,0 +1,4 @@
[submodule "ComfyUI-Easy-Use-Frontend"]
path = ComfyUI-Easy-Use-Frontend
url = https://github.com/yolain/ComfyUI-Easy-Use-Frontend.git
branch = main
+22
View File
@@ -52,6 +52,28 @@ git clone https://github.com/yolain/ComfyUI-Easy-Use
## 📜 更新日志
**v1.3.3**
- 删除CSS类名称`gird-cols-1` #859
- 修复锁定种子在 `easy promptAwait` 中不起作用
- 重命名节点图
- 修复`easy ImageChooser`输出错误类型 #845
**v1.3.2**
- 改造 `easy imageChooser` 节点以兼容 frontend>=v1.24.2, 解决方案参考自 [Comfyui_LG_Tools](https://github.com/LAOGOU-666/Comfyui_LG_Tools)
- 改造 `easy stylesSelector` 节点, 你可在 [other styles files](https://github.com/yolain/EasyUse-Styles-Templates) 下载到 `styles` 文件夹下
- 改造 `easy humanSegmentation` 节点
- 修复 `easy makeImageForICLora` 节点.
- 添加 `easy joycaption3API` 节点
- 添加 `easy promptAwait` 节点
**v1.3.1**
- 重写 drawNodeWidget 修复组节点预览的问题.
- 更新了一些 XYPlot 的功能 by [mekinney](https://github.com/mekinney)
- 添加 `easy seedList` 节点 (它对循环节点有用)
**v1.3.0**
- 将循环节点设置为最大输入和输出数量为20
+16
View File
@@ -47,6 +47,22 @@ Double-click install.bat to install the required dependencies
## 📜 Changelog
**v1.3.3**
- Removed the definition of the CSS class name gird-cols-1 #859
- Fix lock seed not working in `easy promptAwait`
- Rename the nodes map
- Fix `easy imageChooser` output error type #845
**v1.3.2**
- Revamp `easy imageChooser` node to adapt frontend>=v1.24.2, solution referenced from [Comfyui_LG_Tools](https://github.com/LAOGOU-666/Comfyui_LG_Tools)
- Revamp `easy stylesSelector` node, and you can download [other styles files](https://github.com/yolain/EasyUse-Styles-Templates) to the `styles` folder
- Revamp `easy humanSegmentation` node
- Fix `easy makeImageForICLora` node issue, that occurred when the heights of two images were the same during image stitching on.
- Add `easy joycaption3API` node
- Add `easy promptAwait` node
**v1.3.1**
- Rewrite drawNodeWidget and fix the GroupNode preview issue.
+1 -2
View File
@@ -1,4 +1,4 @@
__version__ = "1.3.1"
__version__ = "1.3.3"
import yaml
import json
@@ -15,7 +15,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {}
importlib.import_module('.py.routes', __name__)
importlib.import_module('.py.server', __name__)
nodes_list = ["util", "seed", "prompt", "loaders", "adapter", "inpaint", "preSampling", "samplers", "fix", "pipe", "xyplot", "image", "logic", "api", "deprecated"]
# locale = {}
for module_name in nodes_list:
imported_module = importlib.import_module(".py.nodes.{}".format(module_name), __name__)
NODE_CLASS_MAPPINGS = {**NODE_CLASS_MAPPINGS, **imported_module.NODE_CLASS_MAPPINGS}
+2 -1
View File
@@ -2,7 +2,8 @@
"settingsCategories": {
"Hotkeys": "Hotkeys",
"Nodes": "Nodes",
"NodesMap": "NodesMap"
"NodesMap": "NodesMap",
"StylesSelector": "StylesSelector"
},
"nodeCategories": {
"Util": "Util",
+2 -1
View File
@@ -2,7 +2,8 @@
"settingsCategories": {
"Hotkeys": "快捷键",
"Nodes": "节点相关",
"NodesMap": "管理节点组"
"NodesMap": "管理节点组",
"StylesSelector": "样式选择器"
},
"nodeCategories": {
"Util": "工具",
+119 -123
View File
@@ -165,6 +165,35 @@
}
}
},
"easy promptAwait": {
"display_name": "提示词等待",
"inputs": {
"now": {
"name": "当前"
},
"prev": {
"name": "上一步"
},
"prompt": {
"name": "提示词",
"placeholder": "输入提示词或使用语音输入转文字"
}
},
"outputs": {
"0": {
"name": "输出"
},
"1": {
"name": "提示词"
},
"2": {
"name": "继续"
},
"3": {
"name": "随机种"
}
}
},
"easy promptConcat": {
"display_name": "提示词联结",
"inputs": {
@@ -1125,6 +1154,31 @@
}
}
},
"easy loraSwitcher": {
"display_name": "简易Lora切换器",
"inputs": {
"optional_lora_stack": {
"name": "LoRA堆(可选)"
},
"toggle": {
"name": "开关"
},
"select": {
"name": "选择项"
},
"num_loras": {
"name": "LoRA数量"
}
},
"outputs": {
"0": {
"name": "LoRA堆"
},
"1": {
"name": "LoRA名称"
}
}
},
"easy controlnetStack": {
"display_name": "简易 ControlNet 堆",
"inputs": {
@@ -4274,6 +4328,37 @@
}
}
},
"easy loraPromptApply":{
"display_name": "应用提示词LoRA",
"inputs": {
"model": {
"name": "模型"
},
"clip": {
"name": "CLIP"
},
"positive":{
"name": "正面提示词"
},
"negative":{
"name": "负面提示词"
}
},
"outputs":{
"0": {
"name": "模型"
},
"1": {
"name": "CLIP"
},
"2": {
"name": "正面提示词"
},
"3": {
"name": "负面提示词"
}
}
},
"easy loraStackApply": {
"display_name": "应用LoRA堆",
"inputs": {
@@ -6113,73 +6198,13 @@
"easy whileLoopStart": {
"display_name": "While循环-开始",
"inputs": {
"initial_value0": {
"name": "初始值0"
},
"initial_value1": {
"name": "初始值1"
},
"initial_value2": {
"name": "初始值2"
},
"initial_value3": {
"name": "初始值3"
},
"initial_value4": {
"name": "初始值4"
},
"initial_value5": {
"name": "初始值5"
},
"initial_value6": {
"name": "初始值6"
},
"initial_value7": {
"name": "初始值7"
},
"initial_value8": {
"name": "初始值8"
},
"initial_value9": {
"name": "初始值9"
},
"condition": {
"name": "条件"
"name": "开始循环"
}
},
"outputs": {
"0": {
"name": "开始"
},
"1": {
"name": "值0"
},
"2": {
"name": "值1"
},
"3": {
"name": "值2"
},
"4": {
"name": "值3"
},
"5": {
"name": "值4"
},
"6": {
"name": "值5"
},
"7": {
"name": "值6"
},
"8": {
"name": "值7"
},
"9": {
"name": "值8"
},
"10": {
"name": "值9"
}
}
},
@@ -6189,71 +6214,11 @@
"flow": {
"name": "结束"
},
"initial_value0": {
"name": "初始值0"
},
"initial_value1": {
"name": "初始值1"
},
"initial_value2": {
"name": "初始值2"
},
"initial_value3": {
"name": "初始值3"
},
"initial_value4": {
"name": "初始值4"
},
"initial_value5": {
"name": "初始值5"
},
"initial_value6": {
"name": "初始值6"
},
"initial_value7": {
"name": "初始值7"
},
"initial_value8": {
"name": "初始值8"
},
"initial_value9": {
"name": "初始值9"
},
"condition": {
"name": "条件"
"name": "继续循环"
}
},
"outputs": {
"0": {
"name": "值0"
},
"1": {
"name": "值1"
},
"2": {
"name": "值2"
},
"3": {
"name": "值3"
},
"4": {
"name": "值4"
},
"5": {
"name": "值5"
},
"6": {
"name": "值6"
},
"7": {
"name": "值7"
},
"8": {
"name": "值8"
},
"9": {
"name": "值9"
}
}
},
"easy forLoopStart": {
@@ -6711,7 +6676,38 @@
"name": "温度"
},
"max_tokens": {
"name": "最大词令牌数"
"name": "最大词元数"
},
"caption_type": {
"name": "提示词类型"
},
"caption_length": {
"name": "提示词长度"
},
"name_input": {
"name": "名称输入"
}
},
"outputs": {
"0": {
"name": "提示词"
}
}
},
"easy joyCaption3API": {
"display_name": "JoyCaption3(硅基流动)",
"inputs": {
"image": {
"name": "图像"
},
"do_sample": {
"name": "执行采样"
},
"temperature": {
"name": "温度"
},
"max_tokens": {
"name": "最大词元数"
},
"caption_type": {
"name": "提示词类型"
+8
View File
@@ -63,5 +63,13 @@
"EasyUse_NodesMap_Enable": {
"name": "启用管理节点组",
"tooltip": "您需要刷新页面以成功更新"
},
"EasyUse_StylesSelector_DisplayType": {
"name": "样式选择器显示类型",
"tooltip": "样式选择器显示类型,如果设置为“网格”,则显示为网格,如果设置为“列表”,则显示为列表",
"options": {
"Gird": "网格",
"List": "列表"
}
}
}
+9
View File
@@ -370,6 +370,15 @@ HUMANPARSING_MODELS = {
},
"human-parts":{
"model_url":"https://huggingface.co/Metal3d/deeplabv3p-resnet50-human/resolve/main/deeplabv3p-resnet50-human.onnx",
},
"segformer_b3_clothes":{
"model_name": "sayeed99/segformer_b3_clothes",
},
"segformer_b3_fashion":{
"model_name": "sayeed99/segformer-b3-fashion",
},
"face_parsing":{
"model_name": "jonathandinu/face-parsing"
}
}
+4 -3
View File
@@ -56,13 +56,14 @@ class BizyAIRAPI:
f"Failed to connect to the server: {e}, if you have no key, "
)
# joycaptionTwo
def joyCaption2(self, payload, image, apikey_override=None):
# joycaption
def joyCaption(self, payload, image, apikey_override=None, API_URL='/supernode/joycaption2'):
if apikey_override is not None:
api_key = apikey_override
else:
api_key = self.getAPIKey()
url = f"{self.base_url}/supernode/joycaption2"
url = f"{self.base_url}{API_URL}"
print('Sending request to:', url)
auth = f"Bearer {api_key}"
headers = {
"accept": "application/json",
+139 -38
View File
@@ -1,52 +1,153 @@
from threading import Event
import torch
from server import PromptServer
from aiohttp import web
from comfy import model_management as mm
from comfy_execution.graph import ExecutionBlocker
import time
class ChooserCancelled(Exception):
pass
class ChooserMessage:
stash = {}
messages = {}
cancelled = False
def get_chooser_cache():
"""获取选择器缓存"""
if not hasattr(PromptServer.instance, '_easyuse_chooser_node'):
PromptServer.instance._easyuse_chooser_node = {}
return PromptServer.instance._easyuse_chooser_node
@classmethod
def addMessage(cls, id, message):
if message == '__cancel__':
cls.messages = {}
cls.cancelled = True
elif message == '__start__':
cls.messages = {}
cls.stash = {}
cls.cancelled = False
else:
cls.messages[str(id)] = message
def cleanup_session_data(node_id):
"""清理会话数据"""
node_data = get_chooser_cache()
if node_id in node_data:
session_keys = ["event", "selected", "images", "total_count", "cancelled"]
for key in session_keys:
if key in node_data[node_id]:
del node_data[node_id][key]
def wait_for_chooser(id, images, mode, period=0.1):
try:
node_data = get_chooser_cache()
images = [images[i:i + 1, ...] for i in range(images.shape[0])]
if mode == "Keep Last Selection":
if id in node_data and "last_selection" in node_data[id]:
last_selection = node_data[id]["last_selection"]
if last_selection and len(last_selection) > 0:
valid_indices = [idx for idx in last_selection if 0 <= idx < len(images)]
if valid_indices:
try:
PromptServer.instance.send_sync("easyuse-image-keep-selection", {
"id": id,
"selected": valid_indices
})
except Exception as e:
pass
cleanup_session_data(id)
indices_str = ','.join(str(i) for i in valid_indices)
images = [images[idx] for idx in valid_indices]
images = torch.cat(images, dim=0)
return {"result": (images,)}
if id in node_data:
del node_data[id]
event = Event()
node_data[id] = {
"event": event,
"images": images,
"selected": None,
"total_count": len(images),
"cancelled": False,
}
while id in node_data:
node_info = node_data[id]
if node_info.get("cancelled", False):
cleanup_session_data(id)
raise ChooserCancelled("Manual selection cancelled")
if "selected" in node_info and node_info["selected"] is not None:
break
@classmethod
def waitForMessage(cls, id, period=0.1, asList=False):
sid = str(id)
while not (sid in cls.messages) and not ("-1" in cls.messages):
if cls.cancelled:
cls.cancelled = False
raise ChooserCancelled()
time.sleep(period)
if cls.cancelled:
cls.cancelled = False
raise ChooserCancelled()
message = cls.messages.pop(str(id), None) or cls.messages.pop("-1")
try:
if asList:
return [int(x.strip()) for x in message.split(",")]
if id in node_data:
node_info = node_data[id]
selected_indices = node_info.get("selected")
if selected_indices is not None and len(selected_indices) > 0:
valid_indices = [idx for idx in selected_indices if 0 <= idx < len(images)]
if valid_indices:
selected_images = [images[idx] for idx in valid_indices]
if id not in node_data:
node_data[id] = {}
node_data[id]["last_selection"] = valid_indices
cleanup_session_data(id)
selected_images = torch.cat(selected_images, dim=0)
return {"result": (selected_images,)}
else:
cleanup_session_data(id)
return {"result": (images[0] if len(images) > 0 else ExecutionBlocker(None),)}
else:
return int(message.strip())
except ValueError:
print(
f"ERROR IN IMAGE_CHOOSER - failed to parse '${message}' as ${'comma separated list of ints' if asList else 'int'}")
return [1] if asList else 1
cleanup_session_data(id)
return {
"result": (images[0] if len(images) > 0 else ExecutionBlocker(None),)}
else:
return {"result": (images[0] if len(images) > 0 else ExecutionBlocker(None),)}
except ChooserCancelled:
raise mm.InterruptProcessingException()
except Exception as e:
node_data = get_chooser_cache()
if id in node_data:
cleanup_session_data(id)
if 'image_list' in locals() and len(images) > 0:
return {"result": (images[0])}
else:
return {"result": (ExecutionBlocker(None),)}
@PromptServer.instance.routes.post('/easyuse/image_chooser_message')
async def make_image_selection(request):
post = await request.post()
ChooserMessage.addMessage(post.get("id"), post.get("message"))
return web.json_response({})
async def handle_image_selection(request):
try:
data = await request.json()
node_id = data.get("node_id")
selected = data.get("selected", [])
action = data.get("action")
node_data = get_chooser_cache()
if node_id not in node_data:
return web.json_response({"code": -1, "error": "Node data does not exist"})
try:
node_info = node_data[node_id]
if "total_count" not in node_info:
return web.json_response({"code": -1, "error": "The node has been processed"})
if action == "cancel":
node_info["cancelled"] = True
node_info["selected"] = []
elif action == "select" and isinstance(selected, list):
valid_indices = [idx for idx in selected if isinstance(idx, int) and 0 <= idx < node_info["total_count"]]
if valid_indices:
node_info["selected"] = valid_indices
node_info["cancelled"] = False
else:
return web.json_response({"code": -1, "error": "Invalid Selection Index"})
else:
return web.json_response({"code": -1, "error": "Invalid operation"})
node_info["event"].set()
return web.json_response({"code": 1})
except Exception as e:
if node_id in node_data and "event" in node_data[node_id]:
node_data[node_id]["event"].set()
return web.json_response({"code": -1, "message": "Processing Failed"})
except Exception as e:
return web.json_response({"code": -1, "message": "Request Failed"})
+55
View File
@@ -0,0 +1,55 @@
from server import PromptServer
from aiohttp import web
import time
import json
class MessageCancelled(Exception):
pass
class Message:
stash = {}
messages = {}
cancelled = False
@classmethod
def addMessage(cls, id, message):
if message == '__cancel__':
cls.messages = {}
cls.cancelled = True
elif message == '__start__':
cls.messages = {}
cls.stash = {}
cls.cancelled = False
else:
cls.messages[str(id)] = message
@classmethod
def waitForMessage(cls, id, period=0.1, asList=False):
sid = str(id)
while not (sid in cls.messages) and not ("-1" in cls.messages):
if cls.cancelled:
cls.cancelled = False
raise MessageCancelled()
time.sleep(period)
if cls.cancelled:
cls.cancelled = False
raise MessageCancelled()
message = cls.messages.pop(str(id), None) or cls.messages.pop("-1")
try:
if asList:
return [str(x.strip()) for x in message.split(",")]
else:
try:
return json.loads(message)
except ValueError:
return message
except ValueError:
print( f"ERROR IN MESSAGE - failed to parse '${message}' as ${'comma separated list of strings' if asList else 'string'}")
return [message] if asList else message
@PromptServer.instance.routes.post('/easyuse/message_callback')
async def message_callback(request):
post = await request.post()
Message.addMessage(post.get("id"), post.get("message"))
return web.json_response({})
+29
View File
@@ -9,12 +9,39 @@ from ..config import *
from ..libs.log import log_node_info, log_node_warn
from ..libs.utils import get_local_filepath, get_sd_version
from ..libs.wildcards import process_with_loras
from ..libs.controlnet import easyControlnet
from ..libs.conditioning import prompt_to_cond
from ..libs import cache as backend_cache
from .. import easyCache
class applyLoraPrompt:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": ("MODEL",),
"clip": ("CLIP",),
"positive": ("STRING", {"default": "", "forceInput": True}),
},
"optional": {
"negative": ("STRING", {"default": "", "forceInput": True}),
}
}
RETURN_TYPES = ("MODEL", "CLIP", "STRING", "STRING")
RETURN_NAMES = ("model", "clip", "positive", "negative")
CATEGORY = "EasyUse/Adapter"
FUNCTION = "apply"
def apply(self, model, clip, positive, negative=None):
model, clip, positive, _, _, _ = process_with_loras(positive, model, clip, 'Positive', easyCache=easyCache)
if negative is not None:
model, clip, negative, _, _, _ = process_with_loras(negative, model, clip, 'Negative', easyCache=easyCache)
return (model, clip, positive, negative if negative is not None else "")
class applyLoraStack:
@classmethod
def INPUT_TYPES(s):
@@ -1284,6 +1311,7 @@ class applyPulIDADV(applyPulID):
NODE_CLASS_MAPPINGS = {
"easy loraPromptApply": applyLoraPrompt,
"easy loraStackApply": applyLoraStack,
"easy controlnetStackApply": applyControlnetStack,
"easy ipadapterApply": ipadapterApply,
@@ -1303,6 +1331,7 @@ NODE_CLASS_MAPPINGS = {
}
NODE_DISPLAY_NAME_MAPPINGS = {
"easy loraPromptApply": "Easy Apply LoraPrompt",
"easy loraStackApply": "Easy Apply LoraStack",
"easy controlnetStackApply": "Easy Apply CnetStack",
"easy ipadapterApply": "Easy Apply IPAdapter",
+9 -35
View File
@@ -3,37 +3,8 @@ from ..libs.api.fluxai import fluxaiAPI
from ..libs.api.bizyair import bizyairAPI, encode_data
from nodes import NODE_CLASS_MAPPINGS as ALL_NODE_CLASS_MAPPINGS
class fluxPromptGenAPI:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"describe": ("STRING", {"default": "", "placeholder": "Describe your image idea (you can use any language)", "multiline": True}),
},
"optional": {
"cookie_override": ("STRING", {"default": "", "forceInput": True}),
},
"hidden": {
"prompt": "PROMPT",
"unique_id": "UNIQUE_ID",
"extra_pnginfo": "EXTRA_PNGINFO",
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("prompt",)
FUNCTION = "generate"
OUTPUT_NODE = False
CATEGORY = "EasyUse/API"
def generate(self, describe, cookie_override=None, prompt=None, unique_id=None, extra_pnginfo=None):
prompt = fluxaiAPI.promptGenerate(describe, cookie_override)
return (prompt,)
class joyCaption2API:
API_URL = f"/supernode/joycaption2"
@classmethod
def INPUT_TYPES(s):
@@ -110,12 +81,12 @@ class joyCaption2API:
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("caption",)
FUNCTION = "joycaption2"
FUNCTION = "joycaption"
OUTPUT_NODE = False
CATEGORY = "EasyUse/API"
def joycaption2(
def joycaption(
self,
image,
do_sample,
@@ -149,17 +120,20 @@ class joyCaption2API:
}
pbar.update_absolute(30)
caption = bizyairAPI.joyCaption2(payload, image, apikey_override)
caption = bizyairAPI.joyCaption(payload, image, apikey_override, API_URL=self.API_URL)
pbar.update_absolute(100)
return (caption,)
class joyCaption3API(joyCaption2API):
API_URL = f"/supernode/joycaption3"
NODE_CLASS_MAPPINGS = {
"easy fluxPromptGenAPI": fluxPromptGenAPI,
"easy joyCaption2API": joyCaption2API,
"easy joyCaption3API": joyCaption3API,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"easy fluxPromptGenAPI": "Prompt Gen (FluxAI)",
"easy joyCaption2API": "JoyCaption2 (BizyAIR)",
"easy joyCaption3API": "JoyCaption3 (BizyAIR)",
}
+127 -87
View File
@@ -4,19 +4,19 @@ import torch
import numpy as np
import comfy.utils
import comfy.model_management
import shutil
from comfy_extras.nodes_compositing import JoinImageWithAlpha
from server import PromptServer
from nodes import MAX_RESOLUTION, NODE_CLASS_MAPPINGS as ALL_NODE_CLASS_MAPPINGS
from PIL import Image, ImageDraw, ImageFilter, ImageOps
import torch.nn.functional as F
from torchvision.transforms import Resize, CenterCrop, GaussianBlur
from torchvision.transforms import Resize, CenterCrop, GaussianBlur, ToPILImage
from torchvision.transforms.functional import to_pil_image
from ..libs.log import log_node_info
from ..libs.utils import AlwaysEqualProxy, ByPassTypeTuple
from ..libs.cache import cache, update_cache, remove_cache
from ..libs.image import pil2tensor, tensor2pil, ResizeMode, get_new_bounds, RGB2RGBA, image2mask, empty_image, fit_resize_image
from ..libs.colorfix import adain_color_fix, wavelet_color_fix
from ..libs.chooser import ChooserMessage, ChooserCancelled
from ..config import REMBG_DIR, REMBG_MODELS, HUMANPARSING_MODELS, MEDIAPIPE_MODELS, MEDIAPIPE_DIR
any_type = AlwaysEqualProxy("*")
@@ -1005,6 +1005,7 @@ class imageRemBg:
"result": (new_images, masks)}
# 图像选择器
from ..libs.chooser import wait_for_chooser
class imageChooser(PreviewImage):
@classmethod
def INPUT_TYPES(self):
@@ -1041,21 +1042,11 @@ class imageChooser(PreviewImage):
def chooser(self, prompt=None, my_unique_id=None, extra_pnginfo=None, **kwargs):
id = my_unique_id[0]
id = id.split('.')[len(id.split('.')) - 1] if "." in id else id
if id not in ChooserMessage.stash:
ChooserMessage.stash[id] = {}
my_stash = ChooserMessage.stash[id]
# enable stashing. If images is None, we are operating in read-from-stash mode
if 'images' in kwargs:
my_stash['images'] = kwargs['images']
else:
kwargs['images'] = my_stash.get('images', None)
if (kwargs['images'] is None):
return (None, None, None, "")
return (None,)
images_in = torch.cat(kwargs.pop('images'))
self.batch = images_in.shape[0]
for x in kwargs: kwargs[x] = kwargs[x][0]
try:
@@ -1063,33 +1054,18 @@ class imageChooser(PreviewImage):
except:
pnginfo = None
result = self.save_images(images=images_in, prompt=prompt, extra_pnginfo=pnginfo)
images = result['ui']['images']
PromptServer.instance.send_sync("easyuse-image-choose", {"id": id, "urls": images})
if "ui" in result and "images" in result['ui']:
images = result["ui"]["images"]
else:
images = []
try:
PromptServer.instance.send_sync("easyuse-image-choose", {"id": id, "urls": images})
except Exception as e:
pass
# 获取上次选择
mode = kwargs.pop('mode', 'Always Pause')
last_choosen = None
if mode == 'Keep Last Selection':
if not extra_pnginfo:
print("Error: extra_pnginfo is empty")
elif (not isinstance(extra_pnginfo[0], dict) or "workflow" not in extra_pnginfo[0]):
print("Error: extra_pnginfo[0] is not a dict or missing 'workflow' key")
else:
workflow = extra_pnginfo[0]["workflow"]
node = next((x for x in workflow["nodes"] if str(x["id"]) == id), None)
if node:
last_choosen = node['properties']['values']
# wait for selection
try:
selections = ChooserMessage.waitForMessage(id, asList=True) if last_choosen is None or len(last_choosen)<1 else last_choosen
choosen = [x for x in selections if x >= 0] if len(selections)>1 else [0]
except ChooserCancelled:
raise comfy.model_management.InterruptProcessingException()
return {"ui": {"images": images},
"result": (self.tensor_bundle(images_in, choosen),)}
return wait_for_chooser(id, images_in, mode)
class imageColorMatch(PreviewImage):
@classmethod
@@ -1277,13 +1253,22 @@ class humanSegmentation:
@classmethod
def INPUT_TYPES(cls):
return {
"required":{
"image": ("IMAGE",),
"method": (["selfie_multiclass_256x256", "human_parsing_lip", "human_parts (deeplabv3p)"],),
"method": (["selfie_multiclass_256x256", "human_parsing_lip", "human_parts (deeplabv3p)", "segformer_b3_clothes", "segformer_b3_fashion", "face_parsing"],),
"confidence": ("FLOAT", {"default": 0.4, "min": 0.05, "max": 0.95, "step": 0.01},),
"crop_multi": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.001},),
"mask_components":(
"EASY_COMBO",{
"options": [{'label':'Background','value':0}],
"multi_select": {
"placeholder": "select mask components",
"chip": True,
"max_selected_labels": 4,
}
}
)
},
"hidden": {
"prompt": "PROMPT",
@@ -1309,12 +1294,7 @@ class humanSegmentation:
numpy_image = cv2.cvtColor(numpy_image, cv2.COLOR_BGR2RGB)
return mp.Image(image_format=image_format, data=numpy_image)
def parsing(self, image, confidence, method, crop_multi, prompt=None, my_unique_id=None):
mask_components = []
if my_unique_id in prompt:
if prompt[my_unique_id]["inputs"]['mask_components']:
mask_components = prompt[my_unique_id]["inputs"]['mask_components'].split(',')
mask_components = list(map(int, mask_components))
def parsing(self, image, confidence, method, crop_multi, mask_components, prompt=None, my_unique_id=None):
if method == 'selfie_multiclass_256x256':
try:
import mediapipe as mp
@@ -1442,6 +1422,107 @@ class humanSegmentation:
output_image = torch.cat(ret_images, dim=0)
mask = torch.cat(ret_masks, dim=0)
elif method in ["segformer_b3_clothes", "segformer_b3_fashion", "face_parsing"]:
from transformers import SegformerImageProcessor, AutoModelForSemanticSegmentation
# 分割
def get_segmentation_from_model(tensor_image, model, processor):
cloth = tensor2pil(tensor_image)
inputs = processor(images=cloth, return_tensors="pt")
outputs = model(**inputs)
logits = outputs.logits.cpu()
upsampled_logits = F.interpolate(logits, size=cloth.size[::-1], mode="bilinear",
align_corners=False)
pred_seg = upsampled_logits.argmax(dim=1)[0].numpy()
return pred_seg, cloth
if method in cache:
_, (processor, model) = cache[method][1]
else:
model_folder_path = os.path.join(folder_paths.models_dir, method)
if os.path.exists(model_folder_path):
print(f"Start to load existing model...")
else:
from huggingface_hub import snapshot_download
PromptServer.instance.send_sync("easyuse-toast", {"content": f"Model not found locally. Downloading {method}...", "type": 'loading', "duration": 10000})
print(f"Model not found locally. Downloading {method}...")
model_path_cache = os.path.join(folder_paths.models_dir, "cache-"+method)
snapshot_download(
repo_id=HUMANPARSING_MODELS[method]['model_name'],
local_dir=model_path_cache,
local_dir_use_symlinks=False,
resume_download=True
)
shutil.move(model_path_cache, model_folder_path)
print(f"Model downloaded to {model_folder_path}...")
try:
model_folder_path = os.path.normpath(folder_paths.folder_names_and_paths[method][0][0])
except:
pass
processor = SegformerImageProcessor.from_pretrained(model_folder_path)
model = AutoModelForSemanticSegmentation.from_pretrained(model_folder_path)
update_cache(method, 'human_segmentation', (False, (processor, model)))
ret_images = []
ret_masks = []
if method == "face_parsing":
import matplotlib
import torchvision.transforms as T
transform = ToPILImage()
colormap = matplotlib.colormaps['viridis']
device = model.device
results = []
images = []
for img in image:
size = img.shape[:2]
inputs = processor(images=transform(img.permute(2, 0, 1)), return_tensors="pt")
inputs = {k: v.to(device) for k, v in inputs.items()}
outputs = model(**inputs)
logits = outputs.logits
upsampled_logits = F.interpolate(
logits,
size=size,
mode="bilinear",
align_corners=False)
pred_seg = upsampled_logits.argmax(dim=1)[0]
pred_seg_np = pred_seg.cpu().detach().numpy().astype(np.uint8)
results.append(torch.tensor(pred_seg_np))
results_out = torch.stack(results, dim=0)
for img, result_item in zip(image, results_out):
mask = torch.zeros(result_item.shape, dtype=torch.uint8)
for i in mask_components:
mask = mask | torch.where(result_item == i, 1, 0)
# 将mask转换为numpy数组,并确保数据类型正确
mask_np = (mask * 255).numpy().astype(np.uint8)
_mask = Image.fromarray(mask_np)
# 处理图像输出
ret_image = RGB2RGBA(tensor2pil(img).convert('RGB'), _mask.convert('L'))
ret_images.append(pil2tensor(ret_image))
ret_masks.append(image2mask(_mask))
else:
for img in image:
pred_seg, cloth = get_segmentation_from_model(img, model, processor)
i = torch.unsqueeze(img, 0)
i = pil2tensor(tensor2pil(i).convert('RGB'))
mask = np.isin(pred_seg, mask_components).astype(np.uint8)
_mask = Image.fromarray(mask * 255)
ret_image = RGB2RGBA(tensor2pil(img).convert('RGB'), _mask.convert('L'))
ret_images.append(pil2tensor(ret_image))
ret_masks.append(image2mask(_mask))
output_image = torch.cat(ret_images, dim=0)
mask = torch.cat(ret_masks, dim=0)
# use crop
bbox = [[0, 0, 0, 0]]
if crop_multi > 0.0:
@@ -1892,47 +1973,6 @@ class loadImagesForLoop:
"result": tuple(["stub", index, image, mask, name] + outputs),
"expand": graph.finalize(),
}
# 姿势编辑器
# class poseEditor:
# @classmethod
# def INPUT_TYPES(self):
# temp_dir = folder_paths.get_temp_directory()
#
# if not os.path.isdir(temp_dir):
# os.makedirs(temp_dir)
#
# temp_dir = folder_paths.get_temp_directory()
#
# return {"required":
# {"image": (sorted(os.listdir(temp_dir)),)},
# }
#
# RETURN_TYPES = ("IMAGE",)
# FUNCTION = "output_pose"
#
# CATEGORY = "EasyUse/🚫 Deprecated"
#
# def output_pose(self, image):
# image_path = os.path.join(folder_paths.get_temp_directory(), image)
# # print(f"Create: {image_path}")
#
# i = Image.open(image_path)
# image = i.convert("RGB")
# image = np.array(image).astype(np.float32) / 255.0
# image = torch.from_numpy(image)[None,]
#
# return (image,)
#
# @classmethod
# def IS_CHANGED(self, image):
# image_path = os.path.join(
# folder_paths.get_temp_directory(), image)
# # print(f'Change: {image_path}')
#
# m = hashlib.sha256()
# with open(image_path, 'rb') as f:
# m.update(f.read())
# return m.digest().hex()
class makeImageForICRepaint:
@classmethod
@@ -2022,7 +2062,7 @@ class makeImageForICRepaint:
image, mask, context_mask = None, None, None
# resize
if img1_h != img2_h or img1_w != img2_w:
if img1_h != img2_h and img1_w != img2_w:
width, height = img2_w, img2_h
fit = 'crop'
if method != 'uniform width':
+55 -2
View File
@@ -8,7 +8,7 @@ from nodes import MAX_RESOLUTION, NODE_CLASS_MAPPINGS as ALL_NODE_CLASS_MAPPINGS
from ..libs.log import log_node_info, log_node_error, log_node_warn
from ..libs.wildcards import process_with_loras
from ..libs.utils import find_wildcards_seed, is_linked_styles_selector, get_sd_version
from ..libs.utils import find_wildcards_seed, is_linked_styles_selector, get_sd_version, AlwaysEqualProxy
from ..libs.sampler import easySampler
from ..libs.controlnet import easyControlnet, union_controlnet_types
from ..libs.conditioning import prompt_to_cond
@@ -19,6 +19,7 @@ from ..config import *
from .. import easyCache, sampler
any_type = AlwaysEqualProxy("*")
# 简易加载器完整
resolution_strings = [f"{width} x {height} (custom)" if width == 'width' and height == 'height' else f"{width} x {height}" for width, height in BASE_RESOLUTIONS]
class fullLoader:
@@ -1146,6 +1147,56 @@ class mochiLoader(fullLoader):
my_unique_id=my_unique_id
)
# lora
class loraSwitcher:
@classmethod
def INPUT_TYPES(s):
max_lora_num = 50
inputs = {
"required": {
"toggle": ("BOOLEAN", {"label_on": "on", "label_off": "off"}),
"select": ("INT", {"default": 1, "min": 1, "max": max_lora_num}),
"num_loras": ("INT", {"default": 1, "min": 1, "max": max_lora_num}),
"lora_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
},
"optional": {
"optional_lora_stack": ("LORA_STACK",),
},
}
for i in range(1, max_lora_num + 1):
inputs["optional"][f"lora_{i}_name"] = (
["None"] + folder_paths.get_filename_list("loras"), {"default": "None"})
return inputs
RETURN_TYPES = ("LORA_STACK", any_type)
RETURN_NAMES = ("lora_stack", "lora_name")
FUNCTION = "stack"
CATEGORY = "EasyUse/Loaders"
def stack(self, toggle, select,num_loras, lora_strength, optional_lora_stack=None, **kwargs):
if (toggle in [False, None, "False"]) or not kwargs:
return (None,'')
loras = []
# Import Stack values
if optional_lora_stack is not None:
loras.extend([l for l in optional_lora_stack if l[0] != "None"])
# Import Lora values
lora_name = kwargs.get(f"lora_{select}_name")
if not lora_name or lora_name == "None":
return (None,'')
loras.append((lora_name, lora_strength, lora_strength))
name = os.path.splitext(os.path.basename(str(lora_name)))[0]
return (loras, name)
class loraStack:
def __init__(self):
pass
@@ -1155,7 +1206,7 @@ class loraStack:
max_lora_num = 10
inputs = {
"required": {
"toggle": ("BOOLEAN", {"label_on": "enabled", "label_off": "disabled"}),
"toggle": ("BOOLEAN", {"label_on": "on", "label_off": "off"}),
"mode": (["simple", "advanced"],),
"num_loras": ("INT", {"default": 1, "min": 1, "max": max_lora_num}),
},
@@ -1482,6 +1533,7 @@ NODE_CLASS_MAPPINGS = {
"easy hunyuanDiTLoader": hunyuanDiTLoader,
"easy pixArtLoader": pixArtLoader,
"easy mochiLoader": mochiLoader,
"easy loraSwitcher": loraSwitcher,
"easy loraStack": loraStack,
"easy controlnetStack": controlnetStack,
"easy controlnetLoader": controlnetSimple,
@@ -1503,6 +1555,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy hunyuanDiTLoader": "EasyLoader (HunyuanDiT)",
"easy pixArtLoader": "EasyLoader (PixArt)",
"easy mochiLoader": "EasyLoader (Mochi)",
"easy loraSwitcher": "EasyLoraSwitcher",
"easy loraStack": "EasyLoraStack",
"easy controlnetStack": "EasyControlnetStack",
"easy controlnetLoader": "EasyControlnet",
+6 -4
View File
@@ -1369,10 +1369,12 @@ class showAnything:
if "anything" in kwargs:
for val in kwargs['anything']:
try:
if type(val) is str:
if isinstance(val, str):
values.append(val)
elif type(val) is list:
elif isinstance(val, list):
values = val
elif isinstance(val, (int, float, bool)):
values.append(str(val))
else:
val = json.dumps(val)
values.append(str(val))
@@ -1381,9 +1383,9 @@ class showAnything:
pass
if not extra_pnginfo:
print("Error: extra_pnginfo is empty")
pass
elif (not isinstance(extra_pnginfo[0], dict) or "workflow" not in extra_pnginfo[0]):
print("Error: extra_pnginfo[0] is not a dict or missing 'workflow' key")
pass
else:
workflow = extra_pnginfo[0]["workflow"]
node = next((x for x in workflow["nodes"] if str(x["id"]) == unique_id[0]), None)
+67 -9
View File
@@ -157,7 +157,9 @@ class stylesPromptSelector:
for file_name in os.listdir(styles_dir):
file = os.path.join(styles_dir, file_name)
if os.path.isfile(file) and file_name.endswith(".json"):
styles.append(file_name.split(".")[0])
if file_name != "fooocus_styles.json":
styles.append(file_name.split(".")[0])
return {
"required": {
"styles": (styles, {"default": "fooocus_styles"}),
@@ -165,6 +167,7 @@ class stylesPromptSelector:
"optional": {
"positive": ("STRING", {"forceInput": True}),
"negative": ("STRING", {"forceInput": True}),
"select_styles": ("EASY_PROMPT_STYLES", {}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
@@ -175,11 +178,12 @@ class stylesPromptSelector:
CATEGORY = 'EasyUse/Prompt'
FUNCTION = 'run'
def run(self, styles, positive='', negative='', prompt=None, extra_pnginfo=None, my_unique_id=None):
def run(self, styles, positive='', negative='', select_styles=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
values = []
all_styles = {}
positive_prompt, negative_prompt = '', negative
if styles == "fooocus_styles":
fooocus_custom_dir = os.path.join(FOOOCUS_STYLES_DIR, 'fooocus_styles.json')
if styles == "fooocus_styles" and not os.path.exists(fooocus_custom_dir):
file = os.path.join(RESOURCES_DIR, styles + '.json')
else:
file = os.path.join(FOOOCUS_STYLES_DIR, styles + '.json')
@@ -188,9 +192,14 @@ class stylesPromptSelector:
f.close()
for d in data:
all_styles[d['name']] = d
if my_unique_id in prompt:
if prompt[my_unique_id]["inputs"]['select_styles']:
values = prompt[my_unique_id]["inputs"]['select_styles'].split(',')
# if my_unique_id in prompt:
# if prompt[my_unique_id]["inputs"]['select_styles']:
# values = prompt[my_unique_id]["inputs"]['select_styles'].split(',')
if isinstance(select_styles, str):
values = select_styles.split(',')
else:
values = select_styles if select_styles else []
has_prompt = False
if len(values) == 0:
@@ -201,8 +210,10 @@ class stylesPromptSelector:
if "{prompt}" in all_styles[val]['prompt'] and has_prompt == False:
positive_prompt = all_styles[val]['prompt'].replace('{prompt}', positive)
has_prompt = True
else:
elif "{prompt}" in all_styles[val]['prompt']:
positive_prompt += ', ' + all_styles[val]['prompt'].replace(', {prompt}', '').replace('{prompt}', '')
else:
positive_prompt = all_styles[val]['prompt'] if positive_prompt == '' else positive_prompt + ', ' + all_styles[val]['prompt']
if 'negative_prompt' in all_styles[val]:
negative_prompt += ', ' + all_styles[val]['negative_prompt'] if negative_prompt else all_styles[val]['negative_prompt']
@@ -308,11 +319,56 @@ class promptLine:
return (rows, rows)
import comfy.utils
from server import PromptServer
from ..libs.messages import MessageCancelled, Message
any_type = AlwaysEqualProxy("*")
class promptAwait:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"now": (any_type,),
"prompt": ("STRING", {"multiline": True, "default": "", "placeholder":"Enter a prompt or use voice to enter to text"}),
"toolbar":("EASY_PROMPT_AWAIT_BAR",),
},
"optional":{
"prev": (any_type,),
},
"hidden": {"workflow_prompt": "PROMPT", "my_unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO"},
}
RETURN_TYPES = (any_type, "STRING", "BOOLEAN", "INT")
RETURN_NAMES = ("output", "prompt", "continue", "seed")
FUNCTION = "await_select"
CATEGORY = "EasyUse/Prompt"
def await_select(self, now, prompt, toolbar, prev=None, workflow_prompt=None, my_unique_id=None, extra_pnginfo=None, **kwargs):
id = my_unique_id
id = id.split('.')[len(id.split('.')) - 1] if "." in id else id
if ":" in id:
id = id.split(":")[0]
pbar = comfy.utils.ProgressBar(100)
pbar.update_absolute(30)
PromptServer.instance.send_sync('easyuse_prompt_await', {"id": id})
try:
res = Message.waitForMessage(id, asList=False)
if res is None or res == "-1":
result = (now, prompt, False, 0)
else:
input = now if res['select'] == 'now' or prev is None else prev
result = (input, res['prompt'], False if res['result'] == -1 else True, res['seed'] if res['unlock'] else res['last_seed'])
pbar.update_absolute(100)
return result
except MessageCancelled:
pbar.update_absolute(100)
raise comfy.model_management.InterruptProcessingException()
class promptConcat:
@classmethod
def INPUT_TYPES(cls):
return {"required": {
},
return {"required": {},
"optional": {
"prompt1": ("STRING", {"multiline": False, "default": "", "forceInput": True}),
"prompt2": ("STRING", {"multiline": False, "default": "", "forceInput": True}),
@@ -564,6 +620,7 @@ NODE_CLASS_MAPPINGS = {
"easy prompt": prompt,
"easy promptList": promptList,
"easy promptLine": promptLine,
"easy promptAwait": promptAwait,
"easy promptConcat": promptConcat,
"easy promptReplace": promptReplace,
"easy stylesSelector": stylesPromptSelector,
@@ -578,6 +635,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy prompt": "Prompt",
"easy promptList": "PromptList",
"easy promptLine": "PromptLine",
"easy promptAwait": "PromptAwait",
"easy promptConcat": "PromptConcat",
"easy promptReplace": "PromptReplace",
"easy stylesSelector": "Styles Selector",
+3 -32
View File
@@ -15,7 +15,6 @@ from ..libs.log import log_node_warn
from ..libs.utils import easySave, get_local_filepath, get_sd_version
from ..libs.sampler import alignYourStepsScheduler, gitsScheduler
from ..libs.xyplot import easyXYPlot
from ..libs.chooser import ChooserMessage, ChooserCancelled
from .. import easyCache, sampler
@@ -30,7 +29,7 @@ class samplerFull:
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS+NEW_SCHEDULERS,),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"image_output": (["Hide", "Preview", "Preview&Choose", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],),
"image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
"save_prefix": ("STRING", {"default": "ComfyUI"}),
},
@@ -401,34 +400,6 @@ class samplerFull:
del pipe
if image_output == 'Preview&Choose':
if my_unique_id not in ChooserMessage.stash:
ChooserMessage.stash[my_unique_id] = {}
my_stash = ChooserMessage.stash[my_unique_id]
PromptServer.instance.send_sync("easyuse-image-choose", {"id": my_unique_id, "urls": results})
# wait for selection
try:
selections = ChooserMessage.waitForMessage(my_unique_id, asList=True)
samples = samp_samples['samples']
samples = [samples[x] for x in selections if x >= 0] if len(selections) > 1 else [samples[0]]
new_images = [new_images[x] for x in selections if x >= 0] if len(selections) > 1 else [new_images[0]]
samp_images = [samp_images[x] for x in selections if x >= 0] if len(selections) > 1 else [samp_images[0]]
new_images = torch.stack(new_images, dim=0)
samp_images = torch.stack(samp_images, dim=0)
samples = torch.stack(samples, dim=0)
samp_samples = {"samples": samples}
new_pipe['samples'] = samp_samples
new_pipe['loader_settings']['batch_size'] = len(new_images)
except ChooserCancelled:
raise comfy.model_management.InterruptProcessingException()
new_pipe['images'] = new_images
new_pipe['samp_images'] = samp_images
return {"ui": {"images": results},
"result": sampler.get_output(new_pipe,)}
if image_output in ("Hide", "Hide&Save", "None"):
return {"ui":{}, "result":sampler.get_output(new_pipe,)}
@@ -594,7 +565,7 @@ class samplerSimple(samplerFull):
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
"image_output": (["Hide", "Preview", "Preview&Choose", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],{"default": "Preview"}),
"image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],{"default": "Preview"}),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
"save_prefix": ("STRING", {"default": "ComfyUI"}),
},
@@ -626,7 +597,7 @@ class samplerSimpleCustom(samplerFull):
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
"image_output": (["Hide", "Preview", "Preview&Choose", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],{"default": "None"}),
"image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],{"default": "None"}),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
"save_prefix": ("STRING", {"default": "ComfyUI"}),
},
+30 -9
View File
@@ -78,13 +78,14 @@ async def parse_csv(request):
@PromptServer.instance.routes.get("/easyuse/prompt/styles")
async def getStylesList(request):
if "name" in request.rel_url.query:
name = request.rel_url.query["name"]
if name == 'fooocus_styles':
file = os.path.join(RESOURCES_DIR, name+'.json')
cn_file = os.path.join(RESOURCES_DIR, name + '_cn.json')
style_name = request.rel_url.query["name"]
fooocus_custom_dir = os.path.join(FOOOCUS_STYLES_DIR, 'fooocus_styles.json')
if style_name == 'fooocus_styles' and not os.path.exists(fooocus_custom_dir):
file = os.path.join(RESOURCES_DIR, style_name+'.json')
cn_file = os.path.join(RESOURCES_DIR, style_name + '_cn.json')
else:
file = os.path.join(FOOOCUS_STYLES_DIR, name+'.json')
cn_file = os.path.join(FOOOCUS_STYLES_DIR, name + '_cn.json')
file = os.path.join(FOOOCUS_STYLES_DIR, style_name+'.json')
cn_file = os.path.join(FOOOCUS_STYLES_DIR, style_name + '_cn.json')
cn_data = None
if os.path.isfile(cn_file):
f = open(cn_file, 'r', encoding='utf-8')
@@ -103,13 +104,25 @@ async def getStylesList(request):
key = ' '.join(
word.upper() if word.lower() in ['mre', 'sai', '3d'] else word.capitalize() for word in
words)
img_name = '_'.join(words).lower()
if "name_cn" in d:
nd['name_cn'] = d['name_cn']
elif cn_data:
nd['name_cn'] = cn_data[key] if key in cn_data else key
nd["name"] = d['name']
nd['imgName'] = img_name
if "thumbnail" in d:
thumbnail = d['thumbnail']
if isinstance(d['thumbnail'], str):
nd['thumbnail'] = thumbnail if "http" in thumbnail else f'/easyuse/prompt/styles/image?path={thumbnail}'
elif isinstance(d['thumbnail'], list):
nd['thumbnail'] = [thumb if "http" in thumb else f'/easyuse/prompt/styles/image?path={thumb}' for thumb in thumbnail]
else:
nd['thumbnail'] = f'/easyuse/prompt/styles/image?name={name}&styles_name={style_name}'
if "thumbnail_variant" in d:
nd['thumbnailVariant'] = d['thumbnail_variant']
if "media_type" in d:
nd['mediaType'] = d['media_type']
if "media_subtype" in d:
nd['mediaSubtype'] = d['media_subtype']
if "prompt" in d:
nd['prompt'] = d['prompt']
if "negative_prompt" in d:
@@ -122,7 +135,15 @@ async def getStylesList(request):
@PromptServer.instance.routes.get("/easyuse/prompt/styles/image")
async def getStylesImage(request):
styles_name = request.rel_url.query["styles_name"] if "styles_name" in request.rel_url.query else None
if "name" in request.rel_url.query:
if "path" in request.rel_url.query:
path = request.rel_url.query["path"]
file = os.path.join(FOOOCUS_STYLES_DIR, 'samples', path)
parent_file = os.path.join(FOOOCUS_STYLES_DIR, path)
if os.path.isfile(file):
return web.FileResponse(file)
elif os.path.isfile(parent_file):
return web.FileResponse(parent_file)
elif "name" in request.rel_url.query:
name = request.rel_url.query["name"]
if os.path.exists(os.path.join(FOOOCUS_STYLES_DIR, 'samples')):
file = os.path.join(FOOOCUS_STYLES_DIR, 'samples', name + '.jpg')
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-easy-use"
description = "To enhance the usability of ComfyUI, optimizations and integrations have been implemented for several commonly used nodes."
version = "1.3.1"
version = "1.3.3"
license = { file = "LICENSE" }
dependencies = ["diffusers", "accelerate", "clip_interrogator>=0.6.0", "sentencepiece", "lark", "onnxruntime", "spandrel", "opencv-python", "matplotlib", "peft"]
+1923 -1373
View File
File diff suppressed because it is too large Load Diff
-279
View File
@@ -1,279 +0,0 @@
{
"Fooocus V2": "Fooocus V2扩展词",
"Default (Slightly Cinematic)": "默认(轻微的电影感)",
"Fooocus Enhance": "Fooocus-优化增强",
"Fooocus Cinematic": "Fooocus-电影感",
"Fooocus Sharp": "Fooocus-锐化",
"Fooocus Masterpiece": "Fooocus-杰作",
"Fooocus Photograph": "Fooocus-照片",
"Fooocus Negative": "Fooocus-反向提示词",
"SAI 3D Model": "SAI-3D模型",
"SAI Analog Film": "SAI-模拟电影",
"SAI Anime": "SAI-动漫",
"SAI Cinematic": "SAI-电影片段",
"SAI Comic Book": "SAI-漫画",
"SAI Craft Clay": "SAI-工艺粘土",
"SAI Digital Art": "SAI-数字艺术",
"SAI Enhance": "SAI-增强",
"SAI Fantasy Art": "SAI-奇幻艺术",
"SAI Isometric": "SAI-等距风格",
"SAI Line Art": "SAI-线条艺术",
"SAI Lowpoly": "SAI-低多边形",
"SAI Neonpunk": "SAI-霓虹朋克",
"SAI Origami": "SAI-折纸",
"SAI Photographic": "SAI-摄影",
"SAI Pixel Art": "SAI-像素艺术",
"SAI Texture": "SAI-纹理",
"MRE Cinematic Dynamic": "MRE-史诗电影",
"MRE Spontaneous Picture": "MRE-自然的抓拍照片",
"MRE Artistic Vision": "MRE-艺术视觉",
"MRE Dark Dream": "MRE-黑暗梦境",
"MRE Gloomy Art": "MRE-阴郁艺术",
"MRE Bad Dream": "MRE-噩梦",
"MRE Underground": "MRE-阴森地下",
"MRE Surreal Painting": "MRE-超现实主义绘画",
"MRE Dynamic Illustration": "MRE-动态插画",
"MRE Undead Art": "MRE-遗忘艺术家作品",
"MRE Elemental Art": "MRE-元素艺术",
"MRE Space Art": "MRE-空间艺术",
"MRE Ancient Illustration": "MRE-古代插图",
"MRE Brave Art": "MRE-勇敢艺术",
"MRE Heroic Fantasy": "MRE-英雄幻想",
"MRE Dark Cyberpunk": "MRE-黑暗赛博朋克",
"MRE Lyrical Geometry": "MRE-抒情几何抽象画",
"MRE Sumi E Symbolic": "MRE-墨绘长笔画",
"MRE Sumi E Detailed": "MRE-精细墨绘画",
"MRE Manga": "MRE-日本漫画",
"MRE Anime": "MRE-日本动画片",
"MRE Comic": "MRE-成人漫画书插画",
"Ads Advertising": "广告-广告",
"Ads Automotive": "广告-汽车",
"Ads Corporate": "广告-企业品牌",
"Ads Fashion Editorial": "广告-时尚编辑",
"Ads Food Photography": "广告-食品摄影",
"Ads Gourmet Food Photography": "广告-顶级美食摄影",
"Ads Luxury": "广告-奢侈品",
"Ads Real Estate": "广告-房地产",
"Ads Retail": "广告-零售",
"Artstyle Abstract": "艺术风格-抽象",
"Artstyle Abstract Expressionism": "艺术风格-抽象表现主义",
"Artstyle Art Deco": "艺术风格-装饰艺术",
"Artstyle Art Nouveau": "艺术风格-新艺术",
"Artstyle Constructivist": "艺术风格-构造主义",
"Artstyle Cubist": "艺术风格-立体主义",
"Artstyle Expressionist": "艺术风格-表现主义",
"Artstyle Graffiti": "艺术风格-涂鸦",
"Artstyle Hyperrealism": "艺术风格-超写实主义",
"Artstyle Impressionist": "艺术风格-印象派",
"Artstyle Pointillism": "艺术风格-点彩派",
"Artstyle Pop Art": "艺术风格-波普艺术",
"Artstyle Psychedelic": "艺术风格-迷幻",
"Artstyle Renaissance": "艺术风格-文艺复兴",
"Artstyle Steampunk": "艺术风格-蒸汽朋克",
"Artstyle Surrealist": "艺术风格-超现实主义",
"Artstyle Typography": "艺术风格-字体设计",
"Artstyle Watercolor": "艺术风格-水彩",
"Futuristic Biomechanical": "未来主义-生物机械",
"Futuristic Biomechanical Cyberpunk": "未来主义-生物机械-赛博朋克",
"Futuristic Cybernetic": "未来主义-人机融合",
"Futuristic Cybernetic Robot": "未来主义-人机融合-机器人",
"Futuristic Cyberpunk Cityscape": "未来主义-赛博朋克城市",
"Futuristic Futuristic": "未来主义-未来主义",
"Futuristic Retro Cyberpunk": "未来主义-复古赛博朋克",
"Futuristic Retro Futurism": "未来主义-复古未来主义",
"Futuristic Sci Fi": "未来主义-科幻",
"Futuristic Vaporwave": "未来主义-蒸汽波",
"Game Bubble Bobble": "游戏-泡泡龙",
"Game Cyberpunk Game": "游戏-赛博朋克游戏",
"Game Fighting Game": "游戏-格斗游戏",
"Game Gta": "游戏-侠盗猎车手",
"Game Mario": "游戏-马里奥",
"Game Minecraft": "游戏-我的世界",
"Game Pokemon": "游戏-宝可梦",
"Game Retro Arcade": "游戏-复古街机",
"Game Retro Game": "游戏-复古游戏",
"Game Rpg Fantasy Game": "游戏-角色扮演幻想游戏",
"Game Strategy Game": "游戏-策略游戏",
"Game Streetfighter": "游戏-街头霸王",
"Game Zelda": "游戏-塞尔达传说",
"Misc Architectural": "其他-建筑",
"Misc Disco": "其他-迪斯科",
"Misc Dreamscape": "其他-梦境",
"Misc Dystopian": "其他-反乌托邦",
"Misc Fairy Tale": "其他-童话故事",
"Misc Gothic": "其他-哥特风",
"Misc Grunge": "其他-垮掉的",
"Misc Horror": "其他-恐怖",
"Misc Kawaii": "其他-可爱",
"Misc Lovecraftian": "其他-洛夫克拉夫特",
"Misc Macabre": "其他-恐怖",
"Misc Manga": "其他-漫画",
"Misc Metropolis": "其他-大都市",
"Misc Minimalist": "其他-极简主义",
"Misc Monochrome": "其他-单色",
"Misc Nautical": "其他-航海",
"Misc Space": "其他-太空",
"Misc Stained Glass": "其他-彩色玻璃",
"Misc Techwear Fashion": "其他-科技时尚",
"Misc Tribal": "其他-部落",
"Misc Zentangle": "其他-禅绕画",
"Papercraft Collage": "手工艺-拼贴",
"Papercraft Flat Papercut": "手工艺-平面剪纸",
"Papercraft Kirigami": "手工艺-切纸",
"Papercraft Paper Mache": "手工艺-纸浆塑造",
"Papercraft Paper Quilling": "手工艺-纸艺卷轴",
"Papercraft Papercut Collage": "手工艺-剪纸拼贴",
"Papercraft Papercut Shadow Box": "手工艺-剪纸影箱",
"Papercraft Stacked Papercut": "手工艺-层叠剪纸",
"Papercraft Thick Layered Papercut": "手工艺-厚层剪纸",
"Photo Alien": "摄影-外星人",
"Photo Film Noir": "摄影-黑色电影",
"Photo Glamour": "摄影-魅力",
"Photo Hdr": "摄影-高动态范围",
"Photo Iphone Photographic": "摄影-苹果手机摄影",
"Photo Long Exposure": "摄影-长曝光",
"Photo Neon Noir": "摄影-霓虹黑色",
"Photo Silhouette": "摄影-轮廓",
"Photo Tilt Shift": "摄影-移轴",
"Cinematic Diva": "电影女主角",
"Abstract Expressionism": "抽象表现主义",
"Academia": "学术",
"Action Figure": "动作人偶",
"Adorable 3D Character": "可爱的3D角色",
"Adorable Kawaii": "可爱的卡哇伊",
"Art Deco": "装饰艺术",
"Art Nouveau": "新艺术,美丽艺术",
"Astral Aura": "星体光环",
"Avant Garde": "前卫",
"Baroque": "巴洛克",
"Bauhaus Style Poster": "包豪斯风格海报",
"Blueprint Schematic Drawing": "蓝图示意图",
"Caricature": "漫画",
"Cel Shaded Art": "卡通渲染",
"Character Design Sheet": "角色设计表",
"Classicism Art": "古典主义艺术",
"Color Field Painting": "色彩领域绘画",
"Colored Pencil Art": "彩色铅笔艺术",
"Conceptual Art": "概念艺术",
"Constructivism": "建构主义",
"Cubism": "立体主义",
"Dadaism": "达达主义",
"Dark Fantasy": "黑暗奇幻",
"Dark Moody Atmosphere": "黑暗忧郁气氛",
"Dmt Art Style": "迷幻艺术风格",
"Doodle Art": "涂鸦艺术",
"Double Exposure": "双重曝光",
"Dripping Paint Splatter Art": "滴漆飞溅艺术",
"Expressionism": "表现主义",
"Faded Polaroid Photo": "褪色的宝丽来照片",
"Fauvism": "野兽派",
"Flat 2d Art": "平面 2D 艺术",
"Fortnite Art Style": "堡垒之夜艺术风格",
"Futurism": "未来派",
"Glitchcore": "故障核心",
"Glo Fi": "光明高保真",
"Googie Art Style": "古吉艺术风格",
"Graffiti Art": "涂鸦艺术",
"Harlem Renaissance Art": "哈莱姆文艺复兴艺术",
"High Fashion": "高级时装",
"Idyllic": "田园诗般",
"Impressionism": "印象派",
"Infographic Drawing": "信息图表绘图",
"Ink Dripping Drawing": "滴墨绘画",
"Japanese Ink Drawing": "日式水墨画",
"Knolling Photography": "规律摆放摄影",
"Light Cheery Atmosphere": "轻松愉快的气氛",
"Logo Design": "标志设计",
"Luxurious Elegance": "奢华优雅",
"Macro Photography": "微距摄影",
"Mandola Art": "曼陀罗艺术",
"Marker Drawing": "马克笔绘图",
"Medievalism": "中世纪主义",
"Minimalism": "极简主义",
"Neo Baroque": "新巴洛克",
"Neo Byzantine": "新拜占庭",
"Neo Futurism": "新未来派",
"Neo Impressionism": "新印象派",
"Neo Rococo": "新洛可可",
"Neoclassicism": "新古典主义",
"Op Art": "欧普艺术",
"Ornate And Intricate": "华丽而复杂",
"Pencil Sketch Drawing": "铅笔素描",
"Pop Art 2": "流行艺术2",
"Rococo": "洛可可",
"Silhouette Art": "剪影艺术",
"Simple Vector Art": "简单矢量艺术",
"Sketchup": "草图",
"Steampunk 2": "赛博朋克2",
"Surrealism": "超现实主义",
"Suprematism": "至上主义",
"Terragen": "地表风景",
"Tranquil Relaxing Atmosphere": "宁静轻松的氛围",
"Sticker Designs": "贴纸设计",
"Vibrant Rim Light": "生动的边缘光",
"Volumetric Lighting": "体积照明",
"Watercolor 2": "水彩2",
"Whimsical And Playful": "异想天开、俏皮",
"Mk Chromolithography": "MK 色彩版画",
"Mk Cross Processing Print": "MK 交叉过程打印",
"Mk Dufaycolor Photograph": "MK 杜法色彩照片",
"Mk Herbarium": "MK 植物标本馆",
"Mk Punk Collage": "MK 朋克拼贴画",
"Mk Mosaic": "MK 镶嵌图",
"Mk Van Gogh": "MK 梵高",
"Mk Coloring Book": "MK 色彩书",
"Mk Singer Sargent": "MK 辛格 · 萨尔生特",
"Mk Pollock": "MK 波洛克",
"Mk Basquiat": "MK 巴斯奎特",
"Mk Andy Warhol": "MK 安迪 · 沃霍尔",
"Mk Halftone Print": "MK 半色版画",
"Mk Gond Painting": "MK 贡德绘画",
"Mk Albumen Print": "MK 白蛋清印刷",
"Mk Aquatint Print": "MK 水蚀刻印刷",
"Mk Anthotype Print": "MK 花纹版画",
"Mk Inuit Carving": "MK 因纽特雕塑",
"Mk Bromoil Print": "MK 溴油印刷",
"Mk Calotype Print": "MK 卡洛雅图印刷",
"Mk Color Sketchnote": "MK色彩素描笔记",
"Mk Cibulak Porcelain": "MK 西布拉瓷器",
"Mk Alcohol Ink Art": "MK 酒精水彩艺术",
"Mk One Line Art": "MK 一线画",
"Mk Blacklight Paint": "MK 黑光油漆",
"Mk Carnival Glass": "MK 嘉年华玻璃",
"Mk Cyanotype Print": "MK 青色版画",
"Mk Cross Stitching": "MK 交叉针织",
"Mk Encaustic Paint": "MK 蜡漆",
"Mk Embroidery": "MK 刺绣",
"Mk Gyotaku": "MK 鱼拓版画",
"Mk Luminogram": "MK 光感影像",
"Mk Lite Brite Art": "MK 彩色灯泡艺术",
"Mk Mokume Gane": "MK 木金工艺",
"Pebble Art": "MK 鹅卵石艺术",
"Mk Palekh": "MK 帕列赫",
"Mk Suminagashi": "MK 澄洗画",
"Mk Scrimshaw": "MK 丝线绣",
"Mk Shibori": "MK 湿布雕版印刷",
"Mk Vitreous Enamel": "MK 玻璃珐琅",
"Mk Ukiyo E": "MK 浮世绘",
"Mk Vintage Airline Poster": "MK 古董航空公司海报",
"Mk Vintage Travel Poster": "MK 古董旅行海报",
"Mk Bauhaus Style": "Mk 包豪斯风格",
"Mk Afrofuturism": "Mk 非洲未来主义",
"Mk Atompunk": "Mk 原子朋克",
"Mk Constructivism": "Mk 构成派",
"Mk Chicano Art": "Mk 西班牙裔美国艺术",
"Mk De Stijl": "Mk 去风格派",
"Mk Dayak Art": "Mk 达雅克艺术",
"Mk Fayum Portrait": "Mk 法尤姆肖像画",
"Mk Illuminated Manuscript": "Mk 彩绘手稿",
"Mk Kalighat Painting": "Mk 卡利加特绘画",
"Mk Madhubani Painting": "Mk 马杜班尼绘画",
"Mk Pictorialism": "Mk 描绘主义",
"Mk Pichwai Painting": "Mk 皮奇瓦伊绘画",
"Mk Patachitra Painting": "Mk 帕塔基特拉绘画",
"Mk Samoan Art Inspired": "Mk 萨莫亚艺术启发的",
"Mk Tlingit Art": "Mk 特林吉特艺术",
"Mk Adnate Style": "Mk 阿达内特风格",
"Mk Ron English Style": "Mk 罗恩英国风格",
"Mk Shepard Fairey Style": "Mk 舒帕德 · 费尔利风格"
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long