This commit is contained in:
justumen
2024-09-13 12:24:18 +02:00
parent 0cdec9184c
commit eedab4fde5
9 changed files with 288 additions and 6 deletions

View File

@@ -1,4 +1,4 @@
# 🔗 Comfyui : Bjornulf_custom_nodes v0.23 🔗
# 🔗 Comfyui : Bjornulf_custom_nodes v0.24 🔗
# ☁ 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.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.24**: And a new node: Pause, select input, pick one.
# 📝 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)
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.
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
![pick input](screenshots/pick.png)
**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`.

View File

@@ -41,6 +41,8 @@ from .text_to_speech import TextToSpeech
from .loop_combine_texts_by_lines import CombineTextsByLines
from .free_vram_hack import FreeVRAM
from .pause_resume_stop import PauseResume
from .pick_input import PickInput
# from .pass_preview_image import PassPreviewImage
# from .check_black_image import CheckBlackImage
# from .clear_vram import ClearVRAM
@@ -49,6 +51,8 @@ from .pause_resume_stop import PauseResume
NODE_CLASS_MAPPINGS = {
# "Bjornulf_CustomStringType": CustomStringType,
"Bjornulf_ollamaLoader": ollamaLoader,
# "Bjornulf_PassPreviewImage": PassPreviewImage,
"Bjornulf_PickInput": PickInput,
"Bjornulf_PauseResume": PauseResume,
"Bjornulf_FreeVRAM": FreeVRAM,
"Bjornulf_CombineTextsByLines": CombineTextsByLines,
@@ -98,6 +102,8 @@ NODE_CLASS_MAPPINGS = {
NODE_DISPLAY_NAME_MAPPINGS = {
# "Bjornulf_CustomStringType": "!!! CUSTOM STRING TYPE !!!",
"Bjornulf_ollamaLoader": "🦙 Ollama (Description)",
# "Bjornulf_PassPreviewImage": "🖼⮕ Pass Preview Image",
"Bjornulf_PickInput": "⏸️🔍 Paused. Select input, Pick one",
"Bjornulf_PauseResume": "⏸️ Paused. Resume or Stop ?",
"Bjornulf_FreeVRAM": "🧹 Free VRAM hack",
"Bjornulf_CombineTextsByLines": "♻ Loop (All Lines from input 🔗 combine by lines)",

View File

@@ -17,7 +17,8 @@ class LoadImageWithTransparency:
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"
def load_image_alpha(self, image):
@@ -62,7 +63,7 @@ class LoadImageWithTransparency:
output_image = output_images[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
def IS_CHANGED(s, image):

View File

@@ -42,7 +42,6 @@ class PauseResume:
if PauseResume.should_stop:
PauseResume.should_stop = False # Reset for next run
PauseResume.is_paused = True
PauseResume.should_stop = False
raise Exception("Workflow stopped by user")
PauseResume.is_paused = True

152
pick_input.py Normal file
View 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")

View File

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

BIN
screenshots/pick.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 KiB

114
web/js/pick_input.js Normal file
View 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);
}
},
});