mirror of
https://github.com/justUmen/Bjornulf_custom_nodes.git
synced 2026-03-26 06:45:44 -03:00
0.34
This commit is contained in:
36
README.md
36
README.md
@@ -1,4 +1,4 @@
|
|||||||
# 🔗 Comfyui : Bjornulf_custom_nodes v0.32 🔗
|
# 🔗 Comfyui : Bjornulf_custom_nodes v0.34 🔗
|
||||||
|
|
||||||
# ❤️ Coffee : ☕☕☕☕☕ 5/5
|
# ❤️ Coffee : ☕☕☕☕☕ 5/5
|
||||||
|
|
||||||
@@ -81,6 +81,7 @@ wget --content-disposition -P /workspace/ComfyUI/models/checkpoints "https://civ
|
|||||||
- **v0.31**: ❗Sorry, Breaking changes for Write/Show text nodes, cleaner system : 1 simple write text and the other is 1 advanced with console and special syntax. Also Show can now manage INT, FLOAT, TEXT.
|
- **v0.31**: ❗Sorry, Breaking changes for Write/Show text nodes, cleaner system : 1 simple write text and the other is 1 advanced with console and special syntax. Also Show can now manage INT, FLOAT, TEXT.
|
||||||
- **v0.32**: Quick rename to avoid breaking loop_text node.
|
- **v0.32**: Quick rename to avoid breaking loop_text node.
|
||||||
- **v0.33**: Control random on paused nodes, fix pydub sound bug permissions on Windows.
|
- **v0.33**: Control random on paused nodes, fix pydub sound bug permissions on Windows.
|
||||||
|
- **v0.34**: Two new nodes : Load Images from output folder and Select an Image, Pick.
|
||||||
|
|
||||||
# 📝 Nodes descriptions
|
# 📝 Nodes descriptions
|
||||||
|
|
||||||
@@ -441,3 +442,36 @@ Just take a trio at random from a load checkpoint node.
|
|||||||
|
|
||||||
**Description:**
|
**Description:**
|
||||||
Loop over all the trios from several checkpoint node.
|
Loop over all the trios from several checkpoint node.
|
||||||
|
|
||||||
|
### 42 - 📂🖼 Load Images from output folder
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Description:**
|
||||||
|
Quickly select all images from a folder inside the output folder. (Not recursively.)
|
||||||
|
So... As you can see from the screenshot the images are split based on their resolution.
|
||||||
|
It is not a choice I made, it is something that is part of the comfyui environment.
|
||||||
|
It's also not possible to edit dynamically the number of outputs, so I just picked a number : 4.
|
||||||
|
The node will separate the images based on their resolution, so with this node you can have 4 different resolutions per folder. (If you have more than that, maybe you should have another folder...)
|
||||||
|
To avoid error or crash if you have less than 4 resolutions in a folder, the node will just output white tensors. (white square image.)
|
||||||
|
So this node is a little hacky for now, but i can select my different characters in less than a second.
|
||||||
|
If you want to know how i personnaly save my images for a specific character, here is part of my workflow (Notice that i personnaly use / for folders because I'm on linux) :
|
||||||
|

|
||||||
|
In this example I put "character/" as a string and then combine with "nothing". But it's the same if you do "character" and then combine with "/". (I just like having a / at the end of my folder's name...)
|
||||||
|
|
||||||
|
If you are satisfied with this logic, you can then select all these nodes, right click and `Convert to Group Node`, you can then have you own customized "save character node" :
|
||||||
|

|
||||||
|
|
||||||
|
Here is another example of the same thing but excluding the save folder node :
|
||||||
|

|
||||||
|
|
||||||
|
### 43 - 🖼🔍 Select an Image, Pick
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Description:**
|
||||||
|
Select an image from a list of images.
|
||||||
|
Useful in combination with my Load images from folder and preview image nodes.
|
||||||
|
|
||||||
|
You can also of course make a group node, like this one, which is the same as the screenshot above :
|
||||||
|

|
||||||
13
__init__.py
13
__init__.py
@@ -47,8 +47,9 @@ from .random_image import RandomImage
|
|||||||
from .loop_model_clip_vae import LoopModelClipVae
|
from .loop_model_clip_vae import LoopModelClipVae
|
||||||
from .write_text_advanced import WriteTextAdvanced
|
from .write_text_advanced import WriteTextAdvanced
|
||||||
from .loop_write_text import LoopWriteText
|
from .loop_write_text import LoopWriteText
|
||||||
# from .load_images_from_folder import LoadImagesFromSelectedFolder
|
from .load_images_from_folder import LoadImagesFromSelectedFolder
|
||||||
# from .show import ShowWhatever
|
# from .show import ShowWhatever
|
||||||
|
from .select_image_from_list import SelectImageFromList
|
||||||
|
|
||||||
# from .pass_preview_image import PassPreviewImage
|
# from .pass_preview_image import PassPreviewImage
|
||||||
# from .check_black_image import CheckBlackImage
|
# from .check_black_image import CheckBlackImage
|
||||||
@@ -59,8 +60,9 @@ from .loop_write_text import LoopWriteText
|
|||||||
NODE_CLASS_MAPPINGS = {
|
NODE_CLASS_MAPPINGS = {
|
||||||
# "Bjornulf_CustomStringType": CustomStringType,
|
# "Bjornulf_CustomStringType": CustomStringType,
|
||||||
"Bjornulf_ollamaLoader": ollamaLoader,
|
"Bjornulf_ollamaLoader": ollamaLoader,
|
||||||
|
"Bjornulf_SelectImageFromList": SelectImageFromList,
|
||||||
"Bjornulf_WriteText": WriteText,
|
"Bjornulf_WriteText": WriteText,
|
||||||
# "Bjornulf_LoadImagesFromSelectedFolder": LoadImagesFromSelectedFolder,
|
"Bjornulf_LoadImagesFromSelectedFolder": LoadImagesFromSelectedFolder,
|
||||||
# "Bjornulf_ShowWhatever": ShowWhatever,
|
# "Bjornulf_ShowWhatever": ShowWhatever,
|
||||||
"Bjornulf_LoopModelClipVae": LoopModelClipVae,
|
"Bjornulf_LoopModelClipVae": LoopModelClipVae,
|
||||||
# "Bjoenulf_RandomCheckpoint": RandomCheckpoint,
|
# "Bjoenulf_RandomCheckpoint": RandomCheckpoint,
|
||||||
@@ -162,9 +164,10 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
|||||||
"Bjornulf_ollamaLoader": "🦙 Ollama (Description)",
|
"Bjornulf_ollamaLoader": "🦙 Ollama (Description)",
|
||||||
"Bjornulf_FreeVRAM": "🧹 Free VRAM hack",
|
"Bjornulf_FreeVRAM": "🧹 Free VRAM hack",
|
||||||
"Bjornulf_TextToSpeech": "🔊 TTS - Text to Speech",
|
"Bjornulf_TextToSpeech": "🔊 TTS - Text to Speech",
|
||||||
"Bjornulf_PickInput": "⏸️🔍 Paused. Select input, Pick one",
|
"Bjornulf_PickInput": "⏸️ Paused. Select input, Pick 👇",
|
||||||
"Bjornulf_PauseResume": "⏸️ Paused. Resume or Stop ?",
|
"Bjornulf_PauseResume": "⏸️ Paused. Resume or Stop, Pick 👇",
|
||||||
# "Bjornulf_LoadImagesFromSelectedFolder": "📂🖼 Load Images from folder",
|
"Bjornulf_LoadImagesFromSelectedFolder": "📂🖼 Load Images from output folder",
|
||||||
|
"Bjornulf_SelectImageFromList": "🖼🔍 Select an Image, Pick",
|
||||||
}
|
}
|
||||||
|
|
||||||
WEB_DIRECTORY = "./web"
|
WEB_DIRECTORY = "./web"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class CombineTexts:
|
|||||||
return {
|
return {
|
||||||
"required": {
|
"required": {
|
||||||
"number_of_inputs": ("INT", {"default": 2, "min": 2, "max": 50, "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", "nothing"], {"default": "newline"}),
|
||||||
"text_1": ("STRING", {"forceInput": True}),
|
"text_1": ("STRING", {"forceInput": True}),
|
||||||
"text_2": ("STRING", {"forceInput": True}),
|
"text_2": ("STRING", {"forceInput": True}),
|
||||||
},
|
},
|
||||||
@@ -44,5 +44,7 @@ class CombineTexts:
|
|||||||
return " "
|
return " "
|
||||||
elif delimiter == "slash":
|
elif delimiter == "slash":
|
||||||
return "/"
|
return "/"
|
||||||
|
elif delimiter == "nothing":
|
||||||
|
return ""
|
||||||
else:
|
else:
|
||||||
return "\n"
|
return "\n"
|
||||||
96
load_images_from_folder.py
Normal file
96
load_images_from_folder.py
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import os
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image, ImageSequence, ImageOps
|
||||||
|
import torch
|
||||||
|
|
||||||
|
class LoadImagesFromSelectedFolder:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
# Get the directory where this script is located
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
comfyui_root = os.path.abspath(os.path.join(script_dir, '..', '..'))
|
||||||
|
output_dir = os.path.join(comfyui_root, 'output')
|
||||||
|
|
||||||
|
def count_images(folder_path):
|
||||||
|
# Count the number of image files in the folder
|
||||||
|
return len([f for f in os.listdir(folder_path) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp'))])
|
||||||
|
|
||||||
|
folders = []
|
||||||
|
for root, dirs, files in os.walk(output_dir):
|
||||||
|
rel_path = os.path.relpath(root, output_dir)
|
||||||
|
if rel_path == '.':
|
||||||
|
continue
|
||||||
|
image_count = count_images(root)
|
||||||
|
if image_count > 0:
|
||||||
|
folder_name = f"{rel_path} ({image_count} images)"
|
||||||
|
folders.append((folder_name, rel_path))
|
||||||
|
|
||||||
|
# Sort folders alphabetically, case-insensitive
|
||||||
|
folders.sort(key=lambda x: x[0].lower())
|
||||||
|
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"selected_folder": ([folder[0] for folder in folders],),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "IMAGE")
|
||||||
|
RETURN_NAMES = ("Images resolution 1", "Images resolution 2", "Images resolution 3", "Images resolution 4")
|
||||||
|
FUNCTION = "load_images_from_selected_folder"
|
||||||
|
CATEGORY = "Bjornulf"
|
||||||
|
|
||||||
|
def load_images_from_selected_folder(self, selected_folder):
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
comfyui_root = os.path.abspath(os.path.join(script_dir, '..', '..'))
|
||||||
|
output_dir = os.path.join(comfyui_root, 'output')
|
||||||
|
folder_path = os.path.join(output_dir, selected_folder.split(" (")[0])
|
||||||
|
|
||||||
|
images_by_resolution = {}
|
||||||
|
|
||||||
|
# Check if the folder exists and contains images
|
||||||
|
if not os.path.exists(folder_path):
|
||||||
|
print(f"Folder {folder_path} does not exist.")
|
||||||
|
return (None, None, None)
|
||||||
|
|
||||||
|
image_files = [f for f in os.listdir(folder_path) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp'))]
|
||||||
|
if not image_files:
|
||||||
|
print(f"No images found in folder {folder_path}.")
|
||||||
|
return (None, None, None)
|
||||||
|
|
||||||
|
for image_file in image_files:
|
||||||
|
image_path = os.path.join(folder_path, image_file)
|
||||||
|
img = Image.open(image_path)
|
||||||
|
|
||||||
|
for i in ImageSequence.Iterator(img):
|
||||||
|
i = ImageOps.exif_transpose(i)
|
||||||
|
|
||||||
|
if i.mode == 'I':
|
||||||
|
i = i.point(lambda i: i * (1 / 255))
|
||||||
|
image = i.convert("RGB")
|
||||||
|
|
||||||
|
resolution = image.size
|
||||||
|
|
||||||
|
image = np.array(image).astype(np.float32) / 255.0
|
||||||
|
image = torch.from_numpy(image)[None,]
|
||||||
|
|
||||||
|
if resolution not in images_by_resolution:
|
||||||
|
images_by_resolution[resolution] = []
|
||||||
|
|
||||||
|
images_by_resolution[resolution].append(image)
|
||||||
|
|
||||||
|
# Sort resolutions by total pixel count (width * height)
|
||||||
|
sorted_resolutions = sorted(images_by_resolution.keys(), key=lambda r: r[0] * r[1], reverse=True)
|
||||||
|
|
||||||
|
outputs = []
|
||||||
|
for i in range(4): # Return up to 4 different resolutions
|
||||||
|
if i < len(sorted_resolutions):
|
||||||
|
resolution = sorted_resolutions[i]
|
||||||
|
output_image = torch.cat(images_by_resolution[resolution], dim=0)
|
||||||
|
outputs.append(output_image)
|
||||||
|
else:
|
||||||
|
# Create a placeholder tensor filled with 11111111111111111111111
|
||||||
|
H, W, C = 64, 64, 3
|
||||||
|
placeholder_image = torch.ones((1, H, W, C), dtype=torch.float32)
|
||||||
|
outputs.append(placeholder_image)
|
||||||
|
|
||||||
|
return tuple(outputs)
|
||||||
@@ -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.33"
|
version = "0.34"
|
||||||
license = {file = "LICENSE"}
|
license = {file = "LICENSE"}
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
|
|||||||
BIN
screenshots/bjornulf_save_character_group.png
Normal file
BIN
screenshots/bjornulf_save_character_group.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
BIN
screenshots/bjornulf_save_character_group2.png
Normal file
BIN
screenshots/bjornulf_save_character_group2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
BIN
screenshots/character_save.png
Normal file
BIN
screenshots/character_save.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
BIN
screenshots/load_images_folder.png
Normal file
BIN
screenshots/load_images_folder.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 553 KiB |
BIN
screenshots/select_image.png
Normal file
BIN
screenshots/select_image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 375 KiB |
BIN
screenshots/select_image_group.png
Normal file
BIN
screenshots/select_image_group.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 481 KiB |
32
select_image_from_list.py
Normal file
32
select_image_from_list.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import torch
|
||||||
|
|
||||||
|
class Everything(str):
|
||||||
|
def __ne__(self, __value: object) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
class SelectImageFromList:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"all_images": ("IMAGE", {}),
|
||||||
|
"selection": ("INT", {"default": 1, "min": 1, "max": 999, "step": 1}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("IMAGE",)
|
||||||
|
RETURN_NAMES = ("selected_image",)
|
||||||
|
FUNCTION = "select_an_image"
|
||||||
|
CATEGORY = "Bjornulf"
|
||||||
|
|
||||||
|
def select_an_image(self, all_images, selection):
|
||||||
|
# Ensure the selection is within bounds
|
||||||
|
selection = max(1, min(selection, all_images.shape[0]))
|
||||||
|
|
||||||
|
# Adjust selection to 0-based index
|
||||||
|
index = selection - 1
|
||||||
|
|
||||||
|
# Select the image at the specified index
|
||||||
|
selected_image = all_images[index].unsqueeze(0)
|
||||||
|
|
||||||
|
return (selected_image,)
|
||||||
Reference in New Issue
Block a user