mirror of
https://github.com/justUmen/Bjornulf_custom_nodes.git
synced 2026-03-21 20:52:11 -03:00
0.24
This commit is contained in:
14
README.md
14
README.md
@@ -1,4 +1,4 @@
|
|||||||
# 🔗 Comfyui : Bjornulf_custom_nodes v0.23 🔗
|
# 🔗 Comfyui : Bjornulf_custom_nodes v0.24 🔗
|
||||||
|
|
||||||
# ☁ Usage in cloud :
|
# ☁ Usage in cloud :
|
||||||
|
|
||||||
@@ -66,6 +66,7 @@ wget --content-disposition -P /workspace/ComfyUI/models/checkpoints "https://civ
|
|||||||
- **v0.21**: Add a new write text node that also display the text in the comfyui console (good for debugging)
|
- **v0.21**: Add a new write text node that also display the text in the comfyui console (good for debugging)
|
||||||
- **v0.22**: Allow write text node to use random selection like this {hood|helmet} will randomly choose between hood or helmet.
|
- **v0.22**: Allow write text node to use random selection like this {hood|helmet} will randomly choose between hood or helmet.
|
||||||
- **v0.23**: And a new node: Pause, resume or stop workflow.
|
- **v0.23**: And a new node: Pause, resume or stop workflow.
|
||||||
|
- **v0.24**: And a new node: Pause, select input, pick one.
|
||||||
|
|
||||||
# 📝 Nodes descriptions
|
# 📝 Nodes descriptions
|
||||||
|
|
||||||
@@ -338,4 +339,13 @@ Just connect this node with your workflow, it takes an image as input and return
|
|||||||
Automatically pause the workflow, and rings a bell when it does. (play the audio `bell.m4a` file provided)
|
Automatically pause the workflow, and rings a bell when it does. (play the audio `bell.m4a` file provided)
|
||||||
You can then manually resume or stop the workflow by clicking on the node's buttons.
|
You can then manually resume or stop the workflow by clicking on the node's buttons.
|
||||||
I do that let's say for example if I have a very long upscaling process, I can check if the input is good before continuing. Sometimes I might stop the workflow and restart it with another seed.
|
I do that let's say for example if I have a very long upscaling process, I can check if the input is good before continuing. Sometimes I might stop the workflow and restart it with another seed.
|
||||||
You can connect any type of node to the pause node, above is an example with text, but you can send an IMAGE or whatever else, in the node `input = output`. (Of course you need to send the output to something that has the correct format...)
|
You can connect any type of node to the pause node, above is an example with text, but you can send an IMAGE or whatever else, in the node `input = output`. (Of course you need to send the output to something that has the correct format...)
|
||||||
|
|
||||||
|
### 36 - ⏸️🔍 Paused. Select input, Pick one
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Description:**
|
||||||
|
Automatically pause the workflow, and rings a bell when it does. (play the audio `bell.m4a` file provided)
|
||||||
|
You can then manually select the input you want to use, and resume the workflow with it.
|
||||||
|
You can connect this node to anything you want, above is an example with IMAGE. But you can pick whatever you want, in the node `input = output`.
|
||||||
@@ -41,6 +41,8 @@ from .text_to_speech import TextToSpeech
|
|||||||
from .loop_combine_texts_by_lines import CombineTextsByLines
|
from .loop_combine_texts_by_lines import CombineTextsByLines
|
||||||
from .free_vram_hack import FreeVRAM
|
from .free_vram_hack import FreeVRAM
|
||||||
from .pause_resume_stop import PauseResume
|
from .pause_resume_stop import PauseResume
|
||||||
|
from .pick_input import PickInput
|
||||||
|
# from .pass_preview_image import PassPreviewImage
|
||||||
# from .check_black_image import CheckBlackImage
|
# from .check_black_image import CheckBlackImage
|
||||||
# from .clear_vram import ClearVRAM
|
# from .clear_vram import ClearVRAM
|
||||||
|
|
||||||
@@ -49,6 +51,8 @@ from .pause_resume_stop import PauseResume
|
|||||||
NODE_CLASS_MAPPINGS = {
|
NODE_CLASS_MAPPINGS = {
|
||||||
# "Bjornulf_CustomStringType": CustomStringType,
|
# "Bjornulf_CustomStringType": CustomStringType,
|
||||||
"Bjornulf_ollamaLoader": ollamaLoader,
|
"Bjornulf_ollamaLoader": ollamaLoader,
|
||||||
|
# "Bjornulf_PassPreviewImage": PassPreviewImage,
|
||||||
|
"Bjornulf_PickInput": PickInput,
|
||||||
"Bjornulf_PauseResume": PauseResume,
|
"Bjornulf_PauseResume": PauseResume,
|
||||||
"Bjornulf_FreeVRAM": FreeVRAM,
|
"Bjornulf_FreeVRAM": FreeVRAM,
|
||||||
"Bjornulf_CombineTextsByLines": CombineTextsByLines,
|
"Bjornulf_CombineTextsByLines": CombineTextsByLines,
|
||||||
@@ -98,6 +102,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)",
|
||||||
|
# "Bjornulf_PassPreviewImage": "🖼⮕ Pass Preview Image",
|
||||||
|
"Bjornulf_PickInput": "⏸️🔍 Paused. Select input, Pick one",
|
||||||
"Bjornulf_PauseResume": "⏸️ Paused. Resume or Stop ?",
|
"Bjornulf_PauseResume": "⏸️ Paused. Resume or Stop ?",
|
||||||
"Bjornulf_FreeVRAM": "🧹 Free VRAM hack",
|
"Bjornulf_FreeVRAM": "🧹 Free VRAM hack",
|
||||||
"Bjornulf_CombineTextsByLines": "♻ Loop (All Lines from input 🔗 combine by lines)",
|
"Bjornulf_CombineTextsByLines": "♻ Loop (All Lines from input 🔗 combine by lines)",
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ class LoadImageWithTransparency:
|
|||||||
|
|
||||||
CATEGORY = "image"
|
CATEGORY = "image"
|
||||||
|
|
||||||
RETURN_TYPES = ("IMAGE",)
|
RETURN_TYPES = ("IMAGE", "MASK", "STRING") # Added "STRING" for the image path
|
||||||
|
RETURN_NAMES = ("image", "mask", "image_path")
|
||||||
FUNCTION = "load_image_alpha"
|
FUNCTION = "load_image_alpha"
|
||||||
|
|
||||||
def load_image_alpha(self, image):
|
def load_image_alpha(self, image):
|
||||||
@@ -62,7 +63,7 @@ class LoadImageWithTransparency:
|
|||||||
output_image = output_images[0]
|
output_image = output_images[0]
|
||||||
output_mask = output_masks[0]
|
output_mask = output_masks[0]
|
||||||
|
|
||||||
return (output_image, output_mask)
|
return (output_image, output_mask, image_path) # Added image_path to the return tuple
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def IS_CHANGED(s, image):
|
def IS_CHANGED(s, image):
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ class PauseResume:
|
|||||||
if PauseResume.should_stop:
|
if PauseResume.should_stop:
|
||||||
PauseResume.should_stop = False # Reset for next run
|
PauseResume.should_stop = False # Reset for next run
|
||||||
PauseResume.is_paused = True
|
PauseResume.is_paused = True
|
||||||
PauseResume.should_stop = False
|
|
||||||
raise Exception("Workflow stopped by user")
|
raise Exception("Workflow stopped by user")
|
||||||
|
|
||||||
PauseResume.is_paused = True
|
PauseResume.is_paused = True
|
||||||
|
|||||||
152
pick_input.py
Normal file
152
pick_input.py
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import time
|
||||||
|
from aiohttp import web
|
||||||
|
from server import PromptServer
|
||||||
|
import logging
|
||||||
|
from pydub import AudioSegment
|
||||||
|
from pydub.playback import play
|
||||||
|
import os
|
||||||
|
|
||||||
|
class Everything(str):
|
||||||
|
def __ne__(self, __value: object) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class PickInput:
|
||||||
|
is_paused = True
|
||||||
|
should_stop = False
|
||||||
|
selected_input = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"number_of_inputs": ("INT", {"default": 2, "min": 1, "max": 10, "step": 1}),
|
||||||
|
},
|
||||||
|
"hidden": {
|
||||||
|
**{f"input_{i}": (Everything("*"), {"forceInput": "True"}) for i in range(2, 11)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = (Everything("*"),)
|
||||||
|
RETURN_NAMES = ("output",)
|
||||||
|
FUNCTION = "pick_input"
|
||||||
|
CATEGORY = "Bjornulf"
|
||||||
|
|
||||||
|
def play_audio(self):
|
||||||
|
audio_file = os.path.join(os.path.dirname(__file__), 'bell.m4a')
|
||||||
|
sound = AudioSegment.from_file(audio_file, format="m4a")
|
||||||
|
play(sound)
|
||||||
|
|
||||||
|
def pick_input(self, **kwargs):
|
||||||
|
logging.info(f"Selected input at the start: {PickInput.selected_input}")
|
||||||
|
self.play_audio()
|
||||||
|
|
||||||
|
while PickInput.is_paused and not PickInput.should_stop:
|
||||||
|
logging.info(f"PickInput.is_paused: {PickInput.is_paused}, PickInput.should_stop: {PickInput.should_stop}")
|
||||||
|
time.sleep(1) # Sleep to prevent busy waiting
|
||||||
|
|
||||||
|
if PickInput.should_stop:
|
||||||
|
PickInput.should_stop = False # Reset for next run
|
||||||
|
PickInput.is_paused = True
|
||||||
|
raise Exception("Workflow stopped by user")
|
||||||
|
|
||||||
|
PickInput.is_paused = True
|
||||||
|
PickInput.should_stop = False
|
||||||
|
|
||||||
|
# Check if the selected input exists in kwargs
|
||||||
|
if PickInput.selected_input not in kwargs:
|
||||||
|
logging.error(f"Selected input '{PickInput.selected_input}' not found in kwargs")
|
||||||
|
logging.info(f"Available kwargs: {list(kwargs.keys())}")
|
||||||
|
return (None,) # or handle this error as appropriate
|
||||||
|
|
||||||
|
selected_value = kwargs.get(PickInput.selected_input)
|
||||||
|
logging.info(f"Value of selected input '{PickInput.selected_input}': {selected_value}")
|
||||||
|
|
||||||
|
# Store the value in self.target if needed
|
||||||
|
self.target = selected_value
|
||||||
|
|
||||||
|
return (selected_value,)
|
||||||
|
|
||||||
|
def create_select_handler(self, index):
|
||||||
|
async def select_input(request):
|
||||||
|
self.selected_input = index
|
||||||
|
self.is_waiting = False
|
||||||
|
return web.Response(text=f"Input {index + 1} selected")
|
||||||
|
return select_input
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/bjornulf_stop_pick")
|
||||||
|
async def stop_node_pick(request):
|
||||||
|
logging.info("Stop node pick called")
|
||||||
|
PickInput.should_stop = True
|
||||||
|
PickInput.is_paused = False # Ensure the loop exits
|
||||||
|
return web.Response(text="Workflow stopped")
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/bjornulf_select_input_1")
|
||||||
|
async def bjornulf_select_input_1(request):
|
||||||
|
logging.info("Resume node called")
|
||||||
|
PickInput.is_paused = False
|
||||||
|
PickInput.selected_input="input_1"
|
||||||
|
return web.Response(text="Node resumed")
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/bjornulf_select_input_2")
|
||||||
|
async def bjornulf_select_input_2(request):
|
||||||
|
logging.info("Resume node called")
|
||||||
|
PickInput.is_paused = False
|
||||||
|
PickInput.selected_input="input_2"
|
||||||
|
return web.Response(text="Node resumed")
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/bjornulf_select_input_3")
|
||||||
|
async def bjornulf_select_input_3(request):
|
||||||
|
logging.info("Resume node called")
|
||||||
|
PickInput.is_paused = False
|
||||||
|
PickInput.selected_input="input_3"
|
||||||
|
return web.Response(text="Node resumed")
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/bjornulf_select_input_4")
|
||||||
|
async def bjornulf_select_input_4(request):
|
||||||
|
logging.info("Resume node called")
|
||||||
|
PickInput.is_paused = False
|
||||||
|
PickInput.selected_input="input_4"
|
||||||
|
return web.Response(text="Node resumed")
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/bjornulf_select_input_5")
|
||||||
|
async def bjornulf_select_input_5(request):
|
||||||
|
logging.info("Resume node called")
|
||||||
|
PickInput.is_paused = False
|
||||||
|
PickInput.selected_input="input_5"
|
||||||
|
return web.Response(text="Node resumed")
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/bjornulf_select_input_6")
|
||||||
|
async def bjornulf_select_input_6(request):
|
||||||
|
logging.info("Resume node called")
|
||||||
|
PickInput.is_paused = False
|
||||||
|
PickInput.selected_input="input_6"
|
||||||
|
return web.Response(text="Node resumed")
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/bjornulf_select_input_7")
|
||||||
|
async def bjornulf_select_input_7(request):
|
||||||
|
logging.info("Resume node called")
|
||||||
|
PickInput.is_paused = False
|
||||||
|
PickInput.selected_input="input_7"
|
||||||
|
return web.Response(text="Node resumed")
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/bjornulf_select_input_8")
|
||||||
|
async def bjornulf_select_input_8(request):
|
||||||
|
logging.info("Resume node called")
|
||||||
|
PickInput.is_paused = False
|
||||||
|
PickInput.selected_input="input_8"
|
||||||
|
return web.Response(text="Node resumed")
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/bjornulf_select_input_9")
|
||||||
|
async def bjornulf_select_input_9(request):
|
||||||
|
logging.info("Resume node called")
|
||||||
|
PickInput.is_paused = False
|
||||||
|
PickInput.selected_input="input_9"
|
||||||
|
return web.Response(text="Node resumed")
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/bjornulf_select_input_10")
|
||||||
|
async def bjornulf_select_input_10(request):
|
||||||
|
logging.info("Resume node called")
|
||||||
|
PickInput.is_paused = False
|
||||||
|
PickInput.selected_input="input_10"
|
||||||
|
return web.Response(text="Node resumed")
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "bjornulf_custom_nodes"
|
name = "bjornulf_custom_nodes"
|
||||||
description = "Nodes: Ollama, Text to Speech, Save image for Bjornulf LobeChat, Text with random Seed, Random line from input, Combine images (Background + Overlay alpha), Image to grayscale (black & white), Remove image Transparency (alpha), Resize Image, ..."
|
description = "Nodes: Ollama, Text to Speech, Save image for Bjornulf LobeChat, Text with random Seed, Random line from input, Combine images (Background + Overlay alpha), Image to grayscale (black & white), Remove image Transparency (alpha), Resize Image, ..."
|
||||||
version = "0.23"
|
version = "0.24"
|
||||||
license = {file = "LICENSE"}
|
license = {file = "LICENSE"}
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
|
|||||||
BIN
screenshots/pick.png
Normal file
BIN
screenshots/pick.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 519 KiB |
114
web/js/pick_input.js
Normal file
114
web/js/pick_input.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import { app } from "../../../scripts/app.js";
|
||||||
|
|
||||||
|
app.registerExtension({
|
||||||
|
name: "Bjornulf.PickInput",
|
||||||
|
async nodeCreated(node) {
|
||||||
|
if (node.comfyClass === "Bjornulf_PickInput") {
|
||||||
|
|
||||||
|
const updateInputButtons = (numInputs) => {
|
||||||
|
// Remove all existing widgets
|
||||||
|
node.widgets.length = 1;
|
||||||
|
|
||||||
|
// Re-add the number_of_inputs widget
|
||||||
|
// const numInputsWidget = node.addWidget("number", "Number of Inputs", "number_of_inputs", (v) => {
|
||||||
|
// updateInputs();
|
||||||
|
// app.graph.setDirtyCanvas(true);
|
||||||
|
// return v;
|
||||||
|
// }, { min: 1, max: 10, step: 1, precision: 0 });
|
||||||
|
|
||||||
|
// Add new input buttons
|
||||||
|
for (let i = 1; i < numInputs + 1; i++) {
|
||||||
|
node.addWidget("button", `Input ${i}`, `input_button_${i}`, () => {
|
||||||
|
fetch(`/bjornulf_select_input_${i}`, { method: "GET" })
|
||||||
|
.then((response) => response.text())
|
||||||
|
.then((data) => {
|
||||||
|
console.log(`Input ${i} response:`, data);
|
||||||
|
// You can update the UI here if needed
|
||||||
|
})
|
||||||
|
.catch((error) => console.error("Error:", error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-add the Stop button
|
||||||
|
node.addWidget("button", "Stop", "Stop", () => {
|
||||||
|
fetch("/bjornulf_stop_pick", { method: "GET" })
|
||||||
|
.then((response) => response.text())
|
||||||
|
.then((data) => {
|
||||||
|
console.log("Stop response:", data);
|
||||||
|
// You can update the UI here if needed
|
||||||
|
})
|
||||||
|
.catch((error) => console.error("Error:", error));
|
||||||
|
});
|
||||||
|
|
||||||
|
node.setSize(node.computeSize());
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateInputs = () => {
|
||||||
|
const numInputsWidget = node.widgets.find(
|
||||||
|
(w) => w.name === "number_of_inputs"
|
||||||
|
);
|
||||||
|
if (!numInputsWidget) return;
|
||||||
|
|
||||||
|
const numInputs = numInputsWidget.value;
|
||||||
|
|
||||||
|
// Initialize node.inputs if it doesn't exist
|
||||||
|
if (!node.inputs) {
|
||||||
|
node.inputs = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter existing text inputs
|
||||||
|
const existingInputs = node.inputs.filter((input) =>
|
||||||
|
input.name.startsWith("input_")
|
||||||
|
);
|
||||||
|
|
||||||
|
// const Everything = Symbol('Everything');
|
||||||
|
// Determine if we need to add or remove inputs
|
||||||
|
if (existingInputs.length < numInputs) {
|
||||||
|
// Add new inputs if not enough existing
|
||||||
|
for (let i = existingInputs.length + 1; i <= numInputs; i++) {
|
||||||
|
const inputName = `input_${i}`;
|
||||||
|
if (!node.inputs.find((input) => input.name === inputName)) {
|
||||||
|
node.addInput(inputName, "*");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Remove excess inputs if too many
|
||||||
|
node.inputs = node.inputs.filter(
|
||||||
|
(input) =>
|
||||||
|
!input.name.startsWith("input_") ||
|
||||||
|
parseInt(input.name.split("_")[1]) <= numInputs
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update input buttons
|
||||||
|
updateInputButtons(numInputs);
|
||||||
|
|
||||||
|
node.setSize(node.computeSize());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set seed widget to hidden input
|
||||||
|
const seedWidget = node.widgets.find((w) => w.name === "seed");
|
||||||
|
if (seedWidget) {
|
||||||
|
seedWidget.type = "HIDDEN";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move number_of_inputs to the top initially
|
||||||
|
const numInputsWidget = node.widgets.find(
|
||||||
|
(w) => w.name === "number_of_inputs"
|
||||||
|
);
|
||||||
|
if (numInputsWidget) {
|
||||||
|
node.widgets = [
|
||||||
|
numInputsWidget,
|
||||||
|
...node.widgets.filter((w) => w !== numInputsWidget),
|
||||||
|
];
|
||||||
|
numInputsWidget.callback = () => {
|
||||||
|
updateInputs();
|
||||||
|
app.graph.setDirtyCanvas(true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay the initial update to ensure node is fully initialized
|
||||||
|
setTimeout(updateInputs, 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user