This commit is contained in:
justumen
2024-09-17 12:07:26 +02:00
parent 992a2e762d
commit 510d25e766
9 changed files with 153 additions and 41 deletions

View File

@@ -1,4 +1,4 @@
# 🔗 Comfyui : Bjornulf_custom_nodes v0.36 🔗
# 🔗 Comfyui : Bjornulf_custom_nodes v0.37 🔗
# ❤️ Coffee : ☕☕☕☕☕ 5/5
@@ -84,6 +84,7 @@ wget --content-disposition -P /workspace/ComfyUI/models/checkpoints "https://civ
- **v0.34**: Two new nodes : Load Images from output folder and Select an Image, Pick.
- **v0.35**: Great improvements of the TTS node 31. It will also save the audio file in the "ComfyUI/Bjornulf_TTS/" folder. - Not tested on windows yet -
- **v0.36**: Fix random model.
- **v0.37**: New node : Random Load checkpoint (Model Selector). Alternative to the random checkpoint node. (Not preloading all checkpoints in memory, slower to switch between checkpoints, but more outputs to decide where to store your results.)
# 📝 Nodes descriptions
@@ -460,19 +461,41 @@ It will take the same special syntax as the Advanced write text node `{blue|red}
### 40 - 🎲 Random (Model+Clip+Vae) - aka Checkpoint / Model
![pick input](screenshots/random_checkpoint.png)
![random checkpoint](screenshots/random_checkpoint.png)
**Description:**
Just take a trio at random from a load checkpoint node.
Just simply take a trio at random from a load checkpoint node.
Notice that it is using the core Load checkpoint node. It means that all checkpoint will be preloaded in memory.
### 41 - ♻ Loop (Model+Clip+Vae) - aka Checkpoint / Model
Details :
- It will take more VRAM, but it will be faster to switch between checkpoints.
- It can't give you the currently loaded checkpoint name's.
Check node number 41 before deciding which one to use.
### 41 - 🎲 Random Load checkpoint (Model Selector)
![pick input](screenshots/random_load_checkpoint.png)
**Description:**
This is another way to select a load checkpoint node randomly.
It will not preload all the checkpoints in memory, so it will be slower to switch between checkpoints.
But you can use more outputs to decide where to store your results. (`model_folder` is returning the last folder name of the checkpoint.)
I always store my checkpoints in a folder with the type of the model like `SD1.5`, `SDXL`, etc... So it's a good way for me to recover that information quickly.
Details :
- Note that compared to node 40, you can't have separate configuration depending of the selected checkpoint. (For example `CLIP Set Last Layer` node set at -2 for a specific model, or a separate vae or clip.) Aka : All models are going to share the exact same workflow.
Check node number 40 before deciding which one to use.
### 42 - ♻ Loop (Model+Clip+Vae) - aka Checkpoint / Model
![pick input](screenshots/loop_checkpoint.png)
**Description:**
Loop over all the trios from several checkpoint node.
### 42 - 📂🖼 Load Images from output folder
### 43 - 📂🖼 Load Images from output folder
![pick input](screenshots/load_images_folder.png)
@@ -494,7 +517,7 @@ If you are satisfied with this logic, you can then select all these nodes, right
Here is another example of the same thing but excluding the save folder node :
![pick input](screenshots/bjornulf_save_character_group2.png)
### 43 - 🖼🔍 Select an Image, Pick
### 44 - 🖼🔍 Select an Image, Pick
![pick input](screenshots/select_image.png)

View File

@@ -43,29 +43,27 @@ from .pause_resume_stop import PauseResume
from .pick_input import PickInput
from .loop_images import LoopImages
from .random_image import RandomImage
# from .random_checkpoint import RandomCheckpoint
from .loop_model_clip_vae import LoopModelClipVae
from .write_text_advanced import WriteTextAdvanced
from .loop_write_text import LoopWriteText
from .load_images_from_folder import LoadImagesFromSelectedFolder
# from .show import ShowWhatever
from .select_image_from_list import SelectImageFromList
from .random_model_selector import RandomModelSelector
# from .pass_preview_image import PassPreviewImage
# from .check_black_image import CheckBlackImage
# from .clear_vram import ClearVRAM
# from .CUSTOM_STRING import CustomStringType
NODE_CLASS_MAPPINGS = {
# "Bjornulf_CustomStringType": CustomStringType,
"Bjornulf_ollamaLoader": ollamaLoader,
"Bjornulf_RandomModelSelector": RandomModelSelector,
"Bjornulf_SelectImageFromList": SelectImageFromList,
"Bjornulf_WriteText": WriteText,
"Bjornulf_LoadImagesFromSelectedFolder": LoadImagesFromSelectedFolder,
# "Bjornulf_ShowWhatever": ShowWhatever,
"Bjornulf_LoopModelClipVae": LoopModelClipVae,
# "Bjoenulf_RandomCheckpoint": RandomCheckpoint,
"Bjornulf_LoopWriteText": LoopWriteText,
"Bjornulf_LoopImages": LoopImages,
"Bjornulf_RandomImage": RandomImage,
@@ -135,6 +133,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"Bjornulf_RandomLineFromInput": "🎲 Random line from input",
"Bjornulf_RandomTexts": "🎲 Random (Texts)",
"Bjornulf_RandomModelClipVae": "🎲 Random (Model+Clip+Vae)",
"Bjornulf_RandomModelSelector": "🎲 Random Load checkpoint (Model Selector)",
# "Bjornulf_PassPreviewImage": "🖼⮕ Pass Preview Image",
"Bjornulf_CharacterDescriptionGenerator": "🧑📝 Character Description Generator",
"Bjornulf_GreenScreenToTransparency": "🟩➜▢ Green Screen to Transparency",

View File

@@ -15,11 +15,10 @@ class LoadImageWithTransparency:
{"image": (sorted(files), {"image_upload": True})},
}
CATEGORY = "image"
RETURN_TYPES = ("IMAGE", "MASK", "STRING") # Added "STRING" for the image path
RETURN_NAMES = ("image", "mask", "image_path")
FUNCTION = "load_image_alpha"
CATEGORY = "Bjornulf"
def load_image_alpha(self, image):
image_path = folder_paths.get_annotated_filepath(image)

View File

@@ -1,7 +1,7 @@
[project]
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, ..."
version = "0.36"
version = "0.37"
license = {file = "LICENSE"}
[project.urls]

View File

@@ -1,28 +0,0 @@
import random
class RandomCheckpoint:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"number_of_inputs": ("INT", {"default": 2, "min": 2, "max": 10, "step": 1}),
"model_1": ("MODEL", {"forceInput": True}),
"clip_1": ("CLIP", {"forceInput": True}),
"vae_1": ("VAE", {"forceInput": True}),
"model_2": ("MODEL", {"forceInput": True}),
"clip_2": ("CLIP", {"forceInput": True}),
"vae_2": ("VAE", {"forceInput": True}),
}
}
RETURN_TYPES = ("MODEL", "CLIP", "VAE")
FUNCTION = "random_select"
def random_select(self, number_of_inputs, **kwargs):
selected_index = random.randint(1, number_of_inputs)
selected_model = kwargs[f"model_{selected_index}"]
selected_clip = kwargs[f"clip_{selected_index}"]
selected_vae = kwargs[f"vae_{selected_index}"]
return (selected_model, selected_clip, selected_vae)

View File

@@ -24,6 +24,7 @@ class RandomModelClipVae:
RETURN_TYPES = ("MODEL", "CLIP", "VAE")
FUNCTION = "random_select"
CATEGORY = "Bjornulf"
def random_select(self, number_of_inputs, **kwargs):
random.seed(kwargs.get('seed', 0))

56
random_model_selector.py Normal file
View File

@@ -0,0 +1,56 @@
import os
import random
from folder_paths import get_filename_list, get_full_path
import comfy.sd
class RandomModelSelector:
@classmethod
def INPUT_TYPES(cls):
model_list = get_filename_list("checkpoints")
optional_inputs = {}
for i in range(1, 11):
optional_inputs[f"model_{i}"] = (model_list, {"default": model_list[min(i-1, len(model_list)-1)]})
optional_inputs["seed"] = ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff})
return {
"required": {
"number_of_models": ("INT", {"default": 3, "min": 1, "max": 20, "step": 1}),
},
"optional": optional_inputs
}
RETURN_TYPES = ("MODEL", "CLIP", "VAE", "STRING", "STRING", "STRING")
RETURN_NAMES = ("model", "clip", "vae", "model_path", "model_name", "model_folder")
FUNCTION = "random_select_model"
CATEGORY = "Bjornulf"
def random_select_model(self, number_of_models, seed, **kwargs):
random.seed(seed)
available_models = [kwargs[f"model_{i}"] for i in range(1, number_of_models + 1) if f"model_{i}" in kwargs]
if not available_models:
raise ValueError("No models selected")
selected_model = random.choice(available_models)
# Extract just the name of the model (no folders and no extensions)
model_name = os.path.splitext(os.path.basename(selected_model))[0]
# Get the full path of the selected model
model_path = get_full_path("checkpoints", selected_model)
# Get the folder of the selected model (Hopefully people use that to organize their models...)
model_folder = os.path.basename(os.path.dirname(model_path))
# Load the model
loaded_objects = comfy.sd.load_checkpoint_guess_config(model_path)
# Unpack only the values we need
model = loaded_objects[0]
clip = loaded_objects[1]
vae = loaded_objects[2]
return (model, clip, vae, model_path, model_name, model_folder)

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

View File

@@ -0,0 +1,62 @@
import { app } from "../../../scripts/app.js";
app.registerExtension({
name: "Bjornulf.RandomModelSelector",
async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_RandomModelSelector") {
const updateModelInputs = () => {
const numModelsWidget = node.widgets.find(w => w.name === "number_of_models");
if (!numModelsWidget) return;
const numModels = numModelsWidget.value;
const checkpointsList = node.widgets.find(w => w.name === "model_1").options.values;
// Remove excess model widgets
node.widgets = node.widgets.filter(w => !w.name.startsWith("model_") || parseInt(w.name.split("_")[1]) <= numModels);
// Add new model widgets if needed
for (let i = 1; i <= numModels; i++) {
const widgetName = `model_${i}`;
if (!node.widgets.find(w => w.name === widgetName)) {
const defaultIndex = Math.min(i - 1, checkpointsList.length - 1);
node.addWidget("combo", widgetName, checkpointsList[defaultIndex], () => {}, {
values: checkpointsList
});
}
}
// Reorder widgets
node.widgets.sort((a, b) => {
if (a.name === "number_of_models") return -1;
if (b.name === "number_of_models") return 1;
if (a.name === "seed") return 1;
if (b.name === "seed") return -1;
if (a.name.startsWith("model_") && b.name.startsWith("model_")) {
return parseInt(a.name.split("_")[1]) - parseInt(b.name.split("_")[1]);
}
return a.name.localeCompare(b.name);
});
node.setSize(node.computeSize());
};
// Set up number_of_models widget
const numModelsWidget = node.widgets.find(w => w.name === "number_of_models");
if (numModelsWidget) {
numModelsWidget.callback = () => {
updateModelInputs();
app.graph.setDirtyCanvas(true);
};
}
// Set seed widget to hidden input
const seedWidget = node.widgets.find((w) => w.name === "seed");
if (seedWidget) {
seedWidget.type = "HIDDEN";
}
// Initial update
updateModelInputs();
}
}
});