Compare commits

..
16 Commits
Author SHA1 Message Date
yolain 0104f7f6a9 Upgrade v1.2.8 to ComfyRegistry 2025-03-10 11:10:54 +08:00
yolain 6b1f5cbf69 Modify some front-end style displays 2025-03-10 11:01:58 +08:00
yolain f888e3d75d Merge pull request #685 from facok/main
fix: wildcards, improve text encoding handling to prevent Chinese character garb…
2025-03-08 15:31:49 +08:00
facok 16631d21d9 fix: improve text encoding handling to prevent Chinese character garbling
ISO-8859-1 encoding can forcibly read any byte (it maps each byte directly to its corresponding character). This means it won't throw any decoding errors, but it will incorrectly interpret UTF-8 encoded Chinese characters as other characters, resulting in garbled text (mojibake).
ISO-8859-1编码可以强制读取任何字节(它会把每个字节都映射到对应的字符)
这意味着它不会抛出解码错误,但会把UTF-8编码的中文字符错误解释为其他字符
导致中文显示为乱码
2025-03-07 17:39:53 +08:00
yolain ccb4ba08fc Fix the issue that the output images does not replace the preview images after the kSamplers has finished sampling due to ComfyUI Frontend adjustment 2025-03-06 15:42:49 +08:00
yolain 0daf114fe8 Add refine_foreground for ben2 2025-02-24 14:41:22 +08:00
yolain 4e9c9c897c Fix ben2 using the wrong model 2025-02-24 14:24:32 +08:00
yolain 31fde1ae34 Add locale files 2025-02-23 15:08:17 +08:00
yolain aadbb0b389 Fix human segmentation not working in latest ComfyUI-frontend #668 2025-02-20 12:38:44 +08:00
yolain 52a8e7faf3 Fix some chinese translation errors 2025-02-18 22:55:30 +08:00
yolain 3893873085 Fix stylesSelector unable to get selections in ComfyUI_frontend latest version #658 2025-02-14 12:43:36 +08:00
yolain 037080ac39 Add option None to ckpt_name of easy fullLoader and easy fluxLoader #652. 2025-02-13 18:09:40 +08:00
yolain 4738313b64 Fix encodeURIComponent URI malformed when special characters appear in the model name 2025-02-12 13:05:43 +08:00
yolain e842c3bd06 Merge pull request #657 from newideas99/fix-clip-vision-urls
Fix CLIP vision model URLs and improve download error handling
2025-02-11 11:53:13 +08:00
newideas99 fa73da5a00 Update version to 1.2.8 2025-02-10 22:21:55 -05:00
Jacob Ferrari ffe26e8571 Fix CLIP vision model URLs and improve download error handling
- Update CLIP vision model URLs for IPAdapter and DynamiCrafter
- Improve error handling for model downloads with clearer error messages
- Add changelog entry for v1.2.8
2025-02-11 02:14:27 +00:00
30 changed files with 7991 additions and 152 deletions
+5
View File
@@ -52,6 +52,11 @@ git clone https://github.com/yolain/ComfyUI-Easy-Use
## 📜 更新日志
**v1.2.8**
- 修复了一些BUG (😹)
- 增加了多语言目录
**v1.2.7**
- 优化管理节点组显示
+8 -2
View File
@@ -47,10 +47,16 @@ Double-click install.bat to install the required dependencies
## 📜 Changelog
**v1.2.8**
- Added the multi-language catalog
- Fix CLIP vision model download URLs for IPAdapter and DynamiCrafter
- Improve error handling for model downloads with clearer error messages and better handling of download failures
**v1.2.7**
- Optimize display of the node maps
- Add `ben2` on `easy imageRemBg`
- Added `ben2` on `easy imageRemBg`
- Using a new way to display the models thumbnails in the loaders (supported diffusion_models、lors、checkpoints)
**v1.2.6**
@@ -486,4 +492,4 @@ If my custom nodes has added value to your day, consider indulging in a coffee t
My gratitude extends to the generous souls who bestow a star. Your support is much appreciated!
[![Stargazers repo roster for @yolain/ComfyUI-Easy-Use](https://reporoster.com/stars/yolain/ComfyUI-Easy-Use)](https://github.com/yolain/ComfyUI-Easy-Use/stargazers)
[![Stargazers repo roster for @yolain/ComfyUI-Easy-Use](https://reporoster.com/stars/yolain/ComfyUI-Easy-Use)](https://github.com/yolain/ComfyUI-Easy-Use/stargazers)
+30 -3
View File
@@ -1,23 +1,50 @@
__version__ = "1.2.7"
__version__ = "1.2.8"
import yaml
import json
import os
import folder_paths
import importlib
cwd_path = os.path.dirname(os.path.realpath(__file__))
comfy_path = folder_paths.base_path
NODE_CLASS_MAPPINGS = {}
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}
NODE_DISPLAY_NAME_MAPPINGS = {**NODE_DISPLAY_NAME_MAPPINGS, **imported_module.NODE_DISPLAY_NAME_MAPPINGS}
# transfer python nodes to locale file
# for i in imported_module.NODE_CLASS_MAPPINGS:
# locale[i] = {
# "display_name": imported_module.NODE_DISPLAY_NAME_MAPPINGS[i] if i in imported_module.NODE_DISPLAY_NAME_MAPPINGS else i,
# "inputs":{},
# "outputs":{},
# }
# node_class = imported_module.NODE_CLASS_MAPPINGS[i]
# input_types = node_class.INPUT_TYPES()
# if "required" in input_types:
# for j in input_types["required"]:
# locale[i]['inputs'][j] = {"name": j}
# if "optional" in input_types:
# for j in input_types["optional"]:
# locale[i]['inputs'][j] = {"name": j}
# count = 0
# if "RETURN_NAMES" in node_class.__dict__:
# for j in node_class.RETURN_NAMES:
# locale[i]['outputs'][str(count)] = {"name": j}
# count+=1
# en_json_path = os.path.join(cwd_path,'locales/en/nodeDefs.json')
# with open(en_json_path, 'w', encoding='utf-8') as f:
# json.dump(locale, f, ensure_ascii=False, indent=2)
cwd_path = os.path.dirname(os.path.realpath(__file__))
comfy_path = folder_paths.base_path
#Wildcards
from .py.libs.wildcards import read_wildcard_dict
+30
View File
@@ -0,0 +1,30 @@
{
"settingsCategories": {
"Hotkeys": "Hotkeys",
"Nodes": "Nodes",
"NodesMap": "NodesMap"
},
"nodeCategories": {
"Util": "Util",
"Seed": "Seed",
"Prompt": "Prompt",
"Loaders": "Loaders",
"Adapter": "Adapter",
"Inpaint": "Inpaint",
"PreSampling": "PreSampling",
"Sampler": "Sampler",
"Fix": "Fix",
"Pipe": "Pipe",
"XY Inputs": "XY Inputs",
"Image": "Image",
"Segmentation": "Segmentation",
"\uD83D\uDEAB Deprecated": "\uD83D\uDEAB Deprecated",
"Type": "Type",
"Math": "Math",
"Switch": "Switch",
"Index Switch": "Index Switch",
"While Loop": "While Loop",
"For Loop": "For Loop",
"LoadImage": "Load Image"
}
}
File diff suppressed because it is too large Load Diff
+67
View File
@@ -0,0 +1,67 @@
{
"EasyUse_Hotkeys_AddGroup": {
"name": "Enable Shift+g to add the selected nodes to a group",
"tooltip": "From v1.2.39, you can use Ctrl+g instead"
},
"EasyUse_Hotkeys_cleanVRAMUsed": {
"name": "Enable Shift+r to unload model and node cache"
},
"EasyUse_Hotkeys_toggleNodesMap": {
"name": "Enable Shift+m to toggle nodes map"
},
"EasyUse_Hotkeys_AlignSelectedNodes": {
"name": "Enable Shift+Up/Down/Left/Right and Shift+Ctrl+Alt+Left/Right to align selected nodes",
"tooltip": "Shift+Up/Down/Left/Right can align selected nodes, Shift+Ctrl+Alt+Left/Right can distribute nodes horizontally/vertically"
},
"EasyUse_Hotkeys_NormalizeSelectedNodes": {
"name": "Enable Shift+Ctrl+Left/Right to normalize selected nodes",
"tooltip": "Enable Shift+Ctrl+Left to normalize width and Shift+Ctrl+Right to normalize height"
},
"EasyUse_Hotkeys_NodesTemplate": {
"name": "Enable Alt+1~9 to paste node templates into the workflow"
},
"EasyUse_Hotkeys_JumpNearestNodes": {
"name": "Enable Up/Down/Left/Right to jump to the nearest node"
},
"EasyUse_ContextMenu_SubDirectories": {
"name": "Enable automatic nesting of subdirectories in the context menu"
},
"EasyUse_ContextMenu_ModelsThumbnails": {
"name": "Enable model preview thumbnails"
},
"EasyUse_ContextMenu_NodesSort": {
"name": "Enable A~Z sorting of new nodes in the context menu"
},
"EasyUse_ContextMenu_QuickOptions": {
"name": "Use three quick buttons in the context menu",
"options": {
"At the forefront": "At the forefront",
"At the end": "At the end",
"Disable": "Disable"
}
},
"EasyUse_Nodes_Runtime": {
"name": "Enable node runtime display"
},
"EasyUse_Nodes_ChainGetSet": {
"name": "Enable chaining of get and set points with the parent node"
},
"EasyUse_NodesMap_Sorting": {
"name": "Manage nodes group sorting mode",
"tooltip": "Automatically sort by default. If set to manual, groups can be drag and dropped and the order will be saved.",
"options": {
"Auto sorting": "Auto sorting",
"Manual drag&drop sorting": "Manual drag&drop sorting"
}
},
"EasyUse_NodesMap_DisplayNodeID": {
"name": "Enable node ID display"
},
"EasyUse_NodesMap_DisplayGroupOnly": {
"name": "Show groups only"
},
"EasyUse_NodesMap_Enable": {
"name": "Enable Group Map",
"tooltip": "You need to refresh the page to update successfully"
}
}
+30
View File
@@ -0,0 +1,30 @@
{
"settingsCategories": {
"Hotkeys": "Raccourcis",
"Nodes": "Nœuds",
"NodesMap": "Carte des nœuds"
},
"nodeCategories": {
"Util": "Utilitaire",
"Seed": "Graine",
"Prompt": "Prompt",
"Loaders": "Chargeurs",
"Adapter": "Adaptateur",
"Inpaint": "Retouche",
"PreSampling": "Pré-échantillonnage",
"Sampler": "Échantillonneur",
"Fix": "Correction",
"Pipe": "Pipeline",
"XY Inputs": "Entrées XY",
"Image": "Image",
"Segmentation": "Segmentation",
"\uD83D\uDEAB Deprecated": "\uD83D\uDEAB Obsolète",
"Type": "Type",
"Math": "Mathématiques",
"Switch": "Interrupteur",
"Index Switch": "Interrupteur d'index",
"While Loop": "Boucle While",
"For Loop": "Boucle For",
"LoadImage": "Charger l'image"
}
}
+67
View File
@@ -0,0 +1,67 @@
{
"EasyUse_Hotkeys_AddGroup": {
"name": "Activer Shift+g pour ajouter les nœuds sélectionnés à un groupe",
"tooltip": "Depuis la v1.2.39, vous pouvez utiliser Ctrl+g à la place"
},
"EasyUse_Hotkeys_cleanVRAMUsed": {
"name": "Activer Shift+r pour décharger le cache du modèle et des nœuds"
},
"EasyUse_Hotkeys_toggleNodesMap": {
"name": "Activer Shift+m pour basculer la carte des nœuds"
},
"EasyUse_Hotkeys_AlignSelectedNodes": {
"name": "Activer Shift+Up/Down/Left/Right et Shift+Ctrl+Alt+Left/Right pour aligner les nœuds sélectionnés",
"tooltip": "Shift+Up/Down/Left/Right peut aligner les nœuds sélectionnés, Shift+Ctrl+Alt+Left/Right peut les répartir horizontalement/verticalement"
},
"EasyUse_Hotkeys_NormalizeSelectedNodes": {
"name": "Activer Shift+Ctrl+Left/Right pour normaliser les nœuds sélectionnés",
"tooltip": "Activer Shift+Ctrl+Left pour normaliser la largeur et Shift+Ctrl+Right pour normaliser la hauteur"
},
"EasyUse_Hotkeys_NodesTemplate": {
"name": "Activer Alt+1~9 pour coller les modèles de nœuds dans le workflow"
},
"EasyUse_Hotkeys_JumpNearestNodes": {
"name": "Activer Up/Down/Left/Right pour passer au nœud le plus proche"
},
"EasyUse_ContextMenu_SubDirectories": {
"name": "Activer l'imbrication automatique des sous-répertoires dans le menu contextuel"
},
"EasyUse_ContextMenu_ModelsThumbnails": {
"name": "Activer les vignettes d'aperçu du modèle"
},
"EasyUse_ContextMenu_NodesSort": {
"name": "Activer le tri A~Z des nouveaux nœuds dans le menu contextuel"
},
"EasyUse_ContextMenu_QuickOptions": {
"name": "Utiliser trois boutons rapides dans le menu contextuel",
"options": {
"At the forefront": "À l'avant-plan",
"At the end": "À la fin",
"Disable": "Désactiver"
}
},
"EasyUse_Nodes_Runtime": {
"name": "Activer l'affichage du temps d'exécution des nœuds"
},
"EasyUse_Nodes_ChainGetSet": {
"name": "Activer le chaînage des points get et set avec le nœud parent"
},
"EasyUse_NodesMap_Sorting": {
"name": "Gérer le mode de tri des groupes de nœuds",
"tooltip": "Tri automatique par défaut. Si défini sur manuel, les groupes peuvent être glissés-déposés et l'ordre sera sauvegardé.",
"options": {
"Auto sorting": "Tri automatique",
"Manual drag&drop sorting": "Tri manuel par glisser-déposer"
}
},
"EasyUse_NodesMap_DisplayNodeID": {
"name": "Activer l'affichage de l'ID du nœud"
},
"EasyUse_NodesMap_DisplayGroupOnly": {
"name": "Afficher uniquement les groupes"
},
"EasyUse_NodesMap_Enable": {
"name": "Activer la carte des groupes",
"tooltip": "Vous devez actualiser la page pour mettre à jour"
}
}
+30
View File
@@ -0,0 +1,30 @@
{
"settingsCategories": {
"Hotkeys": "ショートカットキー",
"Nodes": "ノード",
"NodesMap": "ノードマップ"
},
"nodeCategories": {
"Util": "ユーティリティ",
"Seed": "シード",
"Prompt": "プロンプト",
"Loaders": "ローダー",
"Adapter": "アダプター",
"Inpaint": "インペイント",
"PreSampling": "プリサンプリング",
"Sampler": "サンプラー",
"Fix": "フィックス",
"Pipe": "パイプ",
"XY Inputs": "XY入力",
"Image": "画像",
"Segmentation": "セグメンテーション",
"\uD83D\uDEAB Deprecated": "🚫 非推奨",
"Type": "タイプ",
"Math": "数学",
"Switch": "スイッチ",
"Index Switch": "インデックススイッチ",
"While Loop": "Whileループ",
"For Loop": "Forループ",
"LoadImage": "画像読み込み"
}
}
+67
View File
@@ -0,0 +1,67 @@
{
"EasyUse_Hotkeys_AddGroup": {
"name": "Shift+gを使用して選択したノードをグループに追加する",
"tooltip": "v1.2.39以降、Ctrl+gが使用できます"
},
"EasyUse_Hotkeys_cleanVRAMUsed": {
"name": "Shift+rを使用してモデルおよびノードキャッシュをアンロードする"
},
"EasyUse_Hotkeys_toggleNodesMap": {
"name": "Shift+mを使用してノードマップを表示/非表示にします"
},
"EasyUse_Hotkeys_AlignSelectedNodes": {
"name": "Shift+上/下/左/右およびShift+Ctrl+Alt+左/右を使用して選択したノードを整列する",
"tooltip": "Shift+上/下/左/右で選択したノードを整列し、Shift+Ctrl+Alt+左/右で水平方向/垂直方向に分布させる"
},
"EasyUse_Hotkeys_NormalizeSelectedNodes": {
"name": "Shift+Ctrl+左/右を使用して選択したノードのサイズを正規化する",
"tooltip": "Shift+Ctrl+左で幅を、Shift+Ctrl+右で高さを正規化する"
},
"EasyUse_Hotkeys_NodesTemplate": {
"name": "Alt+1~9を使用してワークフローにノードテンプレートを貼り付ける"
},
"EasyUse_Hotkeys_JumpNearestNodes": {
"name": "上/下/左/右を使用して最も近いノードにジャンプする"
},
"EasyUse_ContextMenu_SubDirectories": {
"name": "コンテキストメニューでサブディレクトリを自動でネストする"
},
"EasyUse_ContextMenu_ModelsThumbnails": {
"name": "モデルプレビューサムネイルを有効にする"
},
"EasyUse_ContextMenu_NodesSort": {
"name": "コンテキストメニューで新規ノードをA~Z順に並べ替える"
},
"EasyUse_ContextMenu_QuickOptions": {
"name": "コンテキストメニューで3つのクイックボタンを使用する",
"options": {
"At the forefront": "最前面に",
"At the end": "最後に",
"Disable": "無効"
}
},
"EasyUse_Nodes_Runtime": {
"name": "ノードの実行時間表示を有効にする"
},
"EasyUse_Nodes_ChainGetSet": {
"name": "親ノードと取得/設定ポイントを連結することを有効にする"
},
"EasyUse_NodesMap_Sorting": {
"name": "ノードグループの並べ替えモードを管理する",
"tooltip": "デフォルトで自動的に並べ替えます。マニュアルに設定した場合、グループをドラッグアンドドロップで並べ替え、順序が保存されます。",
"options": {
"Auto sorting": "自動並べ替え",
"Manual drag&drop sorting": "手動ドラッグアンドドロップによる並べ替え"
}
},
"EasyUse_NodesMap_DisplayNodeID": {
"name": "ノードIDの表示を有効にする"
},
"EasyUse_NodesMap_DisplayGroupOnly": {
"name": "グループのみ表示する"
},
"EasyUse_NodesMap_Enable": {
"name": "グループマップを有効にする",
"tooltip": "ページを更新する必要があります"
}
}
+30
View File
@@ -0,0 +1,30 @@
{
"settingsCategories": {
"Hotkeys": "단축키",
"Nodes": "노드",
"NodesMap": "노드 맵"
},
"nodeCategories": {
"Util": "유틸",
"Seed": "시드",
"Prompt": "프롬프트",
"Loaders": "로더",
"Adapter": "어댑터",
"Inpaint": "인페인트",
"PreSampling": "사전 샘플링",
"Sampler": "샘플러",
"Fix": "픽스",
"Pipe": "파이프",
"XY Inputs": "XY 입력",
"Image": "이미지",
"Segmentation": "분할",
"\uD83D\uDEAB Deprecated": "\uD83D\uDEAB 사용 중단",
"Type": "유형",
"Math": "수학",
"Switch": "스위치",
"Index Switch": "인덱스 스위치",
"While Loop": "while 루프",
"For Loop": "for 루프",
"LoadImage": "이미지 로드"
}
}
+67
View File
@@ -0,0 +1,67 @@
{
"EasyUse_Hotkeys_AddGroup": {
"name": "Shift+g 를 사용하여 선택된 노드를 그룹에 추가합니다",
"tooltip": "v1.2.39부터는 Ctrl+g 를 사용할 수 있습니다"
},
"EasyUse_Hotkeys_cleanVRAMUsed": {
"name": "Shift+r 를 사용하여 모델 및 노드 캐시를 언로드합니다"
},
"EasyUse_Hotkeys_toggleNodesMap": {
"name": "Shift+m 를 사용하여 노드 맵을 전환합니다"
},
"EasyUse_Hotkeys_AlignSelectedNodes": {
"name": "Shift+Up/Down/Left/Right 와 Shift+Ctrl+Alt+Left/Right 를 사용하여 선택된 노드를 정렬합니다",
"tooltip": "Shift+Up/Down/Left/Right 는 선택된 노드를 정렬하며, Shift+Ctrl+Alt+Left/Right 는 노드를 수평/수직으로 분배합니다"
},
"EasyUse_Hotkeys_NormalizeSelectedNodes": {
"name": "Shift+Ctrl+Left/Right 를 사용하여 선택된 노드를 정규화합니다",
"tooltip": "Shift+Ctrl+Left 는 너비를, Shift+Ctrl+Right 는 높이를 정규화합니다"
},
"EasyUse_Hotkeys_NodesTemplate": {
"name": "Alt+1~9 를 사용하여 워크플로우에 노드 템플릿을 붙여넣습니다"
},
"EasyUse_Hotkeys_JumpNearestNodes": {
"name": "Up/Down/Left/Right 를 사용하여 가장 가까운 노드로 이동합니다"
},
"EasyUse_ContextMenu_SubDirectories": {
"name": "컨텍스트 메뉴에서 자동으로 하위 디렉토리를 중첩합니다"
},
"EasyUse_ContextMenu_ModelsThumbnails": {
"name": "모델 미리보기 썸네일을 활성화합니다"
},
"EasyUse_ContextMenu_NodesSort": {
"name": "컨텍스트 메뉴에서 새로운 노드를 A~Z 순으로 정렬합니다"
},
"EasyUse_ContextMenu_QuickOptions": {
"name": "컨텍스트 메뉴에 3개의 빠른 옵션 버튼을 사용합니다",
"options": {
"At the forefront": "앞쪽에",
"At the end": "뒤쪽에",
"Disable": "비활성화"
}
},
"EasyUse_Nodes_Runtime": {
"name": "노드 실행 시간 표시를 활성화합니다"
},
"EasyUse_Nodes_ChainGetSet": {
"name": "부모 노드와 연결된 get/ set 포인트 체이닝을 활성화합니다"
},
"EasyUse_NodesMap_Sorting": {
"name": "노드 그룹 정렬 모드를 관리합니다",
"tooltip": "기본값은 자동 정렬입니다. 수동으로 설정하면 그룹을 드래그 앤 드롭할 수 있으며 순서가 저장됩니다.",
"options": {
"Auto sorting": "자동 정렬",
"Manual drag&drop sorting": "수동 드래그 앤 드롭 정렬"
}
},
"EasyUse_NodesMap_DisplayNodeID": {
"name": "노드 ID 표시를 활성화합니다"
},
"EasyUse_NodesMap_DisplayGroupOnly": {
"name": "그룹만 표시합니다"
},
"EasyUse_NodesMap_Enable": {
"name": "그룹 맵을 활성화합니다",
"tooltip": "업데이트를 위해 페이지를 새로고침해야 합니다"
}
}
+30
View File
@@ -0,0 +1,30 @@
{
"settingsCategories": {
"Hotkeys": "Горячие клавиши",
"Nodes": "Узлы",
"NodesMap": "Карта узлов"
},
"nodeCategories": {
"Util": "Утилиты",
"Seed": "Сид",
"Prompt": "Подсказка",
"Loaders": "Загрузчики",
"Adapter": "Адаптер",
"Inpaint": "Ретушь",
"PreSampling": "Предвыборка",
"Sampler": "Сэмплер",
"Fix": "Исправление",
"Pipe": "Конвейер",
"XY Inputs": "Ввод XY",
"Image": "Изображение",
"Segmentation": "Сегментация",
"\uD83D\uDEAB Deprecated": "\uD83D\uDEAB Устарело",
"Type": "Тип",
"Math": "Математика",
"Switch": "Переключатель",
"Index Switch": "Переключатель индексов",
"While Loop": "Цикл while",
"For Loop": "Цикл for",
"LoadImage": "Загрузка изображения"
}
}
+67
View File
@@ -0,0 +1,67 @@
{
"EasyUse_Hotkeys_AddGroup": {
"name": "Включить Shift+g для добавления выделенных узлов в группу",
"tooltip": "Начиная с версии v1.2.39, можно использовать Ctrl+g"
},
"EasyUse_Hotkeys_cleanVRAMUsed": {
"name": "Включить Shift+r для выгрузки модели и кэша узлов"
},
"EasyUse_Hotkeys_toggleNodesMap": {
"name": "Включить Shift+m для переключения карты узлов"
},
"EasyUse_Hotkeys_AlignSelectedNodes": {
"name": "Включить Shift+Стрелки для выравнивания выделенных узлов и Shift+Ctrl+Alt+Стрелки для распределения узлов по горизонтали/вертикали",
"tooltip": "Shift+Стрелки выравнивают выделенные узлы, Shift+Ctrl+Alt+Стрелки распределяют узлы по горизонтали/вертикали"
},
"EasyUse_Hotkeys_NormalizeSelectedNodes": {
"name": "Включить Shift+Ctrl+Стрелки для нормализации выделенных узлов",
"tooltip": "Включить Shift+Ctrl+Лево для нормализации ширины и Shift+Ctrl+Право для нормализации высоты"
},
"EasyUse_Hotkeys_NodesTemplate": {
"name": "Включить Alt+1~9 для вставки шаблонов узлов в рабочий процесс"
},
"EasyUse_Hotkeys_JumpNearestNodes": {
"name": "Включить Стрелки для перехода к ближайшему узлу"
},
"EasyUse_ContextMenu_SubDirectories": {
"name": "Включить автоматическое вложение подкаталогов в контекстном меню"
},
"EasyUse_ContextMenu_ModelsThumbnails": {
"name": "Включить превью миниатюр моделей"
},
"EasyUse_ContextMenu_NodesSort": {
"name": "Включить A~Z сортировку новых узлов в контекстном меню"
},
"EasyUse_ContextMenu_QuickOptions": {
"name": "Использовать три быстрых кнопки в контекстном меню",
"options": {
"At the forefront": "В начале",
"At the end": "В конце",
"Disable": "Отключено"
}
},
"EasyUse_Nodes_Runtime": {
"name": "Включить отображение времени выполнения узлов"
},
"EasyUse_Nodes_ChainGetSet": {
"name": "Включить связывание точек получения и установки с родительским узлом"
},
"EasyUse_NodesMap_Sorting": {
"name": "Управление режимом сортировки групп узлов",
"tooltip": "По умолчанию автоматическая сортировка. При ручном режиме группы можно перемещать методом перетаскивания, и порядок будет сохранён.",
"options": {
"Auto sorting": "Автоматическая сортировка",
"Manual drag&drop sorting": "Ручная сортировка перетаскиванием"
}
},
"EasyUse_NodesMap_DisplayNodeID": {
"name": "Включить отображение ID узлов"
},
"EasyUse_NodesMap_DisplayGroupOnly": {
"name": "Показывать только группы"
},
"EasyUse_NodesMap_Enable": {
"name": "Включить карту групп",
"tooltip": "Необходимо обновить страницу для успешного обновления"
}
}
+5
View File
@@ -1,4 +1,9 @@
{
"settingsCategories": {
"Hotkeys": "快捷键",
"Nodes": "节点相关",
"NodesMap": "管理节点组"
},
"nodeCategories": {
"Util": "工具",
"Seed": "随机种",
+75 -43
View File
@@ -360,7 +360,7 @@
"name": "VAE名称"
},
"clip_skip": {
"name": "Clip停止层"
"name": "CLIP停止层"
},
"lora_name": {
"name": "LoRA名称"
@@ -412,11 +412,20 @@
"1": {
"name": "模型"
},
"2": {
"name": "VAE"
},
"3": {
"name": "CLIP"
},
"4": {
"name": "正面条件"
},
"5": {
"name": "负面条件"
},
"6": {
"name": "LATENT"
}
}
},
@@ -436,7 +445,7 @@
"name": "VAE"
},
"clip_skip": {
"name": "Clip停止层"
"name": "CLIP停止层"
},
"lora_name": {
"name": "LoRA模型"
@@ -475,6 +484,9 @@
},
"1": {
"name": "模型"
},
"2": {
"name": "VAE"
}
}
},
@@ -494,7 +506,7 @@
"name": "VAE"
},
"clip_skip": {
"name": "Clip停止层"
"name": "CLIP停止层"
},
"lora_name": {
"name": "LoRA模型"
@@ -1574,7 +1586,7 @@
}
},
"easy latentNoisy": {
"display_name": "噪波Latent(Sigma乘积)",
"display_name": "噪波LATENT(Sigma乘积)",
"inputs": {
"pipe": {
"name": "节点束"
@@ -1583,7 +1595,7 @@
"name": "模型(可选)"
},
"optional_latent": {
"name": "Latent(可选)"
"name": "LATENT(可选)"
},
"sample_name": {
"name": "采样器"
@@ -1618,7 +1630,7 @@
"name": "节点束"
},
"1": {
"name": "Latent"
"name": "LATENT"
},
"2": {
"name": "伽马值"
@@ -1635,7 +1647,7 @@
"name": "文字拼接"
},
"source_latent": {
"name": "Latent(源)"
"name": "LATENT(源)"
},
"source_mask": {
"name": "遮罩(源)"
@@ -1655,7 +1667,7 @@
"name": "节点束"
},
"1": {
"name": "Latent"
"name": "LATENT"
},
"2": {
"name": "条件"
@@ -1672,7 +1684,7 @@
"name": "图像转Latent"
},
"latent": {
"name": "Latent"
"name": "LATENT"
},
"noise": {
"name": "噪波"
@@ -1695,7 +1707,7 @@
},
"outputs": {
"0": {
"name": "Latent"
"name": "LATENT"
}
}
},
@@ -1794,7 +1806,7 @@
"name": "图像(可选)"
},
"latent": {
"name": "Latent(可选)"
"name": "LATENT(可选)"
}
},
"outputs": {
@@ -1849,7 +1861,7 @@
"name": "图像(可选)"
},
"latent": {
"name": "Latent(可选)"
"name": "LATENT(可选)"
}
},
"outputs": {
@@ -1865,7 +1877,7 @@
"name": "节点束"
},
"optional_latent": {
"name": "Latent(可选)"
"name": "LATENT(可选)"
},
"optional_noise_seed": {
"name": "随机种(可选)"
@@ -1914,7 +1926,7 @@
"name": "图像转Latent"
},
"latent": {
"name": "Latent"
"name": "LATENT"
},
"optional_sampler": {
"name": "采样器(可选)"
@@ -1993,7 +2005,7 @@
"name": "图像(可选)"
},
"latent": {
"name": "Latent(可选)"
"name": "LATENT(可选)"
}
},
"outputs": {
@@ -2232,7 +2244,7 @@
"name": "图像(可选)"
},
"latent": {
"name": "Latent(可选)"
"name": "LATENT(可选)"
}
},
"outputs": {
@@ -2260,7 +2272,7 @@
"name": "图像(可选)"
},
"latent": {
"name": "Latent(可选)"
"name": "LATENT(可选)"
},
"steps": {
"name": "步数"
@@ -2313,13 +2325,16 @@
"name": "负面条件"
},
"5": {
"name": "图像转Latent"
"name": "LATENT"
},
"6": {
"name": "Latent"
"name": "VAE"
},
"7": {
"name": "随机种"
"name": "CLIP"
},
"8": {
"name": "seed"
}
}
},
@@ -2594,7 +2609,7 @@
"name": "负面条件(可选)"
},
"optional_latent": {
"name": "Latent(可选)"
"name": "LATENT(可选)"
},
"steps": {
"name": "步数"
@@ -2620,7 +2635,7 @@
"name": "节点束"
},
"1": {
"name": "Latent"
"name": "LATENT"
}
}
},
@@ -2643,7 +2658,7 @@
"name": "图像转Latent"
},
"latent": {
"name": "Latent"
"name": "LATENT"
},
"image": {
"name": "图像"
@@ -2679,7 +2694,7 @@
"name": "图像转Latent"
},
"5": {
"name": "Latent"
"name": "LATENT"
},
"6": {
"name": "图像"
@@ -2739,17 +2754,17 @@
"neg": {
"name": "负面条件"
},
"vae": {
"VAE": {
"name": "VAE"
},
"clip": {
"CLIP": {
"name": "CLIP"
},
"image_to_latent": {
"name": "图像转Latent"
},
"latent": {
"name": "Latent"
"name": "LATENT"
},
"image": {
"name": "图像"
@@ -2811,7 +2826,7 @@
"name": "图像转Latent"
},
"9": {
"name": "Latent"
"name": "LATENT"
},
"10": {
"name": "图像"
@@ -3097,34 +3112,34 @@
"name": "模型名称10"
},
"clip_skip_1": {
"name": "Clip停止层1"
"name": "CLIP停止层1"
},
"clip_skip_2": {
"name": "Clip停止层2"
"name": "CLIP停止层2"
},
"clip_skip_3": {
"name": "Clip停止层3"
"name": "CLIP停止层3"
},
"clip_skip_4": {
"name": "Clip停止层4"
"name": "CLIP停止层4"
},
"clip_skip_5": {
"name": "Clip停止层5"
"name": "CLIP停止层5"
},
"clip_skip_6": {
"name": "Clip停止层6"
"name": "CLIP停止层6"
},
"clip_skip_7": {
"name": "Clip停止层7"
"name": "CLIP停止层7"
},
"clip_skip_8": {
"name": "Clip停止层8"
"name": "CLIP停止层8"
},
"clip_skip_9": {
"name": "Clip停止层9"
"name": "CLIP停止层9"
},
"clip_skip_10": {
"name": "Clip停止层10"
"name": "CLIP停止层10"
},
"vae_name_1": {
"name": "VAE名称1"
@@ -3847,7 +3862,7 @@
"name": "图像"
},
"2": {
"name": "Latent"
"name": "LATENT"
}
}
},
@@ -4017,10 +4032,10 @@
"model": {
"name": "模型"
},
"clip": {
"CLIP": {
"name": "CLIP"
},
"vae": {
"VAE": {
"name": "VAE"
},
"positive": {
@@ -4168,6 +4183,12 @@
},
"save_prefix": {
"name": "保存前缀"
},
"add_background": {
"name": "添加背景"
},
"refine_foreground": {
"name": "优化前景"
}
},
"outputs": {
@@ -4922,7 +4943,7 @@
"name": "模型"
},
"latent": {
"name": "Latent"
"name": "LATENT"
},
"mode": {
"name": "模式"
@@ -5511,6 +5532,12 @@
},
"image_output": {
"name": "图像输出"
},
"add_background": {
"name": "添加背景"
},
"refine_foreground": {
"name": "优化前景"
}
},
"outputs": {
@@ -5729,7 +5756,7 @@
"name": "模型"
},
"latent": {
"name": "Latent"
"name": "LATENT"
},
"head": {
"name": "head"
@@ -6511,6 +6538,11 @@
"anything": {
"name": "输入任何"
}
},
"outputs": {
"0": {
"name": "输出"
}
}
},
"easy showTensorShape": {
+67
View File
@@ -0,0 +1,67 @@
{
"EasyUse_Hotkeys_AddGroup": {
"name": "启用 Shift+g 键将选中的节点添加一个组",
"tooltip": "从v1.2.39开始,可以使用Ctrl+g代替"
},
"EasyUse_Hotkeys_cleanVRAMUsed": {
"name": "启用 Shift+r 键卸载模型和节点缓存"
},
"EasyUse_Hotkeys_toggleNodesMap": {
"name": "启用 Shift+m 键显隐管理节点组"
},
"EasyUse_Hotkeys_AlignSelectedNodes": {
"name": "启用 Shift+上/下/左/右 和 Shift+Ctrl+Alt+左/右 键对齐选中的节点",
"tooltip": "Shift+上/下/左/右 可以对齐选中的节点, Shift+Ctrl+Alt+左/右 可以水平/垂直分布节点"
},
"EasyUse_Hotkeys_NormalizeSelectedNodes": {
"name": "启用 Shift+Ctrl+左/右 键规范化选中的节点",
"tooltip": "启用 Shift+Ctrl+左 键规范化宽度和 Shift+Ctrl+右 键规范化高度"
},
"EasyUse_Hotkeys_NodesTemplate": {
"name": "启用 Alt+1~9 从节点模板粘贴到工作流中"
},
"EasyUse_Hotkeys_JumpNearestNodes": {
"name": "启用 上/下/左/右 键跳转到最近的前后节点"
},
"EasyUse_ContextMenu_SubDirectories": {
"name": "启用上下文菜单自动嵌套子目录"
},
"EasyUse_ContextMenu_ModelsThumbnails": {
"name": "启动模型预览图显示"
},
"EasyUse_ContextMenu_NodesSort": {
"name": "启用右键菜单中新建节点A~Z排序"
},
"EasyUse_ContextMenu_QuickOptions": {
"name": "在右键菜单中使用三个快捷按钮",
"options": {
"At the forefront": "在最前面",
"At the end": "在最后面",
"Disable": "禁用"
}
},
"EasyUse_Nodes_Runtime": {
"name": "启动节点运行时间显示"
},
"EasyUse_Nodes_ChainGetSet": {
"name": "启用将获取点和设置点与父节点链在一起"
},
"EasyUse_NodesMap_Sorting": {
"name": "管理节点组排序模式",
"tooltip": "默认自动排序,如果设置为手动,组可以拖放并保存排序结果。",
"options": {
"Auto sorting": "自动排序",
"Manual drag&drop sorting": "手动拖拽排序"
}
},
"EasyUse_NodesMap_DisplayNodeID": {
"name": "启用节点ID显示"
},
"EasyUse_NodesMap_DisplayGroupOnly": {
"name": "仅显示组"
},
"EasyUse_NodesMap_Enable": {
"name": "启用管理节点组",
"tooltip": "您需要刷新页面以成功更新"
}
}
+4 -4
View File
@@ -195,7 +195,7 @@ REMBG_MODELS = {
"model_url": "briaai/RMBG-2.0"
},
"BEN2": {
"model_url": "https://huggingface.co/PramaLLC/BEN/resolve/main/BEN_Base.pth"
"model_url": "https://huggingface.co/PramaLLC/BEN2/resolve/main/BEN2_Base.pth"
}
}
@@ -336,7 +336,7 @@ IPADAPTER_CLIPVISION_MODELS = {
"model_url": "https://huggingface.co/openai/clip-vit-large-patch14-336/resolve/main/pytorch_model.bin"
},
"clip-vit-h-14-laion2B-s32B-b79K":{
"model_url": "https://huggingface.co/laion/CLIP-ViT-H-14-laion2B-s32B-b79K/resolve/main/open_clip_pytorch_model.safetensors"
"model_url": "https://huggingface.co/laion/CLIP-ViT-H-14-laion2B-s32B-b79K/resolve/main/open_clip_model.safetensors"
},
"sigclip_vision_patch14_384":{
"model_url": "https://huggingface.co/Comfy-Org/sigclip_vision_384/resolve/main/sigclip_vision_patch14_384.safetensors"
@@ -350,7 +350,7 @@ DYNAMICRAFTER_MODELS = {
"model_url": "https://huggingface.co/ExponentialML/DynamiCrafterUNet/resolve/main/dynamicrafter_unet_512.safetensors",
"vae_url": "https://huggingface.co/stabilityai/sd-vae-ft-mse-original/resolve/main/vae-ft-mse-840000-ema-pruned.safetensors",
"clip_url": "https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/text_encoder/model.safetensors",
"clip_vision_url": "https://huggingface.co/laion/CLIP-ViT-H-14-laion2B-s32B-b79K/resolve/main/open_clip_pytorch_model.safetensors",
"clip_vision_url": "https://huggingface.co/laion/CLIP-ViT-H-14-laion2B-s32B-b79K/resolve/main/open_clip_model.safetensors",
},
"dynamicrafter_unet_512_interp (2.98GB)": {
"model_url": "https://huggingface.co/ExponentialML/DynamiCrafterUNet/resolve/main/dynamicrafter_unet_512_interp.safetensors"
@@ -395,4 +395,4 @@ PROMPT_TEMPLATE = {
"nsfw": ["nude", "breast", "small breast", "middle breast", "large breast", "nipples", "clothes lift", "pussy juice trail", "pussy juice puddle", "small testicles", "medium testicles", "large testicles", "disembodied penis", "cum on body", "cum inside", "cum outside", "fingering", "handjob", "fellatio", "licking penis", "paizuri", "doggystyle", "cowgirl", "reversed cowgirl", "piledriver", "suspended congress", "full nelson",],
}
NEW_SCHEDULERS = ['align_your_steps', 'gits']
NEW_SCHEDULERS = ['align_your_steps', 'gits']
+7 -5
View File
@@ -82,6 +82,7 @@ def compare_revision(num):
if not comfy_ui_revision:
comfy_ui_revision = get_comfyui_revision()
return True if comfy_ui_revision == 'Unknown' or int(comfy_ui_revision) >= num else False
def find_tags(string: str, sep="/") -> list[str]:
"""
find tags from string use the sep for split
@@ -217,14 +218,15 @@ def get_local_filepath(url, dirname, local_file_name=None):
except Exception as e:
use_mirror = True
url = url.replace('huggingface.co', 'hf-mirror.com')
print(f'无法从huggingface下载,正在尝试从 {url} 下载...')
PromptServer.instance.send_sync("easyuse-toast", {'content': f'无法连接huggingface,正在尝试从 {url} 下载...', 'duration': 10000})
print(f'Unable to download from huggingface, trying mirror: {url}')
PromptServer.instance.send_sync("easyuse-toast", {'content': f'Unable to connect to huggingface, trying mirror: {url}', 'duration': 10000})
try:
download_url_to_file(url, destination)
except Exception as err:
error_msg = str(err.args[0]) if err.args else str(err)
PromptServer.instance.send_sync("easyuse-toast",
{'content': f'无法从 {url} 下载模型', 'type':'error'})
raise Exception(f'无法从 {url} 下载,错误信息:{str(err.args[0])}')
{'content': f'Unable to download model from {url}', 'type':'error'})
raise Exception(f'Download failed. Original URL and mirror both failed.\nError: {error_msg}')
return destination
def to_lora_patch_dict(state_dict: dict) -> dict:
@@ -277,4 +279,4 @@ def getMetadata(filepath):
def cleanGPUUsedForce():
gc.collect()
mm.unload_all_models()
mm.soft_empty_cache()
mm.soft_empty_cache()
+2 -2
View File
@@ -34,11 +34,11 @@ def read_wildcard_dict(wildcard_path):
key = os.path.splitext(rel_path)[0].replace('\\', '/').lower()
try:
with open(file_path, 'r', encoding="ISO-8859-1") as f:
with open(file_path, 'r', encoding="UTF-8", errors="ignore") as f:
lines = f.read().splitlines()
easy_wildcard_dict[key] = lines
except UnicodeDecodeError:
with open(file_path, 'r', encoding="UTF-8", errors="ignore") as f:
with open(file_path, 'r', encoding="ISO-8859-1") as f:
lines = f.read().splitlines()
easy_wildcard_dict[key] = lines
elif file.endswith('.yaml'):
+445 -82
View File
@@ -1,13 +1,36 @@
import math
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.utils.checkpoint as checkpoint
from einops import rearrange
from PIL import Image, ImageFilter, ImageOps
from timm.layers import DropPath, to_2tuple, trunc_normal_
import torch.utils.checkpoint as checkpoint
import numpy as np
from timm.models.layers import DropPath, to_2tuple, trunc_normal_
from PIL import Image, ImageOps
from torchvision import transforms
import numpy as np
import random
import cv2
import os
import subprocess
import time
import tempfile
def set_random_seed(seed):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
set_random_seed(9)
torch.set_float32_matmul_precision('highest')
class Mlp(nn.Module):
""" Multilayer perceptron."""
@@ -247,6 +270,7 @@ class PatchMerging(nn.Module):
dim (int): Number of input channels.
norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm
"""
def __init__(self, dim, norm_layer=nn.LayerNorm):
super().__init__()
self.dim = dim
@@ -495,7 +519,8 @@ class SwinTransformer(nn.Module):
patch_size = to_2tuple(patch_size)
patches_resolution = [pretrain_img_size[0] // patch_size[0], pretrain_img_size[1] // patch_size[1]]
self.absolute_pos_embed = nn.Parameter(torch.zeros(1, embed_dim, patches_resolution[0], patches_resolution[1]))
self.absolute_pos_embed = nn.Parameter(
torch.zeros(1, embed_dim, patches_resolution[0], patches_resolution[1]))
trunc_normal_(self.absolute_pos_embed, std=.02)
self.pos_drop = nn.Dropout(p=drop_rate)
@@ -550,7 +575,6 @@ class SwinTransformer(nn.Module):
for param in m.parameters():
param.requires_grad = False
def forward(self, x):
x = self.patch_embed(x)
@@ -559,18 +583,16 @@ class SwinTransformer(nn.Module):
if self.ape:
# interpolate the position embedding to the corresponding size
absolute_pos_embed = F.interpolate(self.absolute_pos_embed, size=(Wh, Ww), mode='bicubic')
x = (x + absolute_pos_embed) # B Wh*Ww C
x = (x + absolute_pos_embed) # B Wh*Ww C
outs = [x.contiguous()]
x = x.flatten(2).transpose(1, 2)
x = self.pos_drop(x)
for i in range(self.num_layers):
layer = self.layers[i]
x_out, H, W, x, Wh, Ww = layer(x, Wh, Ww)
if i in self.out_indices:
norm_layer = getattr(self, f'norm{i}')
x_out = norm_layer(x_out)
@@ -578,16 +600,9 @@ class SwinTransformer(nn.Module):
out = x_out.view(-1, H, W, self.num_features[i]).permute(0, 3, 1, 2).contiguous()
outs.append(out)
return tuple(outs)
def get_activation_fn(activation):
"""Return an activation function given a string"""
if activation == "gelu":
@@ -622,6 +637,43 @@ def patches2image(x):
"""(hg wg b) c h w -> b c (hg h) (wg w)"""
x = rearrange(x, '(hg wg b) c h w -> b c (hg h) (wg w)', hg=2, wg=2)
return x
class PositionEmbeddingSine:
def __init__(self, num_pos_feats=64, temperature=10000, normalize=False, scale=None):
super().__init__()
self.num_pos_feats = num_pos_feats
self.temperature = temperature
self.normalize = normalize
if scale is not None and normalize is False:
raise ValueError("normalize should be True if scale is passed")
if scale is None:
scale = 2 * math.pi
self.scale = scale
self.dim_t = torch.arange(0, self.num_pos_feats, dtype=torch.float32)
def __call__(self, b, h, w):
device = self.dim_t.device
mask = torch.zeros([b, h, w], dtype=torch.bool, device=device)
assert mask is not None
not_mask = ~mask
y_embed = not_mask.cumsum(dim=1, dtype=torch.float32)
x_embed = not_mask.cumsum(dim=2, dtype=torch.float32)
if self.normalize:
eps = 1e-6
y_embed = (y_embed - 0.5) / (y_embed[:, -1:, :] + eps) * self.scale
x_embed = (x_embed - 0.5) / (x_embed[:, :, -1:] + eps) * self.scale
dim_t = self.temperature ** (2 * (self.dim_t.to(device) // 2) / self.num_pos_feats)
pos_x = x_embed[:, :, :, None] / dim_t
pos_y = y_embed[:, :, :, None] / dim_t
pos_x = torch.stack((pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4).flatten(3)
pos_y = torch.stack((pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4).flatten(3)
return torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2)
class PositionEmbeddingSine:
def __init__(self, num_pos_feats=64, temperature=10000, normalize=False, scale=None):
super().__init__()
@@ -688,13 +740,15 @@ class MCLM(nn.Module):
l: 4,c,h,w
g: 1,c,h,w
"""
self.p_poses = []
self.g_pos = None
b, c, h, w = l.size()
# 4,c,h,w -> 1,c,2h,2w
concated_locs = rearrange(l, '(hg wg b) c h w -> b c (hg h) (wg w)', hg=2, wg=2)
pools = []
for pool_ratio in self.pool_ratios:
# b,c,h,w
# b,c,h,w
tgt_hw = (round(h / pool_ratio), round(w / pool_ratio))
pool = F.adaptive_avg_pool2d(concated_locs, tgt_hw)
pools.append(rearrange(pool, 'b c h w -> (h w) b c'))
@@ -712,11 +766,9 @@ class MCLM(nn.Module):
self.p_poses = self.p_poses.to(device)
self.g_pos = self.g_pos.to(device)
# attention between glb (q) & multisensory concated-locs (k,v)
g_hw_b_c = rearrange(g, 'b c h w -> (h w) b c')
g_hw_b_c = g_hw_b_c + self.dropout1(self.attention[0](g_hw_b_c + self.g_pos, pools + self.p_poses, pools)[0])
g_hw_b_c = self.norm1(g_hw_b_c)
g_hw_b_c = g_hw_b_c + self.dropout2(self.linear2(self.dropout(self.activation(self.linear1(g_hw_b_c)).clone())))
@@ -740,13 +792,6 @@ class MCLM(nn.Module):
return rearrange(l, "(h w) b c -> b c h w", h=h, w=w) ## (5,c,h*w)
class MCRM(nn.Module):
def __init__(self, d_model, num_heads, pool_ratios=[4, 8, 16], h=None):
super(MCRM, self).__init__()
@@ -852,91 +897,390 @@ class BEN_Base(nn.Module):
if isinstance(m, nn.GELU) or isinstance(m, nn.Dropout):
m.inplace = True
@torch.inference_mode()
@torch.autocast(device_type="cuda", dtype=torch.float16)
def forward(self, x):
device = x.device
shallow = self.shallow(x)
glb = rescale_to(x, scale_factor=0.5, interpolation='bilinear')
loc = image2patches(x)
input = torch.cat((loc, glb), dim=0)
feature = self.backbone(input)
e5 = self.output5(feature[4]) # (5,128,16,16)
e4 = self.output4(feature[3]) # (5,128,32,32)
e3 = self.output3(feature[2]) # (5,128,64,64)
e2 = self.output2(feature[1]) # (5,128,128,128)
e1 = self.output1(feature[0]) # (5,128,128,128)
loc_e5, glb_e5 = e5.split([4, 1], dim=0)
e5 = self.multifieldcrossatt(loc_e5, glb_e5) # (4,128,16,16)
real_batch = x.size(0)
e4, tokenattmap4 = self.dec_blk4(e4 + resize_as(e5, e4))
e4 = self.conv4(e4)
e3, tokenattmap3 = self.dec_blk3(e3 + resize_as(e4, e3))
e3 = self.conv3(e3)
e2, tokenattmap2 = self.dec_blk2(e2 + resize_as(e3, e2))
e2 = self.conv2(e2)
e1, tokenattmap1 = self.dec_blk1(e1 + resize_as(e2, e1))
e1 = self.conv1(e1)
loc_e1, glb_e1 = e1.split([4, 1], dim=0)
output1_cat = patches2image(loc_e1) # (1,128,256,256)
output1_cat = output1_cat + resize_as(glb_e1, output1_cat)
final_output = self.insmask_head(output1_cat) # (1,128,256,256)
final_output = final_output + resize_as(shallow, final_output)
final_output = self.upsample1(rescale_to(final_output))
final_output = rescale_to(final_output + resize_as(shallow, final_output))
final_output = self.upsample2(final_output)
final_output = self.output(final_output)
shallow_batch = self.shallow(x)
glb_batch = rescale_to(x, scale_factor=0.5, interpolation='bilinear')
return final_output.sigmoid()
final_input = None
for i in range(real_batch):
start = i * 4
end = (i + 1) * 4
loc_batch = image2patches(x[i, :, :, :].unsqueeze(dim=0))
input_ = torch.cat((loc_batch, glb_batch[i, :, :, :].unsqueeze(dim=0)), dim=0)
@torch.no_grad()
def inference(self,image):
image, h, w,original_image = rgb_loader_refiner(image)
if final_input == None:
final_input = input_
else:
final_input = torch.cat((final_input, input_), dim=0)
img_tensor = img_transform(image).unsqueeze(0).to(next(self.parameters()).device)
features = self.backbone(final_input)
outputs = []
res = self.forward(img_tensor)
for i in range(real_batch):
start = i * 5
end = (i + 1) * 5
pred_array = postprocess_image(res, im_size=[w, h])
f4 = features[4][start:end, :, :, :] # shape: [5, C, H, W]
f3 = features[3][start:end, :, :, :]
f2 = features[2][start:end, :, :, :]
f1 = features[1][start:end, :, :, :]
f0 = features[0][start:end, :, :, :]
e5 = self.output5(f4)
e4 = self.output4(f3)
e3 = self.output3(f2)
e2 = self.output2(f1)
e1 = self.output1(f0)
loc_e5, glb_e5 = e5.split([4, 1], dim=0)
e5 = self.multifieldcrossatt(loc_e5, glb_e5) # (4,128,16,16)
mask_image = Image.fromarray(pred_array, mode='L')
e4, tokenattmap4 = self.dec_blk4(e4 + resize_as(e5, e4))
e4 = self.conv4(e4)
e3, tokenattmap3 = self.dec_blk3(e3 + resize_as(e4, e3))
e3 = self.conv3(e3)
e2, tokenattmap2 = self.dec_blk2(e2 + resize_as(e3, e2))
e2 = self.conv2(e2)
e1, tokenattmap1 = self.dec_blk1(e1 + resize_as(e2, e1))
e1 = self.conv1(e1)
blurred_mask = mask_image.filter(ImageFilter.GaussianBlur(radius=1))
loc_e1, glb_e1 = e1.split([4, 1], dim=0)
original_image_rgba = original_image.convert("RGBA")
output1_cat = patches2image(loc_e1) # (1,128,256,256)
foreground = original_image_rgba.copy()
# add glb feat in
output1_cat = output1_cat + resize_as(glb_e1, output1_cat)
# merge
final_output = self.insmask_head(output1_cat) # (1,128,256,256)
# shallow feature merge
shallow = shallow_batch[i, :, :, :].unsqueeze(dim=0)
final_output = final_output + resize_as(shallow, final_output)
final_output = self.upsample1(rescale_to(final_output))
final_output = rescale_to(final_output + resize_as(shallow, final_output))
final_output = self.upsample2(final_output)
final_output = self.output(final_output)
mask = final_output.sigmoid()
outputs.append(mask)
foreground.putalpha(blurred_mask)
return torch.cat(outputs, dim=0)
return blurred_mask, foreground
def loadcheckpoints(self,model_path):
def loadcheckpoints(self, model_path):
model_dict = torch.load(model_path, map_location="cpu", weights_only=True)
self.load_state_dict(model_dict['model_state_dict'], strict=True)
del model_path
def inference(self, image, refine_foreground=False):
set_random_seed(9)
# image = ImageOps.exif_transpose(image)
if isinstance(image, Image.Image):
image, h, w, original_image = rgb_loader_refiner(image)
if torch.cuda.is_available():
img_tensor = img_transform(image).unsqueeze(0).to(next(self.parameters()).device)
else:
img_tensor = img_transform32(image).unsqueeze(0).to(next(self.parameters()).device)
with torch.no_grad():
res = self.forward(img_tensor)
# Show Results
if refine_foreground == True:
pred_pil = transforms.ToPILImage()(res.squeeze())
image_masked = refine_foreground_process(original_image, pred_pil)
mask = pred_pil.resize(original_image.size)
image_masked.putalpha(mask)
return mask, image_masked
else:
alpha = postprocess_image(res, im_size=[w, h])
pred_pil = transforms.ToPILImage()(alpha)
mask = pred_pil.resize(original_image.size)
original_image.putalpha(mask)
# mask = Image.fromarray(alpha)
return mask, original_image
def segment_video(self, video_path, output_path="./", fps=0, refine_foreground=False, batch=1,
print_frames_processed=True, webm=False, rgb_value=(0, 255, 0)):
"""
Segments the given video to extract the foreground (with alpha) from each frame
and saves the result as either a WebM video (with alpha channel) or MP4 (with a
color background).
Args:
video_path (str):
Path to the input video file.
output_path (str, optional):
Directory (or full path) where the output video and/or files will be saved.
Defaults to "./".
fps (int, optional):
The frames per second (FPS) to use for the output video. If 0 (default), the
original FPS of the input video is used. Otherwise, overrides it.
refine_foreground (bool, optional):
Whether to run an additional “refine foreground” process on each frame.
Defaults to False.
batch (int, optional):
Number of frames to process at once (inference batch size). Large batch sizes
may require more GPU memory. Defaults to 1.
print_frames_processed (bool, optional):
If True (default), prints progress (how many frames have been processed) to
the console.
webm (bool, optional):
If True (default), exports a WebM video with alpha channel (VP9 / yuva420p).
If False, exports an MP4 video composited over a solid color background.
rgb_value (tuple, optional):
The RGB background color (e.g., green screen) used to composite frames when
saving to MP4. Defaults to (0, 255, 0).
Returns:
None. Writes the output video(s) to disk in the specified format.
"""
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise IOError(f"Cannot open video: {video_path}")
original_fps = cap.get(cv2.CAP_PROP_FPS)
original_fps = 30 if original_fps == 0 else original_fps
fps = original_fps if fps == 0 else fps
ret, first_frame = cap.read()
if not ret:
raise ValueError("No frames found in the video.")
height, width = first_frame.shape[:2]
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
foregrounds = []
frame_idx = 0
processed_count = 0
batch_frames = []
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
while True:
ret, frame = cap.read()
if not ret:
if batch_frames:
batch_results = self.inference(batch_frames, refine_foreground)
if isinstance(batch_results, Image.Image):
foregrounds.append(batch_results)
else:
foregrounds.extend(batch_results)
if print_frames_processed:
print(f"Processed frames {frame_idx - len(batch_frames) + 1} to {frame_idx} of {total_frames}")
break
# Process every frame instead of using intervals
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
pil_frame = Image.fromarray(frame_rgb)
batch_frames.append(pil_frame)
if len(batch_frames) == batch:
batch_results = self.inference(batch_frames, refine_foreground)
if isinstance(batch_results, Image.Image):
foregrounds.append(batch_results)
else:
foregrounds.extend(batch_results)
if print_frames_processed:
print(f"Processed frames {frame_idx - batch + 1} to {frame_idx} of {total_frames}")
batch_frames = []
processed_count += batch
frame_idx += 1
if webm:
alpha_webm_path = os.path.join(output_path, "foreground.webm")
pil_images_to_webm_alpha(foregrounds, alpha_webm_path, fps=original_fps)
else:
cap.release()
fg_output = os.path.join(output_path, 'foreground.mp4')
pil_images_to_mp4(foregrounds, fg_output, fps=original_fps, rgb_value=rgb_value)
cv2.destroyAllWindows()
try:
fg_audio_output = os.path.join(output_path, 'foreground_output_with_audio.mp4')
add_audio_to_video(fg_output, video_path, fg_audio_output)
except Exception as e:
print("No audio found in the original video")
print(e)
def rgb_loader_refiner(original_image):
h, w = original_image.size
def rgb_loader_refiner( original_image):
h, w = original_image.size
# # Apply EXIF orientation
image = ImageOps.exif_transpose(original_image)
# Convert to RGB if necessary
if image.mode != 'RGB':
image = image.convert('RGB')
image = original_image
# Convert to RGB if necessary
if image.mode != 'RGB':
image = image.convert('RGB')
# Resize the image
image = image.resize((1024, 1024), resample=Image.LANCZOS)
# Resize the image
image = image.resize((1024, 1024), resample=Image.LANCZOS)
return image.convert('RGB'), h, w, original_image
return image.convert('RGB'), h, w,original_image
# Define the image transformation
img_transform = transforms.Compose([
transforms.ToTensor(),
transforms.ConvertImageDtype(torch.float16),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
img_transform32 = transforms.Compose([
transforms.ToTensor(),
transforms.ConvertImageDtype(torch.float32),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
def pil_images_to_mp4(images, output_path, fps=24, rgb_value=(0, 255, 0)):
"""
Converts an array of PIL images to an MP4 video.
Args:
images: List of PIL images
output_path: Path to save the MP4 file
fps: Frames per second (default: 24)
rgb_value: Background RGB color tuple (default: green (0, 255, 0))
"""
if not images:
raise ValueError("No images provided to convert to MP4.")
width, height = images[0].size
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
video_writer = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
for image in images:
# If image has alpha channel, composite onto the specified background color
if image.mode == 'RGBA':
# Create background image with specified RGB color
background = Image.new('RGB', image.size, rgb_value)
background = background.convert('RGBA')
# Composite the image onto the background
image = Image.alpha_composite(background, image)
image = image.convert('RGB')
else:
# Ensure RGB format for non-alpha images
image = image.convert('RGB')
# Convert to OpenCV format and write
open_cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
video_writer.write(open_cv_image)
video_writer.release()
def pil_images_to_webm_alpha(images, output_path, fps=30):
"""
Converts a list of PIL RGBA images to a VP9 .webm video with alpha channel.
NOTE: Not all players will display alpha in WebM.
Browsers like Chrome/Firefox typically do support VP9 alpha.
"""
if not images:
raise ValueError("No images provided for WebM with alpha.")
# Ensure output directory exists
os.makedirs(os.path.dirname(output_path), exist_ok=True)
with tempfile.TemporaryDirectory() as tmpdir:
# Save frames as PNG (with alpha)
for idx, img in enumerate(images):
if img.mode != "RGBA":
img = img.convert("RGBA")
out_path = os.path.join(tmpdir, f"{idx:06d}.png")
img.save(out_path, "PNG")
# Construct ffmpeg command
# -c:v libvpx-vp9 => VP9 encoder
# -pix_fmt yuva420p => alpha-enabled pixel format
# -auto-alt-ref 0 => helps preserve alpha frames (libvpx quirk)
ffmpeg_cmd = [
"ffmpeg", "-y",
"-framerate", str(fps),
"-i", os.path.join(tmpdir, "%06d.png"),
"-c:v", "libvpx-vp9",
"-pix_fmt", "yuva420p",
"-auto-alt-ref", "0",
output_path
]
subprocess.run(ffmpeg_cmd, check=True)
print(f"WebM with alpha saved to {output_path}")
def add_audio_to_video(video_without_audio_path, original_video_path, output_path):
"""
Check if the original video has an audio stream. If yes, add it. If not, skip.
"""
# 1) Probe original video for audio streams
probe_command = [
'ffprobe', '-v', 'error',
'-select_streams', 'a:0',
'-show_entries', 'stream=index',
'-of', 'csv=p=0',
original_video_path
]
result = subprocess.run(probe_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# result.stdout is empty if no audio stream found
if not result.stdout.strip():
print("No audio track found in original video, skipping audio addition.")
return
print("Audio track detected; proceeding to mux audio.")
# 2) If audio found, run ffmpeg to add it
command = [
'ffmpeg', '-y',
'-i', video_without_audio_path,
'-i', original_video_path,
'-c', 'copy',
'-map', '0:v:0',
'-map', '1:a:0', # we know there's an audio track now
output_path
]
subprocess.run(command, check=True)
print(f"Audio added successfully => {output_path}")
### Thanks to the source: https://huggingface.co/ZhengPeng7/BiRefNet/blob/main/handler.py
def refine_foreground_process(image, mask, r=90):
if mask.size != image.size:
mask = mask.resize(image.size)
image = np.array(image) / 255.0
mask = np.array(mask) / 255.0
estimated_foreground = FB_blur_fusion_foreground_estimator_2(image, mask, r=r)
image_masked = Image.fromarray((estimated_foreground * 255.0).astype(np.uint8))
return image_masked
def FB_blur_fusion_foreground_estimator_2(image, alpha, r=90):
# Thanks to the source: https://github.com/Photoroom/fast-foreground-estimation
alpha = alpha[:, :, None]
F, blur_B = FB_blur_fusion_foreground_estimator(image, image, image, alpha, r)
return FB_blur_fusion_foreground_estimator(image, F, blur_B, alpha, r=6)[0]
def FB_blur_fusion_foreground_estimator(image, F, B, alpha, r=90):
if isinstance(image, Image.Image):
image = np.array(image) / 255.0
blurred_alpha = cv2.blur(alpha, (r, r))[:, :, None]
blurred_FA = cv2.blur(F * alpha, (r, r))
blurred_F = blurred_FA / (blurred_alpha + 1e-5)
blurred_B1A = cv2.blur(B * (1 - alpha), (r, r))
blurred_B = blurred_B1A / ((1 - blurred_alpha) + 1e-5)
F = blurred_F + alpha * \
(image - alpha * blurred_F - (1 - alpha) * blurred_B)
F = np.clip(F, 0, 1)
return F, blurred_B
def postprocess_image(result: torch.Tensor, im_size: list) -> np.ndarray:
result = torch.squeeze(F.interpolate(result, size=im_size, mode='bilinear'), 0)
ma = torch.max(result)
@@ -944,4 +1288,23 @@ def postprocess_image(result: torch.Tensor, im_size: list) -> np.ndarray:
result = (result - mi) / (ma - mi)
im_array = (result * 255).permute(1, 2, 0).cpu().data.numpy().astype(np.uint8)
im_array = np.squeeze(im_array)
return im_array
return im_array
def rgb_loader_refiner(original_image):
h, w = original_image.size
# # Apply EXIF orientation
image = ImageOps.exif_transpose(original_image)
if original_image.mode != 'RGB':
original_image = original_image.convert('RGB')
image = original_image
# Convert to RGB if necessary
# Resize the image
image = image.resize((1024, 1024), resample=Image.LANCZOS)
return image, h, w, original_image
+3
View File
@@ -1,8 +1,11 @@
import numpy as np
import os
import json
import torch
import folder_paths
import comfy
import comfy.model_management
from PIL import Image
from PIL.PngImagePlugin import PngInfo
from nodes import ConditioningSetMask, RepeatLatentBatch
from comfy_extras.nodes_mask import LatentCompositeMasked
+5 -4
View File
@@ -828,7 +828,8 @@ class imageRemBg:
},
"optional":{
"torchscript_jit": ("BOOLEAN", {"default": False}),
"add_background": (["none", "white", "black"], {"default": "none"})
"add_background": (["none", "white", "black"], {"default": "none"}),
"refine_foreground": ("BOOLEAN", {"default": False}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
@@ -841,7 +842,7 @@ class imageRemBg:
CATEGORY = "EasyUse/Image"
def remove(self, rem_mode, images, image_output, save_prefix, torchscript_jit=False, add_background='none',prompt=None, extra_pnginfo=None):
def remove(self, rem_mode, images, image_output, save_prefix, torchscript_jit=False, add_background='none', refine_foreground=False, prompt=None, extra_pnginfo=None):
new_images = list()
masks = list()
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
@@ -941,7 +942,7 @@ class imageRemBg:
if input_image.mode != 'RGBA':
input_image = input_image.convert("RGBA")
mask, new_im = model.inference(input_image)
mask, new_im = model.inference(input_image, refine_foreground)
new_im_tensor = pil2tensor(new_im)
mask_tensor = pil2tensor(mask)
@@ -1745,7 +1746,7 @@ class imageToBase64:
pil_image = tensor2pil(image)
buffered = BytesIO()
pil_image.save(buffered, format="JPEG")
pil_image.save(buffered, format="PNG")
image_bytes = buffered.getvalue()
base64_str = base64.b64encode(image_bytes).decode("utf-8")
+5 -2
View File
@@ -28,7 +28,7 @@ class fullLoader:
a1111_prompt_style_default = False
return {"required": {
"ckpt_name": (folder_paths.get_filename_list("checkpoints"),),
"ckpt_name": (folder_paths.get_filename_list("checkpoints") + ['None'],),
"config_name": (["Default", ] + folder_paths.get_filename_list("configs"), {"default": "Default"}),
"vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),),
"clip_skip": ("INT", {"default": -2, "min": -24, "max": 0, "step": 1}),
@@ -71,6 +71,9 @@ class fullLoader:
my_unique_id=None
):
if ckpt_name == 'None' and model_override is None:
raise Exception("Please select a checkpoint or provide a model override.")
# Clean models from loaded_objects
easyCache.update_loaded_objects(prompt)
@@ -923,7 +926,7 @@ class fluxLoader(fullLoader):
loras = ["None"] + folder_paths.get_filename_list("loras")
return {
"required": {
"ckpt_name": (checkpoints,),
"ckpt_name": (checkpoints + ['None'],),
"vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),),
"lora_name": (loras,),
"lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
+8
View File
@@ -12,6 +12,14 @@ from .libs.utils import getMetadata, cleanGPUUsedForce, get_local_filepath
from .libs.cache import remove_cache
from .libs.translate import has_chinese, zh_to_en
@PromptServer.instance.routes.get('/easyuse/version')
def get_version(request):
try:
from .. import __version__
return web.json_response({"version": __version__})
except Exception as e:
print(e)
return web.Response(status=500)
@PromptServer.instance.routes.post("/easyuse/cleangpu")
def cleanGPU(request):
+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.2.7"
version = "1.2.8"
license = { file = "LICENSE" }
dependencies = ["diffusers", "accelerate", "clip_interrogator>=0.6.0", "sentencepiece", "lark", "onnxruntime", "spandrel", "opencv-python", "matplotlib", "peft"]
+1 -1
View File
@@ -1,5 +1,5 @@
# 开发人员使用(请勿运行)
# 将 https://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Translation 的翻译文件转换格式以适配 ComfyUI核心 locales
# 将 https://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Translation 的翻译文件转换格式以适配 ComfyUI locales
import json
import os
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