diff --git a/README.md b/README.md index 683ed63..ff710de 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Python](https://img.shields.io/badge/Python-3.8+-blue.svg)](https://python.org) [![ComfyUI](https://img.shields.io/badge/ComfyUI-Compatible-green.svg)](https://github.com/comfyanonymous/ComfyUI) [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) -[![Version](https://img.shields.io/badge/Version-1.4.0-informational.svg)](https://github.com/DebugPadawan/DebugPadawans-ComfyUI-Essentials/releases) +[![Version](https://img.shields.io/badge/Version-1.5.0-informational.svg)](https://github.com/DebugPadawan/DebugPadawans-ComfyUI-Essentials/releases) [![GitHub Stars](https://img.shields.io/github/stars/DebugPadawan/DebugPadawans-ComfyUI-Essentials.svg)](https://github.com/DebugPadawan/DebugPadawans-ComfyUI-Essentials/stargazers) @@ -301,6 +301,95 @@ The "all" mode removes all whitespace and collapses multiple spaces into single +
+๐Ÿ”ค Text Case Converter - Transform text case styles + +**Category:** `DebugPadawan/Text` + +Converts text to different case styles for formatting prompts, filenames, and display text. + +**๐Ÿ“ฅ Inputs:** +- `text` *(String)*: The text to convert +- `case_mode` *(List)*: Case style - UPPER, lower, Title Case, Sentence case, snake_case, kebab-case, camelCase, PascalCase + +**๐Ÿ“ค Outputs:** +- `converted_text` *(String)*: The converted text + +**๐Ÿ’ก Example:** +``` +Input: "hello world" +Mode: "Title Case" +Output: "Hello World" + +Input: "Hello World" +Mode: "snake_case" +Output: "hello_world" + +Input: "hello world" +Mode: "PascalCase" +Output: "HelloWorld" +``` + +Normalize prompts, format filenames, convert naming conventions between different systems. + +
+ +
+โœ‚๏ธ Text Trimmer - Advanced text trimming options + +**Category:** `DebugPadawan/Text` + +Trims text with various whitespace removal options. + +**๐Ÿ“ฅ Inputs:** +- `text` *(String)*: The text to trim +- `mode` *(List)*: Trim mode - both, start, end, all_whitespace, collapse_spaces + +**๐Ÿ“ค Outputs:** +- `trimmed_text` *(String)*: The trimmed text + +**๐Ÿ’ก Example:** +``` +Input: " Hello World " +Mode: "collapse_spaces" +Output: "Hello World" + +Mode: "all_whitespace" +Output: "HelloWorld" +``` + +Clean up prompt formatting, remove extra spaces, prepare text for further processing. + +
+ +
+๐Ÿ“Ž Text Prefix Suffix - Add prefix and/or suffix to text + +**Category:** `DebugPadawan/Text` + +Adds prefix and/or suffix to text with optional spacing. + +**๐Ÿ“ฅ Inputs:** +- `text` *(String)*: The base text +- `prefix` *(String, optional)*: Text to add at the beginning +- `suffix` *(String, optional)*: Text to add at the end +- `add_space` *(Boolean)*: Add space between parts (default: True) + +**๐Ÿ“ค Outputs:** +- `result_text` *(String)*: The modified text + +**๐Ÿ’ก Example:** +``` +Text: "beautiful" +Prefix: "a" +Suffix: "sunset" +Output: "a beautiful sunset" +``` + +Build complex prompts from components, wrap text with markers, add modifiers to prompts. + +
+ --- ### ๐Ÿ“ฆ Data Conversion @@ -551,6 +640,115 @@ Get the first N images of a batch, skip the first item, or take a specific range +
+๐ŸŽฒ Random List Selector - Pick random items from a list + +**Category:** `DebugPadawan/List` + +Randomly selects one or more items from a list with seed-based reproducibility. + +**๐Ÿ“ฅ Inputs:** +- `input_list` *(List)*: The input list. +- `seed` *(Integer)*: Seed for reproducible random selection. +- `count` *(Integer)*: Number of items to select (default: 1). +- `allow_duplicates` *(Boolean)*: Allow selecting the same item multiple times. + +**๐Ÿ“ค Outputs:** +- `selected_items` *(List)*: The randomly selected items. +- `count` *(Integer)*: Number of items selected. +- `first_item` *(Any)*: The first selected item (convenient for single selections). + +**๐Ÿ’ก Example:** +``` +Input List: ["style1", "style2", "style3", "style4", "style5"] +Seed: 42 +Count: 2 +Output: ["style3", "style1"] +``` + +Perfect for randomizing prompt styles, selecting random seeds, or picking random colors/tags from lists. + +
+ +
+๐Ÿ”€ List Shuffler - Shuffle list order deterministically + +**Category:** `DebugPadawan/List` + +Shuffles a list into random order using a seed for reproducibility. + +**๐Ÿ“ฅ Inputs:** +- `input_list` *(List)*: The input list. +- `seed` *(Integer)*: Seed for reproducible shuffling. + +**๐Ÿ“ค Outputs:** +- `shuffled_list` *(List)*: The shuffled list. +- `count` *(Integer)*: Number of items. + +**๐Ÿ’ก Example:** +``` +Input: ["a", "b", "c", "d"] +Seed: 123 +Output: ["c", "a", "d", "b"] +``` + +Randomize processing order, shuffle prompt components, or reorder batch elements. + +
+ +
+๐Ÿ”— List Merger - Combine multiple lists + +**Category:** `DebugPadawan/List` + +Merges two lists together with different modes. + +**๐Ÿ“ฅ Inputs:** +- `list_a` *(List)*: First list. +- `list_b` *(List)*: Second list. +- `mode` *(List)*: Merge mode - "concatenate" or "interleave". + +**๐Ÿ“ค Outputs:** +- `merged_list` *(List)*: The merged list. +- `count` *(Integer)*: Number of items. + +**๐Ÿ’ก Example:** +``` +List A: [1, 2, 3] +List B: ["a", "b", "c"] +Mode: "interleave" +Output: [1, "a", 2, "b", 3, "c"] +``` + +Combine tag lists, merge processing pipelines, or interleave prompt components. + +
+ +
+๐Ÿงน List Deduplicator - Remove duplicates from a list + +**Category:** `DebugPadawan/List` + +Removes duplicate items from a list while preserving order. + +**๐Ÿ“ฅ Inputs:** +- `input_list` *(List)*: The input list. + +**๐Ÿ“ค Outputs:** +- `deduplicated_list` *(List)*: List with duplicates removed. +- `count` *(Integer)*: Number of unique items. +- `removed_count` *(Integer)*: Number of duplicates removed. + +**๐Ÿ’ก Example:** +``` +Input: ["apple", "banana", "apple", "orange", "banana"] +Output: ["apple", "banana", "orange"], count: 3, removed: 2 +``` + +Clean up tag lists, remove redundant prompts, or filter unique values. + +
+ --- ### ๐Ÿ”„ Type Conversion @@ -1012,7 +1210,7 @@ Data โ†’ Wait (2.0s) โ†’ Processing โ†’ Wait (1.0s) โ†’ Output | Category | Nodes | Purpose | |----------|-------|---------| -| **DebugPadawan/Text** | Text Splitter, Text Joiner, Text Replace, Text Regex, String Formatter, Text Compare, Text Length, Text Case, Text Trim | Text manipulation and processing | +| **DebugPadawan/Text** | Text Splitter, Text Joiner, Text Replace, Text Regex, String Formatter, Text Compare, Text Length, Text Case, Text Trim, Text Case Converter, Text Trimmer, Text Prefix Suffix | Text manipulation and processing | | **DebugPadawan/JSON** | TextToJSON, JSONToText | Text to JSON conversion | | **DebugPadawan/Debug** | Debug Print | Debugging and monitoring | | **DebugPadawan/Timing** | Wait | Timing control and delays | @@ -1020,7 +1218,7 @@ Data โ†’ Wait (2.0s) โ†’ Processing โ†’ Wait (1.0s) โ†’ Output | **DebugPadawan/Logic** | Conditional String, Logic Gate | Conditional operations | | **DebugPadawan/Image** | Image Info, Color Palette Extractor | Image tensor analysis and color extraction | | **DebugPadawan/Math** | Int/Float Math Operation, Random Generator | Basic arithmetic and random number generation | -| **DebugPadawan/List** | Get List Item, List Slicer | List manipulation | +| **DebugPadawan/List** | Get List Item, List Slicer, Random List Selector, List Shuffler, List Merger, List Deduplicator | List manipulation | | **DebugPadawan/Conversion** | Type Converter, Int to Float, Float to Int, String to Int, String to Float, String to Boolean, Number to String | Type conversion utilities | | **DebugPadawan/Number** | Number Clamp, Number Round, Number Range, Number Compare, Number Absolute, Number Sign, Number Remap, Number Lerp | Number formatting and utilities | diff --git a/__init__.py b/__init__.py index 174ecdc..4564547 100644 --- a/__init__.py +++ b/__init__.py @@ -67,5 +67,5 @@ NODE_DISPLAY_NAME_MAPPINGS = { __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] # Version info -__version__ = "1.4.0" +__version__ = "1.5.0" __author__ = "DebugPadawan" \ No newline at end of file diff --git a/nodes/list_nodes.py b/nodes/list_nodes.py index 9a20270..467549e 100644 --- a/nodes/list_nodes.py +++ b/nodes/list_nodes.py @@ -1,4 +1,6 @@ import torch +import random + class GetListItem: @classmethod @@ -87,10 +89,160 @@ class ListInfo: return (count, first_item, last_item) +class RandomListSelector: + """ + Node for randomly selecting one or more items from a list + Supports seeded randomness for reproducible selections + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_list": ("LIST",), + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + "count": ("INT", {"default": 1, "min": 1}), + "allow_duplicates": ("BOOLEAN", {"default": False}), + } + } + + RETURN_TYPES = ("LIST", "INT", "*") + RETURN_NAMES = ("selected_items", "count", "first_item") + + FUNCTION = "select_random" + CATEGORY = "DebugPadawan/List" + + def select_random(self, input_list, seed, count, allow_duplicates): + if not input_list: + raise ValueError("Input list is empty.") + + rng = random.Random(seed) + list_len = len(input_list) + + if count > list_len and not allow_duplicates: + count = list_len + + if allow_duplicates: + selected = [rng.choice(input_list) for _ in range(count)] + else: + selected = rng.sample(input_list, min(count, list_len)) + + first_item = selected[0] if selected else None + return (selected, len(selected), first_item) + + +class ListShuffler: + """ + Node for shuffling a list with a deterministic seed + Useful for randomizing order while maintaining reproducibility + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_list": ("LIST",), + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + } + } + + RETURN_TYPES = ("LIST", "INT") + RETURN_NAMES = ("shuffled_list", "count") + + FUNCTION = "shuffle_list" + CATEGORY = "DebugPadawan/List" + + def shuffle_list(self, input_list, seed): + if not input_list: + return ([], 0) + + # Create a copy to avoid modifying the original + shuffled = list(input_list) + rng = random.Random(seed) + rng.shuffle(shuffled) + + return (shuffled, len(shuffled)) + + +class ListMerger: + """ + Node for merging multiple lists together + Supports concatenation and interleaving modes + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "list_a": ("LIST",), + "list_b": ("LIST",), + "mode": (["concatenate", "interleave"],), + } + } + + RETURN_TYPES = ("LIST", "INT") + RETURN_NAMES = ("merged_list", "count") + + FUNCTION = "merge_lists" + CATEGORY = "DebugPadawan/List" + + def merge_lists(self, list_a, list_b, mode): + if mode == "concatenate": + result = list_a + list_b + else: # interleave + result = [] + max_len = max(len(list_a), len(list_b)) + for i in range(max_len): + if i < len(list_a): + result.append(list_a[i]) + if i < len(list_b): + result.append(list_b[i]) + + return (result, len(result)) + + +class ListDeduplicator: + """ + Node for removing duplicate items from a list + Preserves original order + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_list": ("LIST",), + } + } + + RETURN_TYPES = ("LIST", "INT", "INT") + RETURN_NAMES = ("deduplicated_list", "count", "removed_count") + + FUNCTION = "deduplicate" + CATEGORY = "DebugPadawan/List" + + def deduplicate(self, input_list): + seen = [] + result = [] + for item in input_list: + # Convert to string for comparison (handles non-hashable types) + item_key = str(item) + if item_key not in seen: + seen.append(item_key) + result.append(item) + + removed = len(input_list) - len(result) + return (result, len(result), removed) + + NODE_CLASS_MAPPINGS = { "DebugPadawan_GetListItem": GetListItem, "DebugPadawan_ListSlicer": ListSlicer, "DebugPadawan_ListInfo": ListInfo, + "DebugPadawan_RandomListSelector": RandomListSelector, + "DebugPadawan_ListShuffler": ListShuffler, + "DebugPadawan_ListMerger": ListMerger, + "DebugPadawan_ListDeduplicator": ListDeduplicator, # Alias for backward compatibility "DP_GetListItem": GetListItem, } @@ -99,5 +251,9 @@ NODE_DISPLAY_NAME_MAPPINGS = { "DebugPadawan_GetListItem": "Get List Item", "DebugPadawan_ListSlicer": "List Slicer", "DebugPadawan_ListInfo": "List Info", + "DebugPadawan_RandomListSelector": "Random List Selector", + "DebugPadawan_ListShuffler": "List Shuffler", + "DebugPadawan_ListMerger": "List Merger", + "DebugPadawan_ListDeduplicator": "List Deduplicator", "DP_GetListItem": "Get List Item (Legacy)", } diff --git a/nodes/text_processing.py b/nodes/text_processing.py index 6168f08..ad2d39c 100644 --- a/nodes/text_processing.py +++ b/nodes/text_processing.py @@ -6,7 +6,7 @@ class TextSplitter: """ Node for splitting text strings by a delimiter """ - + @classmethod def INPUT_TYPES(cls): return { @@ -29,25 +29,25 @@ class TextSplitter: }), } } - + RETURN_TYPES = ("LIST", "INT") RETURN_NAMES = ("text_list", "count") - + FUNCTION = "split_text" CATEGORY = "DebugPadawan/Text" - + def split_text(self, text, delimiter, strip_whitespace=True, remove_empty=True): """ Split text by delimiter and return list with count """ result = text.split(delimiter) - + if strip_whitespace: result = [item.strip() for item in result] - + if remove_empty: result = [item for item in result if item] - + return (result, len(result)) @@ -55,7 +55,7 @@ class TextJoiner: """ Node for joining a list of strings with a delimiter """ - + @classmethod def INPUT_TYPES(cls): return { @@ -77,13 +77,13 @@ class TextJoiner: }), } } - + RETURN_TYPES = ("STRING",) RETURN_NAMES = ("joined_text",) - + FUNCTION = "join_text" CATEGORY = "DebugPadawan/Text" - + def join_text(self, text_list, delimiter, prefix="", suffix=""): str_list = [str(item) for item in text_list] result = delimiter.join(str_list) @@ -95,7 +95,7 @@ class TextReplace: """ Node for replacing specific text in a string """ - + @classmethod def INPUT_TYPES(cls): return { @@ -114,13 +114,13 @@ class TextReplace: }), } } - + RETURN_TYPES = ("STRING",) RETURN_NAMES = ("text",) - + FUNCTION = "replace_text" CATEGORY = "DebugPadawan/Text" - + def replace_text(self, text, find, replace): if not find: return (text,) @@ -131,7 +131,7 @@ class TextRegex: """ Node for searching and replacing text using regular expressions """ - + @classmethod def INPUT_TYPES(cls): return { @@ -150,28 +150,192 @@ class TextRegex: }), } } - + RETURN_TYPES = ("STRING", "LIST", "BOOLEAN") RETURN_NAMES = ("text", "matches", "found") - + FUNCTION = "regex_op" CATEGORY = "DebugPadawan/Text" - + def regex_op(self, text, pattern, replace): if not pattern: return (text, [], False) - + matches = re.findall(pattern, text) result = re.sub(pattern, replace, text) - + return (result, matches, len(matches) > 0) +class TextCaseConverter: + """ + Node for converting text to different case styles + Useful for formatting prompts, filenames, and display text + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "text": ("STRING", { + "multiline": True, + "default": "hello world" + }), + "case_mode": (["UPPER", "lower", "Title Case", "Sentence case", "snake_case", "kebab-case", "camelCase", "PascalCase"],), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("converted_text",) + + FUNCTION = "convert_case" + CATEGORY = "DebugPadawan/Text" + + def convert_case(self, text, case_mode): + """ + Convert text to the specified case style + """ + if case_mode == "UPPER": + return (text.upper(),) + elif case_mode == "lower": + return (text.lower(),) + elif case_mode == "Title Case": + return (text.title(),) + elif case_mode == "Sentence case": + # Capitalize first letter of each sentence + result = '. '.join(s.capitalize() for s in text.split('. ')) + return (result,) + elif case_mode == "snake_case": + # Convert to snake_case + # First normalize spaces and existing separators + normalized = text.lower().replace('-', ' ').replace('_', ' ') + words = normalized.split() + return ('_'.join(words),) + elif case_mode == "kebab-case": + # Convert to kebab-case + normalized = text.lower().replace('_', ' ').replace('-', ' ') + words = normalized.split() + return ('-'.join(words),) + elif case_mode == "camelCase": + # Convert to camelCase + normalized = text.replace('-', ' ').replace('_', ' ') + words = normalized.split() + if not words: + return ("",) + result = words[0].lower() + ''.join(w.capitalize() for w in words[1:]) + return (result,) + elif case_mode == "PascalCase": + # Convert to PascalCase + normalized = text.replace('-', ' ').replace('_', ' ') + words = normalized.split() + result = ''.join(w.capitalize() for w in words) + return (result,) + else: + return (text,) + + +class TextTrimmer: + """ + Node for trimming text with various options + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "text": ("STRING", { + "multiline": True, + "default": " hello world " + }), + "mode": (["both", "start", "end", "all_whitespace", "collapse_spaces"],), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("trimmed_text",) + + FUNCTION = "trim_text" + CATEGORY = "DebugPadawan/Text" + + def trim_text(self, text, mode): + """ + Trim text based on the specified mode + """ + if mode == "both": + return (text.strip(),) + elif mode == "start": + return (text.lstrip(),) + elif mode == "end": + return (text.rstrip(),) + elif mode == "all_whitespace": + # Remove all whitespace characters + return (''.join(text.split()),) + elif mode == "collapse_spaces": + # Collapse multiple spaces into single spaces + return (' '.join(text.split()),) + else: + return (text,) + + +class TextPrefixSuffix: + """ + Node for adding prefix and/or suffix to text + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "text": ("STRING", { + "multiline": True, + "default": "beautiful" + }), + }, + "optional": { + "prefix": ("STRING", { + "multiline": False, + "default": "" + }), + "suffix": ("STRING", { + "multiline": False, + "default": "" + }), + "add_space": ("BOOLEAN", {"default": True}), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("result_text",) + + FUNCTION = "add_prefix_suffix" + CATEGORY = "DebugPadawan/Text" + + def add_prefix_suffix(self, text, prefix="", suffix="", add_space=True): + result = text + + if prefix: + if add_space and text and not text.startswith(' '): + result = prefix + ' ' + result + else: + result = prefix + result + + if suffix: + if add_space and text and not text.endswith(' '): + result = result + ' ' + suffix + else: + result = result + suffix + + return (result,) + + NODE_CLASS_MAPPINGS = { "DebugPadawan_TextSplitter": TextSplitter, - "DebugPadawan_TextJoiner": TextJoiner, + "DebugPadawan_TextJoiner": TextJoiner, "DebugPadawan_TextReplace": TextReplace, "DebugPadawan_TextRegex": TextRegex, + "DebugPadawan_TextCaseConverter": TextCaseConverter, + "DebugPadawan_TextTrimmer": TextTrimmer, + "DebugPadawan_TextPrefixSuffix": TextPrefixSuffix, } NODE_DISPLAY_NAME_MAPPINGS = { @@ -179,4 +343,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "DebugPadawan_TextJoiner": "Text Joiner", "DebugPadawan_TextReplace": "Text Replace", "DebugPadawan_TextRegex": "Text Regex (Search & Replace)", + "DebugPadawan_TextCaseConverter": "Text Case Converter", + "DebugPadawan_TextTrimmer": "Text Trimmer", + "DebugPadawan_TextPrefixSuffix": "Text Prefix Suffix", }