diff --git a/History.md b/History.md new file mode 100644 index 0000000..3c1b3a3 --- /dev/null +++ b/History.md @@ -0,0 +1,5 @@ + +v0.0.1 / 2024-01-19 +================== + + * Initial release diff --git a/README.md b/README.md index 0f67253..7d3758c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,26 @@ DarkPrompts ----------- -A better input for the WAS Text Concatenate box +A slightly better tool for generating random prompts. -DarkReader allows you to load lines from a file and/or lines from a text box, and pick a random line based on the seed. +DarkPrompt +========== +DarkPrompt reads lines from a file and/or a text input, combines them together, optionally strips out comments and blank lines, and then selects a random line for use in generating your prompt based on a seed input. It also has a "prefix" and "suffix" field so you can easily include additional information with each prompt or wrap it inside a weight. For example a prefix of '(' and a suffix of ':1.2)' could return a string: ```(walking a dog:1.2)``` from an file named ```activities.txt``` that contains a line ```walking a dog```. -DarkReader also allows stripping out comments and blank lines during processing +Unlike several other ComfyUI projects, the box is somewhat more compact and gives you an easy way to combine file and text sources for prompt generation. + +DarkCombiner +============ +DarkCombiner accepts up to 7 text inputs and combines them with an optional delimiter (\n by default) with a string output. This allows you to easily combine multiple DarkPrompt nodes to generate complex prompts. + +Screenshot +========== +![DarkPrompt](assets/workflow.png "DarkPrompt") + +Installation Using ComfyUI Manager +================================== +If you are using ComfyUI Manager, copy the URL of this repo and paste it into the "Install via Git URL" box and restart ComfyUI. + +Installation into ComfyUI +========================= +Go into your custom_nodes directory and run a 'git clone https://github.com/darkpixel/darkprompts.git' and then restart ComfyUI. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..260c6f1 --- /dev/null +++ b/__init__.py @@ -0,0 +1,199 @@ +__version__ = "0.0.1" + +import logging +import random +import re + +logger = logging.getLogger(__name__) + + +def strip_blanks_from_lines(lines): + cleaned = [] + for line in lines: + if len(str(line).strip()) == 0: + pass + else: + cleaned.append(line.strip()) + return cleaned + + +def strip_comments_from_lines(lines): + cleaned = [] + for line in lines: + if "#" in line: + cleaned.append(re.sub(r"#.*$|//.*$", "", str(line))) + else: + cleaned.append(line) + return cleaned + + +class DarkCombine(object): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": {}, + "optional": { + "delimiter": ("STRING", {"default": "\n"}), + "text_a": ( + "STRING", + {"forceInput": True}, + ), + "text_b": ( + "STRING", + {"forceInput": True}, + ), + "text_c": ( + "STRING", + {"forceInput": True}, + ), + "text_d": ( + "STRING", + {"forceInput": True}, + ), + "text_e": ( + "STRING", + {"forceInput": True}, + ), + "text_f": ( + "STRING", + {"forceInput": True}, + ), + "text_g": ( + "STRING", + {"forceInput": True}, + ), + }, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "concatenate_inputs" + + CATEGORY = "DarkPrompt" + + def concatenate_inputs(self, delimiter="\n", **kwargs): + text = [] + if delimiter == "\\n": + delimiter = "\n" + + for key in sorted(kwargs.keys()): + val = kwargs[key] + if len(val.strip()) > 0: + text.append(val) + + print(delimiter.join(text)) + return (delimiter.join(text),) + + +class DarkPrompt(object): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + logger.info("darkloader init") + + @classmethod + def INPUT_TYPES(s): + """ + Return a dictionary which contains config for all input fields. + Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT". + Input types "INT", "STRING" or "FLOAT" are special values for fields on the node. + The type can be a list for selection. + + Returns: `dict`: + - Key input_fields_group (`string`): Can be either required, hidden or optional. A node class must have property `required` + - Value input_fields (`dict`): Contains input fields config: + * Key field_name (`string`): Name of a entry-point method's argument + * Value field_config (`tuple`): + + First value is a string indicate the type of field or a list for selection. + + Secound value is a config for type "INT", "STRING" or "FLOAT". + """ + return { + "required": { + "strip_blank_lines": ("BOOLEAN", {"default": 1}), + "strip_comments": ("BOOLEAN", {"default": 1}), + "seed": ( + "INT", + { + "default": 0, + "min": 0, + "max": 0xFFFFFFFFFFFFFFFF, + "forceInput": True, + }, + ), + }, + "optional": { + "filename": ("STRING", {"default": None}), + "prefix": ("STRING", {"default": None}), + "suffix": ("STRING", {"default": None}), + "text": ("STRING", {"default": None, "multiline": True}), + }, + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("string",) + FUNCTION = "dark_prompt" + # OUTPUT_NODE = False + CATEGORY = "DarkPrompt" + + def dark_prompt( + self, + strip_blank_lines, + strip_comments, + seed, + filename=None, + text=None, + prefix=None, + suffix=None, + ): + logger.info("DarkPrompt called with filename %s" % (filename)) + + lines = [] + + if not text and not filename: + raise ValueError("DarkPrompt text and filename can not both be blank") + + if filename: + print("Trying to read %s" % (filename)) + try: + f = open(filename, "r") + file_lines = f.read().splitlines() + for line in file_lines: + lines.append(line) + print("Successfully read %s" % (filename)) + except FileNotFoundError: + logger.error("File not found: %s" % (filename)) + + if text: + for text_line in text.splitlines(): + lines.append(text_line) + + if strip_comments: + lines = strip_comments_from_lines(lines) + + if strip_blank_lines: + lines = strip_blanks_from_lines(lines) + + random.seed(seed) + ret = str(random.choice(lines)) + + if prefix: + ret = prefix + ret + + if suffix: + ret = ret + suffix + + return (ret,) + + +NODE_CLASS_MAPPINGS = { + "DarkCombine": DarkCombine, + "DarkPrompt": DarkPrompt, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "DarkCombine": "Dark Combiner", + "DarkPrompt": "Dark Prompt", +} + +__all__ = [NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS] diff --git a/assets/workflow.png b/assets/workflow.png new file mode 100644 index 0000000..28a02b6 Binary files /dev/null and b/assets/workflow.png differ