This commit is contained in:
justumen
2024-09-14 09:29:13 +02:00
parent c3a573565a
commit 478ad073c4
11 changed files with 85 additions and 24 deletions

View File

@@ -1,4 +1,4 @@
# 🔗 Comfyui : Bjornulf_custom_nodes v0.25 🔗 # 🔗 Comfyui : Bjornulf_custom_nodes v0.26 🔗
# ❤️ Coffee : ☕☕☕☕☕ 5/5 # ❤️ Coffee : ☕☕☕☕☕ 5/5
@@ -73,6 +73,7 @@ wget --content-disposition -P /workspace/ComfyUI/models/checkpoints "https://civ
- **v0.23**: Add a new node: Pause, resume or stop workflow. - **v0.23**: Add a new node: Pause, resume or stop workflow.
- **v0.24**: Add a new node: Pause, select input, pick one. - **v0.24**: Add a new node: Pause, select input, pick one.
- **v0.25**: Two new nodes: Loop Images and Random image. - **v0.25**: Two new nodes: Loop Images and Random image.
- **v0.26**: New node : Loop write Text. Also increase nb of inputs allowed for most nodes. (+ update some breaking changes)
# 📝 Nodes descriptions # 📝 Nodes descriptions
@@ -111,6 +112,7 @@ General-purpose loop node.
**Description:** **Description:**
Cycle through a list of text inputs. Cycle through a list of text inputs.
## 7 - ♻ Loop Integer ## 7 - ♻ Loop Integer
![Loop Integer](screenshots/loop_integer.png) ![Loop Integer](screenshots/loop_integer.png)
![Loop Int + Show Text](screenshots/loop_int+show_text.png) ![Loop Int + Show Text](screenshots/loop_int+show_text.png)
@@ -121,6 +123,10 @@ Iterate through a range of integer values, good for `steps` in ksampler, etc...
❗ Don't forget that you can convert ksampler widgets to input by right-clicking the ksampler node : ❗ Don't forget that you can convert ksampler widgets to input by right-clicking the ksampler node :
![Widget to Input](screenshots/widget-to-input.png) ![Widget to Input](screenshots/widget-to-input.png)
Here is an example of usage with ksampler (Notice that it isn't optimized, but good enough for testing) :
![Widget to Input](screenshots/example_loop_integer.png)
## 8 - ♻ Loop Float ## 8 - ♻ Loop Float
![Loop Float](screenshots/loop_float.png) ![Loop Float](screenshots/loop_float.png)
![Loop Float + Show Text](screenshots/loop_float+show_text.png) ![Loop Float + Show Text](screenshots/loop_float+show_text.png)
@@ -369,4 +375,12 @@ Just take a random image from a list of images.
**Description:** **Description:**
Loop over a list of images. Loop over a list of images.
Usage example : You have a list of images, and you want to apply the same process to all of them. Usage example : You have a list of images, and you want to apply the same process to all of them.
Above is an example of the loop images node sending them to an Ipadapter style transfer workflow. (Same seed of course.) Above is an example of the loop images node sending them to an Ipadapter style transfer workflow. (Same seed of course.)
### 39 - ♻ Loop (✒ Write Text)
![pick input](screenshots/loop_write_text.png)
**Description:**
If you need a quick loop but you don't want something too complex with a loop node, you can use this combined write text + loop.
It will take the same special syntax as the write text node `{blue|red}`, but it will loop over ALL the possibilities instead of taking one at random.

View File

@@ -44,6 +44,8 @@ from .pause_resume_stop import PauseResume
from .pick_input import PickInput from .pick_input import PickInput
from .loop_images import LoopImages from .loop_images import LoopImages
from .random_image import RandomImage from .random_image import RandomImage
from .loop_write_text import LoopWriteText
# from .random_checkpoint import RandomCheckpoint
# from .pass_preview_image import PassPreviewImage # from .pass_preview_image import PassPreviewImage
# from .check_black_image import CheckBlackImage # from .check_black_image import CheckBlackImage
@@ -54,6 +56,8 @@ from .random_image import RandomImage
NODE_CLASS_MAPPINGS = { NODE_CLASS_MAPPINGS = {
# "Bjornulf_CustomStringType": CustomStringType, # "Bjornulf_CustomStringType": CustomStringType,
"Bjornulf_ollamaLoader": ollamaLoader, "Bjornulf_ollamaLoader": ollamaLoader,
# "Bjoenulf_RandomCheckpoint": RandomCheckpoint,
"Bjornulf_LoopWriteText": LoopWriteText,
"Bjornulf_LoopImages": LoopImages, "Bjornulf_LoopImages": LoopImages,
"Bjornulf_RandomImage": RandomImage, "Bjornulf_RandomImage": RandomImage,
# "Bjornulf_PassPreviewImage": PassPreviewImage, # "Bjornulf_PassPreviewImage": PassPreviewImage,
@@ -107,6 +111,8 @@ NODE_CLASS_MAPPINGS = {
NODE_DISPLAY_NAME_MAPPINGS = { NODE_DISPLAY_NAME_MAPPINGS = {
# "Bjornulf_CustomStringType": "!!! CUSTOM STRING TYPE !!!", # "Bjornulf_CustomStringType": "!!! CUSTOM STRING TYPE !!!",
"Bjornulf_ollamaLoader": "🦙 Ollama (Description)", "Bjornulf_ollamaLoader": "🦙 Ollama (Description)",
# "Bjoenulf_RandomCheckpoint": "🎲 Random Checkpoint",
"Bjornulf_LoopWriteText": "♻ Loop (✒ Write Text)",
"Bjornulf_LoopImages": "♻🖼 Loop (Images)", "Bjornulf_LoopImages": "♻🖼 Loop (Images)",
"Bjornulf_RandomImage": "🎲🖼 Random Image", "Bjornulf_RandomImage": "🎲🖼 Random Image",
# "Bjornulf_PassPreviewImage": "🖼⮕ Pass Preview Image", # "Bjornulf_PassPreviewImage": "🖼⮕ Pass Preview Image",

View File

@@ -3,13 +3,13 @@ class CombineTexts:
def INPUT_TYPES(cls): def INPUT_TYPES(cls):
return { return {
"required": { "required": {
"number_of_inputs": ("INT", {"default": 2, "min": 2, "max": 10, "step": 1}), "number_of_inputs": ("INT", {"default": 2, "min": 2, "max": 50, "step": 1}),
"delimiter": (["newline", "comma", "space", "slash"], {"default": "newline"}), "delimiter": (["newline", "comma", "space", "slash"], {"default": "newline"}),
"text_1": ("STRING", {"forceInput": True}), "text_1": ("STRING", {"forceInput": True}),
"text_2": ("STRING", {"forceInput": True}), "text_2": ("STRING", {"forceInput": True}),
}, },
"hidden": { "hidden": {
**{f"text_{i}": ("STRING", {"forceInput": True}) for i in range(3, 11)} **{f"text_{i}": ("STRING", {"forceInput": True}) for i in range(3, 51)}
} }
} }

View File

@@ -3,17 +3,17 @@ class CombineTextsByLines:
def INPUT_TYPES(cls): def INPUT_TYPES(cls):
return { return {
"required": { "required": {
"number_of_inputs": ("INT", {"default": 2, "min": 1, "max": 10, "step": 1}), "number_of_inputs": ("INT", {"default": 2, "min": 1, "max": 50, "step": 1}),
"number_of_lines": ("INT", {"default": 3, "min": 1, "max": 20, "step": 1}), "number_of_lines": ("INT", {"default": 3, "min": 1, "max": 50, "step": 1}),
} }
} }
RETURN_TYPES = tuple(["STRING"] * 20) # Maximum 20 lines RETURN_TYPES = tuple(["STRING"] * 50) # Maximum 50 lines
RETURN_NAMES = tuple([f"line_{i+1}" for i in range(20)]) RETURN_NAMES = tuple([f"line_{i+1}" for i in range(50)])
FUNCTION = "extract_lines" FUNCTION = "extract_lines"
OUTPUT_NODE = True OUTPUT_NODE = True
CATEGORY = "text" CATEGORY = "text"
OUTPUT_IS_LIST = tuple([True] * 20) # Indicate that all outputs are lists OUTPUT_IS_LIST = tuple([True] * 50) # Indicate that all outputs are lists
def extract_lines(self, number_of_inputs, number_of_lines, **kwargs): def extract_lines(self, number_of_inputs, number_of_lines, **kwargs):
grouped_lines = [[] for _ in range(number_of_lines)] grouped_lines = [[] for _ in range(number_of_lines)]
@@ -31,8 +31,8 @@ class CombineTextsByLines:
# Instead of joining the lines, keep them as a list # Instead of joining the lines, keep them as a list
outputs.append(group) outputs.append(group)
# Pad the output to always return 20 items # Pad the output to always return 50 items
outputs.extend([[] for _ in range(20 - len(outputs))]) outputs.extend([[] for _ in range(50 - len(outputs))])
return tuple(outputs) return tuple(outputs)
@@ -42,8 +42,8 @@ class CombineTextsByLines:
@classmethod @classmethod
def VALIDATE_INPUTS(cls, number_of_inputs, number_of_lines, **kwargs): def VALIDATE_INPUTS(cls, number_of_inputs, number_of_lines, **kwargs):
if number_of_lines < 1 or number_of_lines > 20: if number_of_lines < 1 or number_of_lines > 50:
return "Number of lines must be between 1 and 20" return "Number of lines must be between 1 and 50"
if number_of_inputs < 1 or number_of_inputs > 10: if number_of_inputs < 1 or number_of_inputs > 50:
return "Number of inputs must be between 1 and 10" return "Number of inputs must be between 1 and 50"
return True return True

View File

@@ -3,7 +3,7 @@ class LoopImages:
def INPUT_TYPES(cls): def INPUT_TYPES(cls):
return { return {
"required": { "required": {
"number_of_images": ("INT", {"default": 2, "min": 1, "max": 10, "step": 1}), "number_of_images": ("INT", {"default": 2, "min": 1, "max": 30, "step": 1}),
}, },
} }

View File

@@ -3,12 +3,12 @@ class LoopTexts:
def INPUT_TYPES(cls): def INPUT_TYPES(cls):
return { return {
"required": { "required": {
"number_of_inputs": ("INT", {"default": 2, "min": 2, "max": 10, "step": 1}), "number_of_inputs": ("INT", {"default": 2, "min": 2, "max": 50, "step": 1}),
"text_1": ("STRING", {"forceInput": "True"}), # "text_1": ("STRING", {"forceInput": "True"}),
"text_2": ("STRING", {"forceInput": "True"}), # "text_2": ("STRING", {"forceInput": "True"}),
}, },
"hidden": { "hidden": {
**{f"text_{i}": ("STRING", {"forceInput": "True"}) for i in range(3, 11)} **{f"text_{i}": ("STRING", {"forceInput": "True"}) for i in range(1, 51)}
} }
} }

41
loop_write_text.py Normal file
View File

@@ -0,0 +1,41 @@
import re
import itertools
class LoopWriteText:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING", {"multiline": True}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("texts",)
FUNCTION = "loop_write_text"
OUTPUT_NODE = True
OUTPUT_IS_LIST = (True,)
CATEGORY = "Bjornulf"
def loop_write_text(self, text):
pattern = r'\{([^}]+)\}'
matches = re.findall(pattern, text)
if not matches:
return ([text],)
options_list = [opt.split('|') for opt in matches]
combinations = list(itertools.product(*options_list))
results = []
for combo in combinations:
result = text
for i, match in enumerate(matches):
result = result.replace(f"{{{match}}}", combo[i], 1)
results.append(result)
return (results,)
@classmethod
def IS_CHANGED(s, text):
return float("nan") # Always re-execute to ensure consistency

View File

@@ -1,7 +1,7 @@
[project] [project]
name = "bjornulf_custom_nodes" name = "bjornulf_custom_nodes"
description = "Nodes: Ollama, Text to Speech, Combine Texts, Random Texts, Save image for Bjornulf LobeChat, Text with random Seed, Random line from input, Combine images, Image to grayscale (black & white), Remove image Transparency (alpha), Resize Image, ..." description = "Nodes: Ollama, Text to Speech, Combine Texts, Random Texts, Save image for Bjornulf LobeChat, Text with random Seed, Random line from input, Combine images, Image to grayscale (black & white), Remove image Transparency (alpha), Resize Image, ..."
version = "0.25" version = "0.26"
license = {file = "LICENSE"} license = {file = "LICENSE"}
[project.urls] [project.urls]

View File

@@ -7,12 +7,12 @@ class RandomTexts:
"required": { "required": {
"number_of_inputs": ("INT", {"default": 2, "min": 2, "max": 30, "step": 1}), "number_of_inputs": ("INT", {"default": 2, "min": 2, "max": 30, "step": 1}),
"number_of_random": ("INT", {"default": 1, "min": 1, "max": 30, "step": 1}), "number_of_random": ("INT", {"default": 1, "min": 1, "max": 30, "step": 1}),
"text_1": ("STRING", {"forceInput": "True"}), # "text_1": ("STRING", {"forceInput": "True"}),
"text_2": ("STRING", {"forceInput": "True"}), # "text_2": ("STRING", {"forceInput": "True"}),
"seed": ("INT", {"default": "1"}), #Used with control_after_generate, "seed": ("INT", {"default": "1"}), #Used with control_after_generate,
}, },
"hidden": { "hidden": {
**{f"text_{i}": ("STRING", {"forceInput": "True"}) for i in range(3, 31)} **{f"text_{i}": ("STRING", {"forceInput": "True"}) for i in range(1, 31)}
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB