diff --git a/README.md b/README.md index 4fee0c5..f85fd91 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 🔗 Comfyui : Bjornulf_custom_nodes v0.16 🔗 +# 🔗 Comfyui : Bjornulf_custom_nodes v0.17 🔗 # Dependencies @@ -26,6 +26,7 @@ - **v0.14**: Add a new node: Cut image from a mask - **v0.15**: Add two new nodes: TTS - Text to Speech and Character Description Generator - **v0.16**: Big changes on Character Description Generator +- **v0.17**: New loop node, combine by lines. # 📝 Nodes descriptions @@ -263,4 +264,12 @@ If my TTS server is running on port 8020 (You can test in browser with the link Generate a character description based on a json file in the folder `characters` : `ComfyUI/custom_nodes/Bjornulf_custom_nodes/characters` Make your own json file with your own characters, and use this node to generate a description. ❗ For now it's very basic node, a lot of things are going to be added and changed !!! -Some details are unusable for some checkpoints, very much a work in progress, the json structure isn't set in stone either. \ No newline at end of file +Some details are unusable for some checkpoints, very much a work in progress, the json structure isn't set in stone either. + +### 33 - ♻ Loop (All Lines from input 🔗 combine by lines) + +![loop combined](screenshots/loop_combined.png) + +**Description:** +Sometimes you want to loop over several inputs but you also want to separate different lines of your output. +So with this node, you can have the number of inputs and outputs you want. See example for usage. \ No newline at end of file diff --git a/__init__.py b/__init__.py index a2fd4af..520e21f 100644 --- a/__init__.py +++ b/__init__.py @@ -37,6 +37,7 @@ from .load_image_alpha import LoadImageWithTransparency from .image_mask_cutter import ImageMaskCutter from .character_description import CharacterDescriptionGenerator from .text_to_speech import TextToSpeech +from .loop_combine_texts_by_lines import CombineTextsByLines # from .check_black_image import CheckBlackImage # from .clear_vram import ClearVRAM @@ -45,6 +46,7 @@ from .text_to_speech import TextToSpeech NODE_CLASS_MAPPINGS = { # "Bjornulf_CustomStringType": CustomStringType, "Bjornulf_ollamaLoader": ollamaLoader, + "Bjornulf_CombineTextsByLines": CombineTextsByLines, "Bjornulf_TextToSpeech": TextToSpeech, "Bjornulf_CharacterDescriptionGenerator": CharacterDescriptionGenerator, "Bjornulf_ImageMaskCutter": ImageMaskCutter, @@ -90,6 +92,7 @@ NODE_CLASS_MAPPINGS = { NODE_DISPLAY_NAME_MAPPINGS = { # "Bjornulf_CustomStringType": "!!! CUSTOM STRING TYPE !!!", "Bjornulf_ollamaLoader": "🦙 Ollama (Description)", + "Bjornulf_CombineTextsByLines": "♻ Loop (All Lines from input 🔗 combine by lines)", "Bjornulf_TextToSpeech": "🔊 TTS - Text to Speech", "Bjornulf_CharacterDescriptionGenerator": "🧑📝 Character Description Generator", "Bjornulf_ImageMaskCutter": "🖼✂ Cut Image with Mask", diff --git a/loop_combine_texts_by_lines.py b/loop_combine_texts_by_lines.py new file mode 100644 index 0000000..e8a1873 --- /dev/null +++ b/loop_combine_texts_by_lines.py @@ -0,0 +1,49 @@ +class CombineTextsByLines: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "number_of_inputs": ("INT", {"default": 2, "min": 1, "max": 10, "step": 1}), + "number_of_lines": ("INT", {"default": 3, "min": 1, "max": 20, "step": 1}), + } + } + + RETURN_TYPES = tuple(["STRING"] * 20) # Maximum 20 lines + RETURN_NAMES = tuple([f"line_{i+1}" for i in range(20)]) + FUNCTION = "extract_lines" + OUTPUT_NODE = True + CATEGORY = "text" + OUTPUT_IS_LIST = tuple([True] * 20) # Indicate that all outputs are lists + + def extract_lines(self, number_of_inputs, number_of_lines, **kwargs): + grouped_lines = [[] for _ in range(number_of_lines)] + + for i in range(1, number_of_inputs + 1): + text_key = f"text_{i}" + if text_key in kwargs and kwargs[text_key]: + lines = kwargs[text_key].split('\n') + lines = [line.strip() for line in lines if line.strip()] + for j, line in enumerate(lines[:number_of_lines]): + grouped_lines[j].append(line) + + outputs = [] + for group in grouped_lines: + # Instead of joining the lines, keep them as a list + outputs.append(group) + + # Pad the output to always return 20 items + outputs.extend([[] for _ in range(20 - len(outputs))]) + + return tuple(outputs) + + @classmethod + def IS_CHANGED(cls, number_of_inputs, number_of_lines, ** kwargs): + return float("NaN") # This forces the node to always update + + @classmethod + def VALIDATE_INPUTS(cls, number_of_inputs, number_of_lines, **kwargs): + if number_of_lines < 1 or number_of_lines > 20: + return "Number of lines must be between 1 and 20" + if number_of_inputs < 1 or number_of_inputs > 10: + return "Number of inputs must be between 1 and 10" + return True diff --git a/pyproject.toml b/pyproject.toml index e9978e2..5c992c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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.16" +version = "0.17" license = {file = "LICENSE"} [project.urls] diff --git a/screenshots/loop_combined.png b/screenshots/loop_combined.png new file mode 100644 index 0000000..96e3154 Binary files /dev/null and b/screenshots/loop_combined.png differ diff --git a/text_to_speech.py b/text_to_speech.py index 392fd92..36c8d0d 100644 --- a/text_to_speech.py +++ b/text_to_speech.py @@ -40,7 +40,7 @@ class TextToSpeech: RETURN_TYPES = ("AUDIO",) FUNCTION = "generate_audio" - CATEGORY = "audio" + CATEGORY = "Bjornulf" def generate_audio(self, text, language, speaker_wav): # Check if a valid speaker_wav was selected diff --git a/web/js/loop_combine_texts_by_lines.js b/web/js/loop_combine_texts_by_lines.js new file mode 100644 index 0000000..6d150f9 --- /dev/null +++ b/web/js/loop_combine_texts_by_lines.js @@ -0,0 +1,72 @@ +import { app } from "../../../scripts/app.js"; + +app.registerExtension({ + name: "Bjornulf.CombineTextsByLines", + async nodeCreated(node) { + if (node.comfyClass === "Bjornulf_CombineTextsByLines") { + const updateInputsAndOutputs = () => { + const numInputsWidget = node.widgets.find(w => w.name === "number_of_inputs"); + const numLinesWidget = node.widgets.find(w => w.name === "number_of_lines"); + if (!numInputsWidget || !numLinesWidget) return; + + const numInputs = numInputsWidget.value; + const numLines = numLinesWidget.value; + + // Update inputs + if (!node.inputs) { + node.inputs = []; + } + + // Remove excess inputs + node.inputs = node.inputs.filter(input => !input.name.startsWith('text_') || parseInt(input.name.split('_')[1]) <= numInputs); + + // Add new inputs if needed + for (let i = node.inputs.length; i < numInputs; i++) { + const inputName = `text_${i + 1}`; + if (!node.inputs.find(input => input.name === inputName)) { + node.addInput(inputName, "STRING"); + } + } + + // Update outputs + if (!node.outputs) { + node.outputs = []; + } + + // Remove excess outputs + while (node.outputs.length > numLines) { + node.removeOutput(node.outputs.length - 1); + } + + // Add new outputs if needed + while (node.outputs.length < numLines) { + node.addOutput(`Line ${node.outputs.length + 1}`, "STRING", { array: true }); + } + + // Update output labels and types + node.outputs.forEach((output, index) => { + output.name = `line_${index + 1}`; + output.label = `Line ${index + 1}`; + output.type = "STRING"; + output.array = true; + }); + + node.setSize(node.computeSize()); + }; + + // Move control widgets to the top and remove any text area widgets + node.widgets = node.widgets.filter(w => w.name === "number_of_inputs" || w.name === "number_of_lines"); + + // Set up callbacks + node.widgets.forEach(w => { + w.callback = () => { + updateInputsAndOutputs(); + app.graph.setDirtyCanvas(true); + }; + }); + + // Initial update + setTimeout(updateInputsAndOutputs, 0); + } + } +}); \ No newline at end of file