refactor(core): put dirs and category

This commit is contained in:
yemiancheng
2025-06-05 13:23:16 +08:00
parent 0cc642dd60
commit 6f8729c9f5
20 changed files with 1915 additions and 2429 deletions
-7
View File
@@ -1,7 +0,0 @@
- [x] feat(core): add feat that noted in issue 1
- [x] feat(core): add paths when it does not exits
- [x] feat(core): out info when text is empty
- [x] feat(core): use prefix as file name when overwrite_mode is 'prefix_as_filename'
- [x] feat(core): use the 2th result is filename without ext
- [x] feat(core): use the 3th result is ext
- [x] feat(core): rename 'Save Text File' to io-text-save for issue 3
-260
View File
@@ -1,260 +0,0 @@
## basic do
- [x] set git default branch
```bash
git config --global init.defaultBranch main
```
- [x] ini git repo
```bash
[ ! -e .git ] && git init;
```
- [x] set user name and email
```bash
git config --global user.name "yemiancheng" ;git config --global user.email "ymc.github@gmail.com";
```
- [ ] change the master bransh to "main" from "master"
```bash
git branch -M main
```
- [x] add github repo (in plan)
```bash
# copy token files to current project
# cp /d/code/shell/github-cli/secrets/* ./secrets
workspace=$(pwd);
cd /d/code/shell/github-cli;shfile=./index.sh;
repourl="ymc-github/ymc-node-suite-comfyui";repodesc="custom nodes for comfyui,like AI painting in comfyui";
# add repo with default desc
$shfile $repourl add # (in plan)
# put repo desc
# $shfile "$repourl" put "$repodesc" # (process)
# $shfile $repourl del
cd "$workspace"
```
- [x] add remote repo url
```bash
# git remote add origin https://github.com/YMC-GitHub/ymc-node-suite-comfyui.git
git remote add origin git@github.com:YMC-GitHub/ymc-node-suite-comfyui.git
```
- [x] set gitignore and commit it
```bash
git add .gitignore;git commit -m "build(core): add gitignore";
git add .gitignore;git commit -m "build(core): set note for go allow list";
git add .gitignore;git commit -m "build(core): put gitignore to design project dirs";
```
[fefer: some gitignore template](https://github.com/github/gitignore)
- [x] set edifotconfig and commit it
```bash
git add .editorconfig ; git commit -m "build(core): add editor config";
```
- [x] set license and commit it
```bash
git add LICENSE ; git commit -m "build(core): add license";
```
- [x] set readmd and commit it
```bash
git add README.md ; git commit -m "docs(core): add readme";
git add README.md ; git commit -m "docs(core): put readme to readable";
```
- [ ] get time with format
```bash
format="+%Y-%m-%d %H:%M:%S";time="2021-05-18 13:12:19";date -d "$time" "$format";
```
- [ ] git commit files with custom date
```bash
git commit -m "chore(core): update readme in github action" --date "$(date "+%Y-%m-%d %H:%M:%S" -d "+8 hour") +0800"
```
- [ ] design project dirs
```bash
git add release_win/.gitignore;git commit -m "build(core): add gitignore to design project dirs";
git add release_win/README.md;git commit -m "docs(core): output *.exe files here";
git add release_win/*.ico;git commit -m "build(core): output icon files here";
git add icons/.gitignore;git commit -m "build(core): add gitignore to design project dirs";
git add icons/README.md;git commit -m "docs(core): put icon files here as resources";
git add icons/*.ico;git commit -m "build(core): add resources files icon";
git add .vscode/.gitignore;git commit -m "build(core): add gitignore to design project dirs";
git add .vscode/README.md;git commit -m "docs(core): put vscode editor config files of project level here";
```
- [ ] add docs about how to setup this
```bash
git add README.01-SETUP.md ; git commit -m "docs(core): add readme to setup this";
```
- [ ] add docs about how to code before coding
```bash
git add README.02-BEFORE-CODE.md ; git commit -m "docs(core): add readme to code this";
```
- [ ] add docs about some libs i noted
```bash
git add README.03-LIB-NOTE.md ; git commit -m "docs(core): add readme to note libs";
```
- [ ] add go code snippets in project level
```bash
git add .vscode/.gitignore;git commit -m "build(core): add go code snippets in project level";
git add .vscode/go.code-snippets;git commit -m "docs(core): add go code snippets";
```
- [ ] add hello world in root dirs
```bash
git add main.go;git commit -m "build(core): add hello world in root dirs";
```
- [ ] add go mod and workspace
```bash
git add go.mod go.sum go.work.sum;git commit -m "build(core): add mod and workspace";
```
- [ ] add mod chapter01
```bash
git add src/chapter01/higo;git commit -m "build(core): add mod chapter01";
```
- [x] push to github repo (the first)
```bash
git push -u origin main
```
- [ ] replace git protocol with https when failed
```bash
# set
git config --global url."https://".insteadof git://
# unset
# git config --global --unset url."https://".insteadof
```
- [x] rename github repo remote name from "origin" to "github"
```bash
git remote rename origin github
```
- [ ] remove github repo remote
```bash
git remote remove github
```
- [ ] set remote repo url from https to git
```bash
git remote set-url origin git@github.com:YMC-GitHub/ymc-node-suite-comfyui.git
# git remote set-url github git@github.com:YMC-GitHub/ymc-node-suite-comfyui.git
```
- [x] speedup github for `git clone`
```bash
# set
git config --global url."https://ghproxy.com/https://github.com".insteadOf "https://github.com"
# unset
# git config --global --unset url."https://ghproxy.com/https://github.com".insteadOf
# get
# git config --global url."https://ghproxy.com/https://github.com".insteadOf
```
- [x] unset speedup github for `git clone` when pushing
```bash
# unset
git config --global --unset url."https://ghproxy.com/https://github.com".insteadOf
```
- [x] mgnt git credential
```powershell
# git config --global credential.helper manager
# git config --global credential.helper cache
# git config --global --unset credential.helper
# https://zhuanlan.zhihu.com/p/157751660
```
- [x] add docs about using git for this repo i noted
```bash
git add README.04-GIT.md ; git commit -m "docs(core): add readme to note git for this repo";
git add README.04-GIT.md ; git commit -m "docs(core): put readme to note git for this repo";
```
- [ ] add docs about using ssh in github i noted
```bash
git add README.05-SSH.md ; git commit -m "docs(core): add readme to note ssh";
```
- [x] set pull action for this repo
```bash
# zero:knowledge:s:pull-action
# you must know .
# git config pull.rebase false # merge
git config pull.rebase true # rebase
# git config pull.ff only # fast-forward only
# zero:knowledge:e:pull-action
```
- [x] keep workspace clean before pulling
```bash
# git stash
```
- [x] pull remote github repo before pushing again
```bash
git pull origin main
# git pull github main
```
- [x] push remote github repo (no the first time)
```bash
git push origin main
```
- [x] add requirements
```bash
git add requirements.txt ; git commit -m "build(core): add requirements nodes";
```
- [x] add current custom nodes
```bash
git add __init__.py YMC_Node_Suite.py; git commit -m "feat(core): add custom nodes";
```
- [x] add changelog for this repo
```bash
git add README.md CHANGELOG*; git commit -m "docs(core): add changelog info";
```
- [x] add tag
```bash
git log --oneline
git tag v1.0.0 -m "version 1.0.0" 4ce981e
git tag v1.1.0 -m "version 1.1.0" 28b0da0
git tag v2.0.0 -m "version 2.0.0" 44d9b66
git push origin main --tags
```
- [x] get feat or build note from code
build(core): get feat in bash `cat YMC_Node_Suite.py | grep "^ *# feat.*" | sed "s/ *#/- [x]/g" > CHANGELOG.FEAT.md`
build(core): get build in bash `cat YMC_Node_Suite.py | grep "^ *# build.*" | sed "s/ *#/- [x]/g" > CHANGELOG.BUILD.md`
+29
View File
@@ -2,6 +2,31 @@
ymc 's nodes for comfyui
## Usage
- you can find it in search box : double click + typing keywords( eg. `ymc suite` ,`text` ,`cutoff`,`region`)
- you can find it in right mouse menu : `ymc suite`
- ~~you can find it in right mouse menu : `ymc/utils` (as alias)~~
## Based-on
- pypi package [yors_comfyui_node_setup](https://pypi.org/project/yors_comfyui_node_setup/) - setup comfyui custom nodes easily
- pypi package [yors_pano_ansi_color](https://pypi.org/project/yors_pano_ansi_color/) - info msg in console with color in your comfyui custom nodes easily
- pypi package [yors_pano_zero_field](https://pypi.org/project/yors_pano_zero_field/) - set nodes input field to be HQ in your comfyui custom nodes easily
- pypi package [yors_pano_list_util](https://pypi.org/project/yors_pano_list_util/) - enjoy python list for python starter in your comfyui custom nodes easily.
## Published to Comfy registry
- get more details in [publish_to_comfy.yml](.github/workflows/publish_to_comfy.yml)
- [docs for publishing to comfy registey](https://docs.comfy.org/registry/overview)
- installed with comfy-cli ? `comfy node registry-install ymc-node-suite-comfyui`
## Author
name|email|desciption
@@ -15,6 +40,10 @@ MIT
## Changelog
### 3.0.0
- [x] refator(core): rename project dirs and all nodes name for hight qulity nodes.
### 2.0.0
- [x] feat(core): rename 'Save Text File' to io-text-save for issue 3
+53 -13
View File
@@ -7,25 +7,65 @@
# # export comfyui node vars
# __all__,NODE_CLASS_MAPPINGS,NODE_DISPLAY_NAME_MAPPINGS,NODE_MENU_NAMES = entry(__name__,__file__)
# v2
from yors_comfyui_node_setup import node_install_requirements,entry_pre_import,entry_import,get_all_classs_in_sys,register_node_list_local
# v4
import os
from yors_comfyui_node_setup import custom_nodes_get_path,custom_nodes_add_to_sys,import_custom_node_module,node_install_requirements,get_module_name_contains_x_in_sys,get_node_class_in_sys_modules,register_node_list_local
from yors_pano_ansi_color import info_status,info_step,msg_padd,log_msg
import importlib
# install requirements
node_install_requirements(__file__)
info_step("add comfui/custom_nodes to sys.path")
custom_nodes_path = [custom_nodes_get_path(__file__,'../../custom_nodes')]
custom_nodes_add_to_sys(custom_nodes_path)
# gen __all__
__all__ = entry_pre_import(__name__,__file__)
info_step("import modules in sub diretory modules")
import_custom_node_module(os.path.dirname(__file__),__name__,"modules")
# import moudle with __all__
entry_import(__name__,__all__)
# info_step(f"install deps (not installed) in requirements.txt if file exits")
# node_install_requirements(__file__)
# info_step(f"read name in sys.modules if name including name")
# - custom_node_module_class_name_list = read_module_class_name(__name__)
# custom_node_module_class_name_list = get_module_name_contains_x_in_sys(__name__)
info_step(f"read valid node class if key in sys.modules including this module name")
# - NodeClassList,NodeClassNameList = read_module_valid_node_class(__name__)
NodeClassList,NodeClassNameList = get_node_class_in_sys_modules(__name__)
info_step(f"make node_class_mappings and node_display_name_mappings {__name__}")
NODE_CLASS_MAPPINGS,NODE_DISPLAY_NAME_MAPPINGS,NODE_MENU_NAMES = register_node_list_local(NodeClassList)
info_step(f"print node name of {__name__}")
NODE_MENU_NAMES.sort()
# print("\n".join(NODE_MENU_NAMES))
# get class after importing moudle with __all__
this_module_all_classes = get_all_classs_in_sys(__name__)
# register node with default category
# NODE_CLASS_MAPPINGS,NODE_DISPLAY_NAME_MAPPINGS,NODE_MENU_NAMES = register_node_list(this_module_all_classes,False)
# - NODE_CLASS_MAPPINGS,NODE_DISPLAY_NAME_MAPPINGS,NODE_MENU_NAMES = register_node_list(this_module_all_classes,False)
# NODE_CLASS_MAPPINGS,NODE_DISPLAY_NAME_MAPPINGS,NODE_MENU_NAMES = register_node_list_local(this_module_all_classes,False)
# addtional register node with custom category
NODE_CLASS_MAPPINGS,NODE_DISPLAY_NAME_MAPPINGS,NODE_MENU_NAMES = register_node_list_local(this_module_all_classes,True,"ymc/suite")
# NODE_CLASS_MAPPINGS,NODE_DISPLAY_NAME_MAPPINGS,NODE_MENU_NAMES = register_node_list_local(this_module_all_classes,True,"ymc/suite")
# print(NODE_CLASS_MAPPINGS,NODE_DISPLAY_NAME_MAPPINGS,NODE_MENU_NAMES)
print("\n".join(NODE_MENU_NAMES))
# print("\n".join(NODE_MENU_NAMES))
# info_step(f"define __all__ (not web ext) {__name__}")
# __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
info_step(f"define __all__ (support web ext) {__name__}")
WEB_DIRECTORY = "./web"
# docs(core): export NODE_CLASS_MAPPINGS,NODE_DISPLAY_NAME_MAPPINGS,WEB_DIRECTORY
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
# get version from pyproject.toml
# ...
version="3.0.0"
log_msg(msg_padd("=",60,"="))
log_msg(msg_padd("welocme to ymc_node_suite_comfyui",60,"="))
log_msg(f'version: {version}')
log_msg(f'node counts:{len(NODE_MENU_NAMES)}')
log_msg(f'node menu names:')
for node_name in NODE_MENU_NAMES:
# log_msg(f'node name:{node_name}')
info_status(f'{node_name}',0)
log_msg(msg_padd("=",60,"="))
-2
View File
@@ -1,2 +0,0 @@
CURRENT_CATEGORY="ymc/suite"
CURRENT_FUNCTION="exec"
+141
View File
@@ -0,0 +1,141 @@
以下是代码中定义的 ComfyUI 节点(Nodes)及其功能的解读:
---
### **1. IO 相关节点**
- **`io-text-save` (save text file)**
- **功能**:将文本保存到文件中,支持路径和文件名生成规则(如时间戳、自定义前缀等)。
- **输入**:文本内容、路径、文件名前缀、扩展名等。
- **输出**:保存的文本内容、文件名(不含扩展名)、扩展名。
- **特点**:支持历史记录管理,路径自动创建。
- **`io-util-file-list-get`**
- **功能**:获取指定目录下匹配特定模式的文件列表(如 `*.txt`)。
- **输入**:目录路径、文件匹配模式(如 `*.txt`)。
- **输出**:文件列表(每行一个文件名或绝对路径)。
- **`io-util-file-list-get-text`**
- **功能**:读取指定目录下所有匹配文件的文本内容并合并。
- **输入**:目录路径、文件匹配模式。
- **输出**:合并后的多行文本。
---
### **2. 文本处理节点**
- **`text-util-join-text`**
- **功能**:将多个输入文本用分隔符连接成一个字符串。
- **输入**:分隔符和最多 7 个文本字段。
- **输出**:合并后的字符串。
- **`text-util-search-text`**
- **功能**:在文本中搜索并替换内容。
- **输入**:原始文本、搜索字符串、替换字符串。
- **输出**:替换后的文本。
- **`text-util-random-text`**
- **功能**:从分隔符(如 `|`)分隔的文本中随机选择一项。
- **输入**:文本、随机种子、分隔符。
- **输出**:随机选择的文本和种子。
- **`text-util-loop-text`**
- **功能**:按顺序循环选择分隔符分隔的文本项(基于种子)。
- **输入**:文本、种子、分隔符。
- **输出**:选择的文本、种子、当前索引。
- **`text-util-switch-text`**
- **功能**:根据布尔值切换输出两个文本之一。
- **输入**:两个文本、布尔值(0 或 1)。
- **输出**:选择的文本。
---
### **3. 提示词(Prompt)处理节点**
- **`text-util-prompt-join`**
- **功能**:合并多个提示词,自动去重和标准化格式。
- **输入**:最多 7 个提示词字段。
- **输出**:合并后的提示词。
- **`text-util-prompt-del`**
- **功能**:从提示词中删除指定内容。
- **输入**:原始提示词、待删除内容。
- **输出**:清理后的提示词。
- **`text-util-prompt-std`**
- **功能**:标准化提示词(去除多余空格、换行等)。
- **输入**:原始提示词。
- **输出**:标准化后的提示词。
- **`text-util-prompt-shuffle`**
- **功能**:随机打乱提示词顺序(可选保留前 N 项)。
- **输入**:提示词、随机种子、保留项数。
- **输出**:打乱后的提示词。
---
### **4. 区域(Region)工具节点**
- **`region-util-get-by-center-and-size`**
- **功能**:根据中心点坐标和宽高计算裁剪区域的边界(左、上、右、下)。
- **输入**:中心点坐标 `(cx, cy)`、宽 `w`、高 `h`。
- **输出**:边界坐标和中心点信息。
- **`region-util-get-by-lt`**
- **功能**:根据左上角坐标和宽高计算裁剪区域的边界。
- **输入**:左上角坐标 `(left, top)`、宽 `w`、高 `h`。
- **输出**:边界坐标和中心点信息。
- **`region-util-get-pad-out-location-by-size`**
- **功能**:根据宽高计算填充(Padding)后的边界坐标(用于图像扩展)。
- **输入**:宽 `w`、高 `h`。
- **输出**:填充后的边界坐标。
---
### **5. 画布(Canvas)工具节点**
- **`canvas-util-cal-size`**
- **功能**:根据宽高比例(如 `1:1`、`9:16`)调整画布尺寸。
- **输入**:原始宽高、比例、锁定模式(固定宽或高)。
- **输出**:调整后的宽高。
---
### **6. 图像处理节点**
- **`img-util-get-image-size`**
- **功能**:获取图像的宽度和高度。
- **输入**:图像张量。
- **输出**:宽、高。
- **`img-util-switch-input-image`**
- **功能**:根据布尔值切换输出两张图像之一。
- **输入**:两张图像、布尔值。
- **输出**:选择的图像。
---
### **7. 条件(Conditioning)工具节点**
- **`conditioning-util-input-switch`**
- **功能**:根据布尔值切换输出两个条件(Conditioning)之一。
- **输入**:两个条件、布尔值。
- **输出**:选择的条件。
---
### **8. 其他工具节点**
- **`number-util-random-num`**
- **功能**:生成随机数(整数、浮点数或布尔值)。
- **输入**:范围、随机种子。
- **输出**:随机数。
- **`xyz-util-txt-to-int`**
- **功能**:将文本描述的数值范围(如 `1-10[5]`)解析为整数或浮点数列表。
- **输入**:文本范围、键名(如 `Steps`)。
- **输出**:解析后的数值列表和网格标注文本。
- **`pipe-util-to-basic-pipe`**
- **功能**:将模型、CLIP、VAE、条件等组合成基础管道(Basic Pipe)。
- **输入**:模型、CLIP、VAE、正负条件。
- **输出**:基础管道及各组件。
---
### **总结**
这些节点覆盖了文本处理、图像操作、区域计算、条件控制、随机生成等常见需求,适合在 ComfyUI 工作流中实现复杂的逻辑组合。每个节点均支持 ComfyUI 的输入输出规范,可直接与其他节点连接使用。
+33
View File
@@ -0,0 +1,33 @@
## attempted relative import with no known parent package
### 终端输出内容
```terminal
Traceback (most recent call last):
File "D:\code\python\panz\comfyui-nodes\ymc-node-suite-comfyui\node_demo.py", line 1, in <module>
from .conf import CURRENT_CATEGORY,CURRENT_FUNCTION
ImportError: attempted relative import with no known parent package
```
### 错误解释
- `Traceback (most recent call last)`:这是 Python 错误堆栈跟踪的起始行,表明 Python 开始回溯错误发生的位置。
- `File "D:\code\python\panz\comfyui-nodes\ymc-node-suite-comfyui\node_demo.py", line 1, in <module>`:指出错误发生在 `node_demo.py` 文件的第 1 行。
- `from .conf import CURRENT_CATEGORY,CURRENT_FUNCTION`:这是出错的代码行,尝试使用相对导入从 `conf` 模块导入 `CURRENT_CATEGORY` 和 `CURRENT_FUNCTION`。
- `ImportError: attempted relative import with no known parent package`:错误类型为 `ImportError`,提示使用相对导入时没有找到已知的父包。相对导入需要在包的上下文中使用,当直接运行脚本时,Python 不会将其视为包的一部分,所以会报错。
### 修复建议
#### 方法一:将项目作为包运行
如果你想使用相对导入,可以将项目作为包运行。在项目根目录下使用 `-m` 选项:
```powershell
python -m ymc-node-suite-comfyui.node_demo
```
#### 方法二:使用绝对导入
修改 `node_demo.py` 文件,将相对导入改为绝对导入:
```python:d:\code\python\panz\comfyui-nodes\ymc-node-suite-comfyui\node_demo.py
# 修改前
# from .conf import CURRENT_CATEGORY,CURRENT_FUNCTION
# 修改后
from ymc_node_suite_comfyui.conf import CURRENT_CATEGORY,CURRENT_FUNCTION
```
请根据实际的包名调整绝对导入路径。
+3 -1
View File
@@ -1,4 +1,6 @@
from .conf import CURRENT_CATEGORY,CURRENT_FUNCTION
CURRENT_CATEGORY="ymc/suite"
CURRENT_FUNCTION="exec"
class TextInput:
@classmethod
def INPUT_TYPES(s):
+113
View File
@@ -0,0 +1,113 @@
```powershell
# 1
python __init__.py
python nodes.py
#
python -m main; # run module main done
python main.py; # run module main done
# python node_demo.py
python -m ymc_node_suite_comfyui.node_demo
# yours touch docs/ask.for.ai.py.faqs.01.md
# yours touch docs/ask.for.ai.py.faqs.02.md
yours touch docs/ask.for.ai.node-01-the-old-nodes.md
yours touch docs/ask.for.ai.node-02-refactor-nodes.md
yours touch nodes/text/__init__.py
yours touch nodes/text/conf.py
yours touch nodes/text/join.py
yours touch nodes/text/search.py
yours touch nodes/text/random.py
yours touch nodes/text/loop.py
yours touch nodes/text/switch.py
yours touch nodes/text/prompts.py #join,std,del,dup,shuffle
yours touch nodes/text/input.py
yours touch nodes/area/__init__.py
yours touch nodes/area/conf.py
yours touch nodes/area/from_center_and_size.py
yours touch nodes/area/from_lefttop_and_size.py
yours touch nodes/area/padd.py
# canvas cal size
yours touch nodes/image/__init__.py
yours touch nodes/image/conf.py
yours touch nodes/image/get_image_size.py
yours touch nodes/image/switch_image.py
yours touch nodes/utils/__init__.py
yours touch nodes/utils/conf.py
yours touch nodes/utils/random_num.py
yours touch nodes/utils/text_to_int.py
yours touch nodes/utils/basic_pipe.py
yours touch nodes/file/__init__.py
yours touch nodes/file/conf.py
yours touch nodes/file/get_file_list.py
yours touch nodes/file/get_text_of_file_list.py
yours touch nodes/cutoff/__init__.py
sh -c "cp nodes/text/__init__.py nodes/cutoff/__init__.py"
sh -c "cp nodes/text/conf.py nodes/cutoff/conf.py"
yours touch nodes/cutoff/text_from.py
yours touch nodes/cutoff/text_list_color.py
python -c "import sys;print(sys.path)"
# sh -c "rm -rf nodes/text/{input,join,loop,random,search,switch}.py"
# sh -c "rm -rf nodes/cutoff"
# sh -c "rm -rf nodes/cutoff"
```
## fags
- ImportError: attempted relative import with no known parent package
## depoy to comfyui (local + debug)
```bash
# del local nodes in comfyui custom_nodes
sh -c "rm -rf /o//app/ComfyUI/custom_nodes/ymc-node-suite-comfyui"
# copy local nodes to comfyui custom_nodes
sh -c "cp -r /d/code/python/panz/comfyui-nodes/ymc-node-suite-comfyui /o/app/ComfyUI/custom_nodes/"
# conda activate sdwa;cd O:\app\ComfyUI;python main.py;
```
## opv - docs
```bash
git add README.md ; git commit -m "docs(core): put usage"
git add . ; git commit -m "refactor(core): reset dirs"
git add . ; git commit -m "refactor(core): rename nodes map"
git add . ; git commit -m "refactor(core): rename nodes display name"
git add . ; git commit -m "refactor(core): put dirs and category"
# get git msg log with oneline and short hash and msg head and time
git log --pretty=format:"%h %ad %s" --date=short -n 5
git log --oneline -n 5
git rm CHANGELOG.*.md
git rm conf.py
git mv node_demo.py docs/node_demo.py.md
git rm README.*.md
```
## opv - shotscreen
```bash
```
+5
View File
@@ -0,0 +1,5 @@
from yors_comfyui_node_setup import entry,node_install_requirements
node_install_requirements(__file__)
__all__,NODE_CLASS_MAPPINGS,NODE_DISPLAY_NAME_MAPPINGS,NODE_MENU_NAMES = entry(__name__,__file__,False)
# from . import *
+5
View File
@@ -0,0 +1,5 @@
from yors_comfyui_node_setup import entry,node_install_requirements
node_install_requirements(__file__)
__all__,NODE_CLASS_MAPPINGS,NODE_DISPLAY_NAME_MAPPINGS,NODE_MENU_NAMES = entry(__name__,__file__,False)
# from . import *
+2
View File
@@ -0,0 +1,2 @@
CURRENT_CATEGORY="ymc suite/text"
CURRENT_FUNCTION="exec"
+101
View File
@@ -0,0 +1,101 @@
# import random
from yors_pano_list_util import ListDelExclude,ListFillOne,ListToTupe,ListShuffle
# from .conf import CURRENT_CATEGORY,CURRENT_FUNCTION
CURRENT_CATEGORY="ymc suite/text"
CURRENT_FUNCTION="exec"
def StrSetUndefinedAsEmpty(text,deft:str=""):
"""
target_text=StrSetUndefinedAsEmpty(target_text)
when text is "undefined", return ""
"""
if text == "undefined":
return deft
return text
# code(core): about shift + tap and tap in vscode
def CutoffRegionTextBurn(main_text, target_text, weight):
"""
CutoffRegionTextBurn(main_text,target_text,weight)
burn region text
"""
# Converted inputs are sent as the string of 'undefined' if not connected
target_text = StrSetUndefinedAsEmpty(target_text)
main_text = StrSetUndefinedAsEmpty(main_text)
region_text = ""
if weight is None or weight == "" or weight == "1.0":
region_text = (" ").join(filter(None, [target_text, main_text]))
else:
# region_text = (" ").join(filter(None, ["(",target_text, main_text,":",weight,")"]))
region_text = (" ").join(filter(None, [target_text, main_text]))
region_text = ("").join(filter(None, ["(", region_text, ":", weight, ")"]))
# return {"ui": {"text": (region_text,)}, "result": (region_text,)}
return region_text, target_text, weight
# feat(core): NodeCutoffRegionTextBurn - cutoff - region text burn
class NodeCutoffRegionTextBurn:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"main_text": ("STRING", {"default": "dress", "multiline": True}),
"target_text":("STRING", {"default": "white", "multiline": False}),
"weight": ("STRING", {"default": "1.0", "multiline": False}),
}
}
RETURN_TYPES = ("STRING","STRING","STRING",)
RETURN_NAMES = ('region_text','target_text','prompt_weight')
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "cutoff - region text burn"
OUTPUT_NODE = True
def exec(self, main_text,target_text,weight):
return CutoffRegionTextBurn(main_text,target_text,weight)
TEXT_BASE_COLORS=['red','orange','yellow','green','cyan','blue','purple','black','white','pink','custom']
TEXT_BASE_COLORS_COUNT=len(TEXT_BASE_COLORS)
def CutoffColorTextPreset(custom:str="sliver"):
"""
CutoffColorTextList(custom)
color text preset will update the last one will custom
"""
# return red,orange,yellow,green,cyan,blue,purple,black,white,pink,custom
TEXT_BASE_COLORS_VALUE = TEXT_BASE_COLORS.copy()
TEXT_BASE_COLORS_VALUE[TEXT_BASE_COLORS_COUNT-1]=custom
return ListToTupe(TEXT_BASE_COLORS_VALUE)
# feat(core): NodeCutoffRegionColorTextPreset - cutoff - color preset of region text
class NodeCutoffRegionColorTextPreset:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"custom": ("STRING", {"default": "red", "multiline": False})
}
}
# RETURN_TYPES = ("STRING","STRING","STRING","STRING","STRING","STRING","STRING","STRING","STRING","STRING","STRING",)
RETURN_TYPES = ListToTupe(ListFillOne('STRING',TEXT_BASE_COLORS_COUNT))
RETURN_NAMES = ListToTupe(TEXT_BASE_COLORS)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "cutoff - color preset of region text"
OUTPUT_NODE = True
def exec(self, custom):
# Converted inputs are sent as the string of 'undefined' if not connected
custom=StrSetUndefinedAsEmpty(custom,"sliver")
return CutoffColorTextPreset(custom)
+503
View File
@@ -0,0 +1,503 @@
import random
from yors_pano_list_util import ListDelExclude,ListFillOne,ListToTupe,ListShuffle
from .conf import CURRENT_CATEGORY,CURRENT_FUNCTION
def TextListRandom(text:str, seed,char='|'):
"""
item,seed = TextListRandom(text,seed,'|')
TextListRandom(text,seed,',')
"""
iteml = text.split(char)
random.seed(seed)
choice = random.choice(iteml)
return ListToTupe([choice,seed])
def range_safe(i:int,s:int,e:int):
'''
ensure i is in [s,e]
'''
res=i
if res <s:
res=s
if res >e:
res=e
return res
def range_safe_loop(i:int,s:int,e:int):
'''
ensure i is in [s,e] for loop
'''
res=i
if res <s:
res=s
if res >=e:
res = res % e
res = s + res
return res
def TextListLoop(text, seed,char='|'):
iteml = text.split(char)
# get loop index with de/cr seed
length=len(iteml)
index=range_safe_loop(seed-1,0,length)
choice = iteml[index]
# print(maxindex,seed,index,choice)
return ListToTupe([choice,seed,str(index+1),length])
def TextReplace(text, find, replace):
"""
text replace find with re.sub
"""
import re
result = re.sub(find, replace, text)
return result
TEXT_LIST_RETURN_NAMES=['a','b','c','d','e','f','g']
TEXT_LIST_COUNT=len(TEXT_LIST_RETURN_NAMES)
TEXT_LIST_ADV_RETURN_NAMES=TEXT_LIST_RETURN_NAMES.copy()
TEXT_LIST_ADV_RETURN_NAMES.insert(0,"prefix")
# feat(core): NodePlainTextList - input text preset
class NodePlainTextList:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"a": ("STRING",{"forceInput":False}),
"b": ("STRING",{"forceInput":False}),
"c": ("STRING",{"forceInput":False}),
"d": ("STRING",{"forceInput":False}),
"e": ("STRING",{"forceInput":False}),
"f": ("STRING",{"forceInput":False}),
"g": ("STRING",{"forceInput":False}),
}
}
RETURN_TYPES = ListToTupe(ListFillOne('STRING',TEXT_LIST_COUNT))
RETURN_NAMES = ListToTupe(TEXT_LIST_RETURN_NAMES)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "plain - input text preset"
OUTPUT_NODE = True
def exec(self, a,b,c,d,e,f,g):
return ListToTupe([a,b,c,d,e,f,g])
# feat(core): NodePlainTextListAdv - input text preset - adv
class NodePlainTextListAdv:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"prefix": ("STRING",{"forceInput":False}),
"a": ("STRING",{"forceInput":False}),
"b": ("STRING",{"forceInput":False}),
"c": ("STRING",{"forceInput":False}),
"d": ("STRING",{"forceInput":False}),
"e": ("STRING",{"forceInput":False}),
"f": ("STRING",{"forceInput":False}),
"g": ("STRING",{"forceInput":False}),
}
}
RETURN_TYPES = ListToTupe(ListFillOne('STRING',TEXT_LIST_COUNT+1))
RETURN_NAMES = ListToTupe(TEXT_LIST_ADV_RETURN_NAMES)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "plain - input text preset - adv"
OUTPUT_NODE = True
def exec(self,prefix, a,b,c,d,e,f,g):
joined = list(map(lambda x: prefix+x,[a,b,c,d,e,f,g]))
import datetime
# import time
joined.insert(0,prefix)
joined = list(map(lambda x: datetime.datetime.now().strftime(x),joined)) # replace time exp like %Y %M %D
return ListToTupe(joined)
# feat(core): NodePlainTextListJoinTextAdv - join text to text preset - adv
class NodePlainTextListJoinTextAdv:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
},
"optional": {
"text": ("STRING",{"forceInput": True}),
"a": ("STRING",{"forceInput":True}),
"b": ("STRING",{"forceInput":True}),
"c": ("STRING",{"forceInput":True}),
"d": ("STRING",{"forceInput":True}),
"e": ("STRING",{"forceInput":True}),
"f": ("STRING",{"forceInput":True}),
"g": ("STRING",{"forceInput":True}),
"action": (["none","text-as-head","text-as-tail"],),
}
}
RETURN_TYPES = ListToTupe(ListFillOne('STRING',TEXT_LIST_COUNT+1))
RETURN_NAMES = ListToTupe(['text','a','b','c','d','e','f','g'])
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "plain - join text to text preset - adv"
OUTPUT_NODE = True
def exec(self,text, a,b,c,d,e,f,g,action="none"):
joined=[]
if "none" in action:
joined = [a,b,c,d,e,f,g]
elif "text-as-head" in action:
joined = list(map(lambda x: text+x,[a,b,c,d,e,f,g]))
elif "text-as-tail" in action:
joined = list(map(lambda x: x+text,[a,b,c,d,e,f,g]))
import datetime
# import time
joined.insert(0,text)
joined = list(map(lambda x: datetime.datetime.now().strftime(x),joined)) # replace time exp like %Y %M %D
return ListToTupe(joined)
# feat(core): NodePlainTextJoin - join text
class NodePlainTextJoin:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"splitchar": ("STRING", {"default": "_", "multiline": False}),
"a": ("STRING",{"forceInput":False}),
"b": ("STRING",{"forceInput":False}),
"c": ("STRING",{"forceInput":False}),
"d": ("STRING",{"forceInput":False}),
"e": ("STRING",{"forceInput":False}),
"f": ("STRING",{"forceInput":False}),
"g": ("STRING",{"forceInput":False}),
}
}
RETURN_TYPES = ListToTupe(ListFillOne('STRING',1))
RETURN_NAMES = ListToTupe(['text'])
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "plain - join text"
OUTPUT_NODE = True
def exec(self, splitchar,a,b,c,d,e,f,g):
# args to list
ls=[a,b,c,d,e,f,g]
# ignore none
ls = list(filter(None, ls))
# join with splitChar
joinedtxt=splitchar.join(ls)
return ListToTupe([joinedtxt])
# feat(core): NodePlainTextRandom - random text in list
class NodePlainTextRandom:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {"default": '', "multiline": False}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"char": ("STRING", {"default": '|', "multiline": False}),
}
}
RETURN_TYPES = ("STRING",'INT',)
RETURN_NAMES = ListToTupe(['text','seed'])
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "plain - random text in list"
def exec(self, text, seed,char='|'):
return TextListRandom(text,seed,char)
@classmethod
def IS_CHANGED(cls, **kwargs):
return float("NaN")
# feat(core): NodePlainTextLoop - get text in list (loop)
class NodePlainTextLoop:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {"default": '', "multiline": False}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"char": ("STRING", {"default": '|', "multiline": False}),
}
}
RETURN_TYPES = ("STRING",'INT','STRING','INT')
RETURN_NAMES = ListToTupe(['text','seed','index','count'])
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "plain - get text in list (loop)"
def exec(self, text, seed,char='|'):
return TextListLoop(text, seed,char)
@classmethod
def IS_CHANGED(cls, **kwargs):
return float("NaN")
# feat(core): NodePlainTextReplace - replace text
class NodePlainTextReplace:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {"default": '', "multiline": False}),
"find": ("STRING", {"default": '', "multiline": False}),
"replace": ("STRING", {"default": '', "multiline": False}),
"action": (["yes",'no'],),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ListToTupe(['text'])
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "plain - replace"
def exec(self, text, find, replace,action='yes'):
# feat(core): do nothing when action no
if 'no' in action:
return ListToTupe([text])
# feat(core): replace text
return ListToTupe([TextReplace(text, find, replace)])
def NumStrToNumInt(str:str="1",roundit:bool=True):
"""
NumStrToNumInt(0.5)
NumStrToNumInt(float(str))
"""
if roundit == True:
return int(round(str))
return int(str)
def NumStrIsRoundToA(a='1', isRoundTo='1'):
"""
NumStrIsRoundToA("0.8","1")
NumStrIsRoundToA("0.4","1")
"""
if NumStrToNumInt(a) == NumStrToNumInt(isRoundTo):
return True
else:
return False
def BoolStr2BoolNum(str:str="true"):
"""
BoolStr2BoolNum("TRUE")
BoolStr2BoolNum("false")
"""
# true,TRUE
if str=="true":
return 1
else:
return 0
# feat(core): NodeNumStrToNum - text to number
class NodeNumStrToNum:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {"default": '', "multiline": False}),
}
}
# RETURN_TYPES = ('NUMBER',)
RETURN_TYPES = ("NUMBER", "FLOAT", "INT")
RETURN_NAMES = ListToTupe(["number", "float", "roundInt"])
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "plain - to number"
def exec(self, text=''):
strInt = int(text)
strFlo = float(text)
strRoundInt = int(round(strFlo))
return ListToTupe([strInt,strFlo,strRoundInt])
# feat(core): NodeNumStrToBool - text to bool
class NodeNumStrToBool:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {"default": '1', "multiline": False}),
"isRoundTo":("STRING", {"default": '1', "multiline": False}),
}
}
RETURN_TYPES = ('STRING',)
RETURN_NAMES = ListToTupe(["boolean"])
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "plain - is round to x"
def exec(self, a='1', isRoundTo='1'):
return ListToTupe([NumStrIsRoundToA(a,isRoundTo)])
# feat(core): NodePlainTextSwitchWithBool - text switch with bool
class NodePlainTextSwitchWithBool:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"a": ("STRING", {"default": '', "multiline": False}),
"b": ("STRING", {"default": '', "multiline": False}),
"boolean": ("BOOL",),
}
}
RETURN_TYPES = ('STRING',)
RETURN_NAMES = ListToTupe(["text"])
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "plain - switch a and b"
def exec(self, a='', b='', boolean:bool=True):
if boolean==True:
return (a, )
else:
return (b, )
# feat(core): NodePlainTextSwitchWithText - text switch with text
class NodePlainTextSwitchWithText:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"a": ("STRING", {"default": '', "multiline": False}),
"b": ("STRING", {"default": '', "multiline": False}),
"text": ("STRING", {"default": '1', "multiline": False}),
"isRoundTo":("STRING", {"default": '1', "multiline": False}),
}
}
RETURN_TYPES = ('STRING',)
RETURN_NAMES = ListToTupe(["text"])
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "plain - switch a and b (isRoundTo)"
def exec(self, a='', b='', text='1',isRoundTo='1'):
if NumStrIsRoundToA(text,isRoundTo):
return (a, )
else:
return (b, )
# class NodeTextShow:
# @classmethod
# def INPUT_TYPES(s):
# return {"required": {
# "text": ("STRING", {"forceInput": True}),
# }}
# # INPUT_IS_LIST = True
# FUNCTION = CURRENT_FUNCTION
# CATEGORY = CURRENT_CATEGORY
# NODE_DESC = "plain - show text"
# OUTPUT_NODE = True
# RETURN_TYPES = ('STRING',)
# # OUTPUT_IS_LIST = (True,)
# def exec(self, text):
# return {"ui": {"text": text}, "result": (text,)}
# refer: pythongosssss /ComfyUI-Custom-Scripts/py/show_text.py
# feat(core): NodeTextShow - show text
class NodeTextShow:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING", {"forceInput": True}),
},
"hidden": {
"unique_id": "UNIQUE_ID",
"extra_pnginfo": "EXTRA_PNGINFO",
},
}
INPUT_IS_LIST = True
RETURN_TYPES = ("STRING",)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "plain - show text"
OUTPUT_NODE = True
OUTPUT_IS_LIST = (True,)
def exec(self, text, unique_id = None, extra_pnginfo=None):
if unique_id and extra_pnginfo and "workflow" in extra_pnginfo[0]:
workflow = extra_pnginfo[0]["workflow"]
node = next((x for x in workflow["nodes"] if str(x["id"]) == unique_id[0]), None)
if node:
node["widgets_values"] = [text]
return {"ui": {"text": text}, "result": (text,)}
# feat(core): NodeTextCalcSize - cacluate width and height
class NodeTextCalcSize:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"w": ("INT", {"default":512, "max": 10000000, "min":0, "step":1}),
"h": ("INT", {"default":512, "max": 10000000, "min":0, "step":1}),
"lockoff": (["false", "w","h"],),
# "rate": (["1:1", "2:3","9:16"],),
"rate": ("STRING", {"default": "1:1", "multiline": False}),
"reverse": (["false", "true"],),
}
}
RETURN_TYPES = ListToTupe(ListFillOne('INT',2))
RETURN_NAMES = ListToTupe(['w','h'])
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "plain - cacluate width and height"
OUTPUT_NODE = True
def exec(self,w=512, h=512,lockoff='false',rate='1:1',reverse='false'):
rw=w
rh=h
# get h or w with lockoff
if lockoff != "false":
ratel=list(map(int,rate.split(":")))
raten=(ratel[0]/ratel[1])
if lockoff == "w":
rh=int(w/raten)
if lockoff == "h":
rw=int(h*raten)
floatl=[rw,rh]
# reverse result of w h
if reverse == "true":
floatl=[rh,rw]
intl=list(map(int,floatl))
return ListToTupe(intl)
+615
View File
@@ -0,0 +1,615 @@
import re
import random
from yors_pano_list_util import ListDelExclude,ListFillOne,ListToTupe,ListShuffle
# use current_category and current_function from con.py in current directory
# from .conf import CURRENT_CATEGORY,CURRENT_FUNCTION
# docs(core): use current_category and current_function in this file
CURRENT_CATEGORY="ymc suite/text"
CURRENT_FUNCTION="exec"
def tags_listify(s:str):
ls = s.split(",")
ls = list(filter(None, ls))
return ls
def tags_strify(l:list[str]):
return ','.join(l)
def tag_trim(s:str):
trimedtag=s
trimedtag=re.sub(r'^ *', '', trimedtag,0)
trimedtag=re.sub(r' *$', '', trimedtag,0)
return trimedtag
def tag_ms2os(s:str):
return re.sub(re.compile(r' {1,}'), ' ', s,0)
def tag_unweight(s:str):
# unweight,trim,
unwdtag=s.replace("(","").replace(")","")
unwdtag=re.sub(r':\d.*', '', unwdtag,0)
unwdtag=tag_trim(unwdtag)
unwdtag=tag_ms2os(unwdtag)
return unwdtag
def tags_unweight(s:str):
ls=tags_listify(s)
nls=[]
for tag in ls:
stdedtag=tag_unweight(tag)
if stdedtag !="":
nls.append(stdedtag)
return ",".join(nls)
def tags_onelineify(s:str):
return re.sub(r'\n', ',', s,0)
def tag_std(s:str):
# trim,ms2os
stdedtag=s
stdedtag=tag_trim(stdedtag)
stdedtag=tag_ms2os(stdedtag)
return stdedtag
def tags_dup(s:str):
nls=tags_listify(s)
nls=list(dict.fromkeys(nls))
return ",".join(nls)
def tags_std(s:str):
ls=tags_listify(s)
nls=[]
for tag in ls:
stdedtag=tag_std(tag)
if stdedtag !="":
nls.append(stdedtag)
return ",".join(nls)
def tags_ignore(s:str,i:str):
ls=tags_listify(s)
li=tags_listify(i)
nls=[]
for tag in ls:
stdedtag=tag_unweight(tag)
if stdedtag !="" and not(stdedtag in li):
nls.append(tag)
return ",".join(nls)
def tags_replace(s:str,i:str,r:str):
ls=tags_listify(s)
li=tags_listify(i)
# lr=tags_listify(r)
nls=[]
for tag in ls:
stdedtag=tag_unweight(tag)
if stdedtag !="":
if stdedtag in li:
# replace found with r
nls.append(r)
else:
nls.append(tag)
return ",".join(nls)
def tags_search(s:str,i:str):
ls=tags_listify(s)
li=tags_listify(i)
nls=[]
for tag in ls:
stdedtag=tag_unweight(tag)
if stdedtag !="" :
for pattern in li:
if pattern in stdedtag:
nls.append(tag)
return ",".join(nls)
def tags_head(s:str,i:str):
"""
move some tags to head
"""
head=tags_listify(i)
tails=tags_listify(tags_ignore(s,i))
return ",".join(head+tails)
def tags_tail(s:str,i:str):
"""
move some tags to head
"""
tails=tags_listify(i)
head=tags_listify(tags_ignore(s,i))
return ",".join(head+tails)
# feat(core): NodePromptAdd - prompt add some text
class NodePromptAdd:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING",{"forceInput":False}),
"toadd": ("STRING",{"forceInput":False}),
"action": (["add2head",'add2tail','del|add2head','del|add2tail','none'],),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ('STRING',)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "prompt - add"
OUTPUT_NODE = True
def exec(self, text='',toadd='',action='add2head'):
# if action =="add2head":
# return list_2_tupe([tags_head(text,prev)])
# if action =="add2tail":
# return list_2_tupe([tags_tail(text,prev)])
if 'none' in action:
return ListToTupe([text])
prev=toadd
if 'del' in action:
prev=tags_ignore(text,toadd)
if 'add2head' in action:
prev=tags_head(text,prev)
if 'add2tail' in action:
prev=tags_tail(text,prev)
return ListToTupe([prev])
# feat(core): NodePromptAdvAdd - prompt add some text (adv)
class NodePromptAdvSearch:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING",{"forceInput":False}),
"search": ("STRING",{"forceInput":False}),
"action": (['none',"del", "move2head",'move2tail',"replace","search"],),
"replace": ("STRING",{"forceInput":False}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ('STRING',)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "prompt - search (adv)"
OUTPUT_NODE = True
def exec(self, text='',search='',action='none',replace=''):
if action == "none":
return ListToTupe([text])
if action == "search":
return ListToTupe([tags_search(text,search)])
searchedtags=tags_search(text,search)
if action =="replace":
return ListToTupe([tags_replace(text,searchedtags,replace)])
if action =="del":
return ListToTupe([tags_ignore(text,searchedtags)])
if action =="move2head":
return ListToTupe([tags_head(text,searchedtags)])
if action =="move2tail":
return ListToTupe([tags_tail(text,searchedtags)])
# feat(core): NodePromptSearch - prompt search some text
class NodePromptSearch:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING",{"forceInput":False}),
"search": ("STRING",{"forceInput":False}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ('STRING',)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "prompt - search"
OUTPUT_NODE = True
def exec(self, text='',search=''):
return ListToTupe([tags_search(text,search)])
# feat(core): NodePromptDup - prompt dup some text
class NodePromptDup:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING",{"forceInput":False}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ('STRING',)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "prompt - dup"
OUTPUT_NODE = True
def exec(self, text):
return ListToTupe([tags_dup(text)])
# feat(core): NodePromptAdvDup - prompt dup some text (adv)
class NodePromptAdvDup:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING",{"forceInput":False}),
# "mode": (["alline", "in-each-line"],),
}
}
RETURN_TYPES = ("STRING","STRING",)
RETURN_NAMES = ('STRING',"STRING",)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "prompt - dup (adv)"
OUTPUT_NODE = True
def exec(self, text):
a=''
res=[]
textl=text.split("\n")
for line in textl:
res.append(tags_dup(line))
a='\n'.join(res)
tags_dup(tags_onelineify(text))
return ListToTupe([tags_dup(tags_onelineify(a)),a])
# feat(core): NodePromptShuffle - prompt shuffle some text
class NodePromptShuffle:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING",{"forceInput":False}),
"keep_n_token": ("STRING", {"default": "0", "multiline": False}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ('STRING',)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "prompt - shuffle"
OUTPUT_NODE = True
def exec(self, text='',keep_n_token='0',seed=0):
random.seed(seed)
textl=tags_listify(tags_onelineify(text))
el=ListShuffle(textl,int(keep_n_token))
return ListToTupe([','.join(el)])
# feat(core): NodePromptDel - prompt del some text
class NodePromptDel:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"search": ("STRING",{"forceInput":False}),
"todel": ("STRING",{"forceInput":False}),
"action": (["yes",'no'],),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ('STRING',)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "prompt - del"
OUTPUT_NODE = True
def exec(self, search='',todel='',action='yes'):
# https://www.geeksforgeeks.org/python-remove-empty-strings-from-list-of-strings/
if 'no' in action:
return ListToTupe([search])
ca=tags_ignore(search,tags_unweight(todel))
return ListToTupe([ca])
# feat(core): NodePromptStd - prompt std
class NodePromptStd:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING",{"forceInput":False}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ('STRING',)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "prompt - std"
OUTPUT_NODE = True
def exec(self, text):
return ListToTupe([tags_std(text)])
# feat(core): NodePromptInput - prompt input
class NodePromptInput:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING",{"default": "","multiline":True}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ('text',)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "prompt - input"
OUTPUT_NODE = True
def exec(self, text=''):
return ListToTupe([tags_std(text)])
# feat(core): NodePromptUnweight - prompt unweight
class NodePromptUnweight:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING",{"forceInput":False}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ('STRING',)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "prompt - unweight"
OUTPUT_NODE = True
def exec(self, text):
return ListToTupe([tags_unweight(text)])
# feat(core): NodePromptJoin - prompt join
class NodePromptJoin:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"a": ("STRING",{"forceInput":False}),
"b": ("STRING",{"forceInput":False}),
"c": ("STRING",{"forceInput":False}),
"d": ("STRING",{"forceInput":False}),
"e": ("STRING",{"forceInput":False}),
"f": ("STRING",{"forceInput":False}),
"g": ("STRING",{"forceInput":False}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ('STRING',)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "prompt - join"
OUTPUT_NODE = True
def exec(self, a,b,c,d,e,f,g):
vl=[a,b,c,d,e,f,g]
vs = (",").join(vl)
vs=tags_std(vs)
# vs = vs.replace(" ", " ").replace(" ,", ",").replace(", ", ",").replace(",,", ",").replace(",,", ",")
return {"ui": {"text": (vs,)}, "result": (vs,)}
class AnyType(str):
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
def __ne__(self, __value: object) -> bool:
return False
any_type = AnyType("*")
default_prompt1='''Swing
Slide
Climbing frame
Sandbox
See-saw
Merry-go-round
Jungle gym
Trampoline
Monkey bars
Rocking horse
Playhouse
Hopscotch
Balance beam
Spring rider
Water play area
Ball pit
Tunnel
Zip line
Basketball hoop
Bicycle rack
Spinner
Climbing wall
Rope ladder
Tetherball
Flying fox
Swinging bridge
Spiral slide
Water sprinkler
Pedal go-kart
Miniature golf course
'''
default_prompt1="\n".join([p.strip() for p in default_prompt1.split('\n') if p.strip()!=''])
# refer comfyui-mixlab-nodes
# feat(core): NodePromptRandom - prompt random
class NodePromptRandom:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"max_count": ("INT", {"default": 9, "min": 1, "max": 1000}),
# "image_field": ("IMAGE",),
"mutable_prompt": ("STRING",
{
"multiline": True,
"default": default_prompt1
}),
"immutable_prompt": ("STRING",
{
"multiline": True,
"default": 'sticker, Cartoon, ``'
}),
"random_sample": (["enable", "disable"],),
# "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "step": 1}),
},
"optional":{
"seed": (any_type, {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
}
}
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "prompt - random"
RETURN_TYPES = ("STRING",)
# CATEGORY = "♾️Mixlab/Prompt"
OUTPUT_IS_LIST = (True,)
OUTPUT_NODE = True
def exec(self,max_count,mutable_prompt,immutable_prompt,random_sample,seed=0):
# Split the text into an array of words
words1 = mutable_prompt.split("\n")
# Split the text into an array of words
words2 = immutable_prompt.split("\n")
# progress bar
# pbar = comfy.utils.ProgressBar(len(words1)*len(words2))
# Select a random word from the array
# random_word = random.choice(words)
prompts=[]
for w1 in words1:
w1=w1.strip()
for w2 in words2:
w2=w2.strip()
if '``' not in w2:
if w2=="":
w2='``'
else:
w2=w2+',``'
if w1!='' and w2!='':
prompts.append(w2.replace('``', w1))
# pbar.update(1)
if len(prompts)==0:
prompts.append(immutable_prompt)
if random_sample=='enable':
# random - get x-count element in array
prompts = random.sample(prompts, min(max_count,len(prompts)))
else:
prompts = prompts[:min(max_count,len(prompts))]
prompts= [elem.strip() for elem in prompts if elem.strip()]
# return (new_prompt)
return {"ui": {"prompts": prompts}, "result": (prompts,)}
def addWeight(text, weight=1):
if weight == 1:
return text
else:
return f"({text}:{round(weight,3)})"
# feat(core): NodePromptWeight - prompt add weight
class NodePromptWeight:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"prompt_keyword": ("STRING",
{
"multiline": False,
"default": '',
"dynamicPrompts": False
}),
"weight":("FLOAT", {"default": 1, "min": -3,"max": 3,"step": 0.01,"display": "slider"}),
# "min_value":("FLOAT", {
# "default": -2,
# "min": -10,
# "max": 0xffffffffffffffff,
# "step": 0.01,
# "display": "number"
# }),
# "max_value":("FLOAT", {
# "default": 2,
# "min": -10,
# "max": 0xffffffffffffffff,
# "step": 0.01,
# "display": "number"
# }),
}
}
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "prompt - add weight"
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("prompt",)
# FUNCTION = "run"
# CATEGORY = "♾️Mixlab/Prompt"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
OUTPUT_NODE = False
def exec(self,prompt_keyword,weight):
# if weight < min_value:
# weight= min_value
# elif weight > max_value:
# weight= max_value
p=addWeight(prompt_keyword,weight)
return (p,)
+160
View File
@@ -0,0 +1,160 @@
import glob
import hashlib
# import json
# import numpy as np
# from numba import jit
import os
from pathlib import Path
from yors_pano_list_util import ListDelExclude,ListFillOne,ListToTupe,ListShuffle
CURRENT_CATEGORY="ymc suite/text"
CURRENT_FUNCTION="exec"
def pyioReadDirectory(dir='',pattern='*.txt'):
"""
pyio - get path at dir and pattern
"""
res=[]
for filename in glob.glob(os.path.join(dir,pattern)):
res.append(filename)
return res
def pyioReadAsText(loc):
"""
pyio - read text at location
"""
res=''
with open(loc) as f:
res = f.read()
f.close()
return res
def pyioGetFilesInDir(input_dir):
"""
files = pyioGetFilesInDir(input_dir)
"""
return [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
def pyioParseFilename(filename:str):
"""
pyioParseFilename('config.txt')
"""
name, suffix = os.path.splitext(filename)
return (name, suffix)
def pyioGetFilename(filename:str):
"""
pyioGetFilename('config.txt')
"""
name, suffix = os.path.splitext(filename)
return name
def pyioPlibParseFilename(filename:str):
"""
pyioPlibParseFilename('config.txt')
"""
p=Path(filename)
return (p.suffix, p.stem)
def StrmFromList(txtl:list[str],eof:str='\n'):
"""
strm - from text list
StrmFromList(fileAbsPathList)
"""
return eof.join(txtl)
# feat(core): NodePyioFilenameListDetect - pyio - detect filename list
class NodePyioFilenameListDetect:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"path": ("STRING", {"default": "", "multiline": False}),
"pattern": ("STRING", {"default": "*.txt", "multiline": False}),
# "mode": ("STRING", {"default": "filename", "multiline": False}),
"mode": (["filename", "abspath","filename-no-ext"],),
}
}
RETURN_TYPES = ("STRING",'INT')
RETURN_NAMES = ('STRING','count')
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "pyio - detect filename list"
OUTPUT_NODE = True
def exec(self, path='',pattern='*.txt',mode='filename'):
fileAbsPathList=pyioReadDirectory(path,pattern)
resl = fileAbsPathList
if mode=='filename':
fileNameList=list(map(os.path.basename,fileAbsPathList))
resl=fileNameList
if mode=='filename-no-ext':
filenameNoExtList=list(map(pyioGetFilename,fileNameList))
resl=filenameNoExtList
# resl='\n'.join(resl)
# fileAbsPathListStrm = StrmFromList(fileAbsPathList)
# fileNameListStrm = StrmFromList(fileNameList)
# filenmeWithoutSuffixList=StrmFromList(filenameNoExtList)
return ListToTupe([StrmFromList(resl),len(resl)])
# feat(core): NodePyioFilenameListOutmDetect - pyio - detect filename list (outm)
class NodePyioFilenameListOutmDetect:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"path": ("STRING", {"default": "", "multiline": False}),
"pattern": ("STRING", {"default": "*.txt", "multiline": False}),
}
}
RETURN_TYPES = ("STRING","STRING","STRING","INT")
RETURN_NAMES = ('absPath','name','nameNoExt','count')
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "pyio - detect filename list (outm)"
OUTPUT_NODE = True
def exec(self, path='',pattern='*.txt'):
absPathList=pyioReadDirectory(path,pattern)
nameList=list(map(os.path.basename,absPathList))
nameNoExtList=list(map(pyioGetFilename,nameList))
return ListToTupe([StrmFromList(absPathList),StrmFromList(nameList),StrmFromList(nameNoExtList),len(absPathList)])
# feat(core): IoUtilFileListGetText - io - get file list and read as text
class IoUtilFileListGetText:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"path": ("STRING", {"default": "", "multiline": False}),
"pattern": ("STRING", {"default": "*.txt", "multiline": False}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ('STRING',)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
OUTPUT_NODE = True
def exec(self, path='',pattern='*.txt'):
plist=pyioReadDirectory(path,pattern)
text=[]
for loc in plist:
text.append(pyioReadAsText(loc))
# to multi line
text='\n'.join(text)
return ListToTupe([text])
+149
View File
@@ -0,0 +1,149 @@
from yors_pano_list_util import ListDelExclude,ListFillOne,ListToTupe,ListShuffle
# CURRENT_CATEGORY="Ymc Suite/region-util"
CURRENT_CATEGORY="ymc suite/text"
CURRENT_FUNCTION="exec"
def region_strify(l:list):
tl=list(map(str,l))
tl=list(filter(None, tl))
return ','.join(tl)
def region_listfy(s:str):
tl=s.split(",")
tl=list(filter(None, tl))
return tl
def region_intify(l:list):
tl=list(map(int,l))
return tl
def region_get_location_from_center_size(cx=128, cy=128, w=256, h=256):
left=cx-w/2
top=cy-h/2
right=cx+w/2
bottom=cy+h/2
floatl=[left,top,right,bottom,cx,cy,w,h]
intl=list(map(int,floatl))
return intl
def region_burn_corp_location_as_square(w=256, h=256):
c=0
if w<h:
c=1
left=-1*c*(w-h)/2
right=left
c=0
if w>=h:
c=1
top=c*(w-h)/2
bottom=top
floatl=[left,top,right,bottom]
intl=list(map(int,floatl))
return ListToTupe(intl)
def region_burn_corp_location_from_lt_and_size(left=0, top=0, w=256, h=256):
cx=left+w/2
cy=top+h/2
right=cx+w/2
bottom=cy+h/2
floatl=[left,top,right,bottom,cx,cy,w,h]
intl=list(map(int,floatl))
return ListToTupe(intl)
REGION_GET_CROP_LOCATION_BY_CENTER_RETURN_NAMES=['left','top','right','bottom','cx','cy','w','h']
REGION_GET_CROP_LOCATION_BY_CENTER_RETURN_NAMES_COUNT=len(REGION_GET_CROP_LOCATION_BY_CENTER_RETURN_NAMES)
# feat(core): NodeRegionCropLocationBurnNumz - num - burn crop location (center & size)
class NodeRegionCropLocationBurnNumz:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"cx": ("INT", {"default":128, "max": 10000000, "min":0, "step":1}),
"cy": ("INT", {"default":128, "max": 10000000, "min":0, "step":1}),
"w": ("INT", {"default":256, "max": 10000000, "min":0, "step":1}),
"h": ("INT", {"default":256, "max": 10000000, "min":0, "step":1}),
}
}
RETURN_TYPES = ListToTupe(ListFillOne('INT',REGION_GET_CROP_LOCATION_BY_CENTER_RETURN_NAMES_COUNT))
RETURN_NAMES = ListToTupe(REGION_GET_CROP_LOCATION_BY_CENTER_RETURN_NAMES)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "num - burn crop location (center & size)"
OUTPUT_NODE = True
def exec(self, cx=128, cy=128, w=256, h=256):
intl=region_get_location_from_center_size(cx,cy,w,h)
return ListToTupe(intl)
# feat(core): NodeRegionCropLocationBurnTxtz - txt - burn crop location (center & size)
class NodeRegionCropLocationBurnTxtz:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"cxcy": ("STRING", {"default": '128,128', "multiline": False}),
"size": ("STRING", {"default": '256,256', "multiline": False}),
}
}
RETURN_TYPES = ListToTupe(ListFillOne('INT',REGION_GET_CROP_LOCATION_BY_CENTER_RETURN_NAMES_COUNT))
RETURN_NAMES = ListToTupe(REGION_GET_CROP_LOCATION_BY_CENTER_RETURN_NAMES)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "txt - burn crop location (cxcy & size)"
OUTPUT_NODE = True
def exec(self, cxcy='128,128',size='256,256'):
cx,cy=region_intify(region_listfy(cxcy))
w,h=region_intify(region_listfy(size))
intl=region_get_location_from_center_size(cx,cy,w,h)
return ListToTupe(intl)
REGION_GET_CROP_LOCATION_BY_LT_RETURN_NAMES=['left','top','right','bottom','cx','cy','w','h']
REGION_GET_CROP_LOCATION_BY_LT_RETURN_NAMES_COUNT=len(REGION_GET_CROP_LOCATION_BY_LT_RETURN_NAMES)
# feat(core): NodeRegionCropLocationBurnLtwh - int - burn crop location (lt & size)
class NodeRegionCropLocationBurnLtwh:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"left": ("INT", {"default":0, "max": 10000000, "min":0, "step":1}),
"top": ("INT", {"default":0, "max": 10000000, "min":0, "step":1}),
"w": ("INT", {"default":256, "max": 10000000, "min":0, "step":1}),
"h": ("INT", {"default":256, "max": 10000000, "min":0, "step":1}),
}
}
RETURN_TYPES = ListToTupe(ListFillOne('INT',REGION_GET_CROP_LOCATION_BY_LT_RETURN_NAMES_COUNT))
RETURN_NAMES = ListToTupe(REGION_GET_CROP_LOCATION_BY_LT_RETURN_NAMES)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "int - burn crop location (lt & size)"
OUTPUT_NODE = True
def exec(self, left=0, top=0, w=256, h=256):
return region_burn_corp_location_from_lt_and_size(left,top,w,h)
REGION_LOCATION_RETURN_NAMES=['left','top','right','bottom']
# feat(core): NodeRegionPaddingSquareLocationBurn - int - burn padding location as square (size)
class NodeRegionPaddingSquareLocationBurn:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"w": ("INT", {"default":256, "max": 10000000, "min":0, "step":1}),
"h": ("INT", {"default":256, "max": 10000000, "min":0, "step":1}),
}
}
RETURN_TYPES = ListToTupe(ListFillOne('INT',len(REGION_LOCATION_RETURN_NAMES)))
RETURN_NAMES = ListToTupe(REGION_LOCATION_RETURN_NAMES)
FUNCTION = CURRENT_FUNCTION
CATEGORY = CURRENT_CATEGORY
NODE_DESC = "int - burn padding location as square (size)"
OUTPUT_NODE = True
def exec(self, w=256, h=256):
return region_burn_corp_location_as_square(w,h)
-2127
View File
File diff suppressed because it is too large Load Diff
+3 -19
View File
@@ -1,20 +1,4 @@
yors_comfyui_node_setup
cmake
fairscale>=0.4.4
git+https://github.com/WASasquatch/img2texture.git
gitpython
imageio
joblib
matplotlib
numba
numpy<1.24>=1.18
opencv-python-headless[ffmpeg]<=4.7.0.72
pilgram
git+https://github.com/WASasquatch/ffmpy.git
rembg
scikit-image==0.20.0
scikit-learn
scipy
timm>=0.4.12
tqdm
transformers==4.26.1
yors_pano_ansi_color
yors_pano_zero_field
yors_pano_list_util