diff --git a/README.md b/README.md index de9ad27..f7b85f4 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # noEmbryo Nodes A diverse set of nodes for ComfyUI. -* [PromptTermList (1-6)](#PromptTermList) -* [Resolution Scale](#ResolutionScale) -* [Regex Text Chopper](#RegexTextChopper) +- [PromptTermList (1-6)](#prompttermlist-1-6) +- [Resolution Scale](#resolution-scale) +- [Regex Text Chopper](#regex-text-chopper) +- [Auto Save Workflow](#auto-save-workflow) You can access them through "Add node > noEmbryo" submenu. @@ -10,7 +11,7 @@ You can access them through "Add node > noEmbryo" submenu. [![License: MIT][MIT]](LICENSE) --- -## PromptTermList (1-6) +## PromptTermList (1-6) These are some nodes that help with the creation of Prompts inside [ComfyUI](https://github.com/comfyanonymous/ComfyUI). @@ -27,20 +28,20 @@ The "label" part is what we see at the node's dropdown menu, and the "value" par These `json` files are located inside the `TermLists` directory, in the node's folder. There are two ways to add a new term. -* From within ComfyUI: - * Connect a text box to the node's `text` input. - * Write the "label"/"value" part in the box using the following format: +- From within ComfyUI: + - Connect a text box to the node's `text` input. + - Write the "label"/"value" part in the box using the following format: ``` label=Descriptive text value=masterpiece, artful and cozy ``` - * Enable the `store_input` switch. - * Run a generation job. - * Refresh the page. -* Manually: - * Just open the `json` file and add/remove/change entries. + - Enable the `store_input` switch. + - Run a generation job. + - Refresh the page. +- Manually: + - Just open the `json` file and add/remove/change entries. - Of course we must be very careful with this, to keep the `json` format of labels/values (with the appropriate commas), otherwise the file will not be parsed. + Ofcourse we must be very careful with this, to keep the `json` format of labels/values (with the appropriate commas), otherwise the file will not be parsed. This `text` input is also useful if we want to manually add something *after* our term, or as the *only* term if we select the `None` label of the dropdown. The `strength` value is changing the impact of the term by using the parenthesis format like this: `(a great term:1.3)` @@ -51,16 +52,30 @@ label=The label to be deleted value= ``` --- -## Resolution Scale +## Resolution Scale ![Demo workflow](https://raw.githubusercontent.com/noembryo/ComfyUI-noEmbryo/master/stuff/res_scale1.png) A simple node that outputs the resolution of an image using the dimensions of an input image or some custom user-defined dimensions, using a Scale Factor. If there is an input image connected, setting either `width` or `height` to 0 will use the other dimension to scale the image (but always multiple of 4). --- -## Regex Text Chopper +## Regex Text Chopper ![Demo workflow](https://raw.githubusercontent.com/noembryo/ComfyUI-noEmbryo/master/stuff/regex_text.png) -A node that "chops" a text using a regular expression and outputs the chopped parts of the text. +A node that "chops" a text using a regular expression and outputs the chopped parts of the text. + +--- +## Auto Save Workflow +![Demo workflow](https://raw.githubusercontent.com/noembryo/ComfyUI-noEmbryo/master/stuff/auto_save_workflow.png) +This node can save the current workflow to a `.json` file, every time a generation job is run. +- **UI controls** + - **save_directory** is the directory where the `.json` file will be saved. + It can be a relative path inside the ComfyUI's output directory, or an absolute path of your hard drive. + - **filename** is the name of the `.json` file. + It can contain the `{timestamp}` placeholder, which will be replaced with the current timestamp (for unique filenames). + - **trigger** switch will enable (or not) the saving of the workflow. +- **Outputs** + - **status** outputs a text with the status of the saving process (the path of the saved file, or an error message). + - **✳️trigger** is a dummy output that's used to trigger the saving process, even if nothing is connected to the node. --- ## Installation diff --git a/nodes.py b/nodes.py index 3030ea6..b2269f5 100644 --- a/nodes.py +++ b/nodes.py @@ -1,7 +1,8 @@ -import io -import re +import os, re, io import json from os.path import realpath, join, dirname +from datetime import datetime +import folder_paths MANIFEST = {"name": "noEmbryo Nodes", "version": (1, 0, 3), @@ -11,7 +12,7 @@ MANIFEST = {"name": "noEmbryo Nodes", "license": "MIT", } __author__ = "noEmbryo" -__version__ = "1.0.5" +__version__ = "1.1.0" # LISTS_PATH = realpath("./custom_nodes/ComfyUI-noEmbryo/TermLists/") LISTS_PATH = join(dirname(realpath(__file__)), "TermLists") @@ -262,6 +263,77 @@ class RegExTextChopper: return text1, text2, text3, text4, text_all +class AutoSaveWorkflow: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "save_directory": ("STRING", { + "default": "saved_workflows", + "tooltip": "Relative to ComfyUI output directory or absolute path" + }), + "filename": ("STRING", { + "default": "workflow_{timestamp}", + "tooltip": "Filename (include {timestamp} for unique timestamps)" + }), + "trigger": ("BOOLEAN", { + "default": True, + "label_on": "Enabled", + "label_off": "Disabled", + "tooltip": "Save the workflow if Enabled" + }), + }, + "hidden": { + "prompt": "PROMPT", + "extra_pnginfo": "EXTRA_PNGINFO", + "trigger": "BOOLEAN", # Hidden trigger input + }, + } + + RETURN_TYPES = ("STRING", "BOOLEAN") + RETURN_NAMES = ("status", "✳️trigger") + OUTPUT_TOOLTIPS = ("Get a status report text", + "Dammy output, to trigger execution if nothing is connected") + FUNCTION = "execute" + CATEGORY = "utils" + OUTPUT_NODE = True + + def execute(self, trigger, save_directory, filename, prompt=None, extra_pnginfo=None): + status = "Trigger disabled - workflow not saved" + + if trigger: + try: + workflow_data = extra_pnginfo.get("workflow", {}) if extra_pnginfo else {} + + # Process save directory + if os.path.isabs(save_directory): + output_dir = save_directory + else: + output_dir = os.path.join(folder_paths.get_output_directory(), + save_directory) + os.makedirs(output_dir, exist_ok=True) + + # Process filename with timestamp + timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") + processed_filename = filename.replace("{timestamp}", timestamp) + + # Ensure .json extension + if not processed_filename.lower().endswith('.json'): + processed_filename += '.json' + + save_path = os.path.join(output_dir, processed_filename) + + # Save workflow to JSON + with open(save_path, "w", encoding="utf-8") as f: + json.dump(workflow_data, f, indent=4) + + status = f"Workflow saved to: {save_path}" + except Exception as e: + status = f"Error saving workflow: {str(e)}" + + return (status,) + + NODE_CLASS_MAPPINGS = {"PromptTermList1": PromptTermList1, "PromptTermList2": PromptTermList2, "PromptTermList3": PromptTermList3, @@ -270,6 +342,7 @@ NODE_CLASS_MAPPINGS = {"PromptTermList1": PromptTermList1, "PromptTermList6": PromptTermList6, f"Resolution Scale /{__author__}": ResolutionScale, f"Regex Text Chopper /{__author__}": RegExTextChopper, + f"Auto Save Workflow /{__author__}": AutoSaveWorkflow } NODE_DISPLAY_NAME_MAPPINGS = {"PromptTermList1": f"PromptTermList 1 /{__author__}", @@ -282,4 +355,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {"PromptTermList1": f"PromptTermList 1 /{__author__ f"Resolution Scale /{__author__}", f"Regex Text Chopper /{__author__}": f"Regex Text Chopper /{__author__}", + f"Auto Save Workflow /{__author__}": + f"Auto Save Workflow /{__author__}", } diff --git a/pyproject.toml b/pyproject.toml index 55d2b8b..a3c1fb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,10 +2,12 @@ name = "comfyui-noembryo" description = """ A collection of nodes for ComyUI. -- "PromptTermList (1-6)" nodes to help with the creation of Prompts. -- "Resolution Scale" is a node that resizes an image using pixels or scale factor. -- "Regex Text Chopper" is a node that cuts text based on regex.""" -version = "1.0.5" +* "PromptTermList (1-6)" nodes to help with the creation of Prompts. +* "Resolution Scale" is a node that resizes an image using pixels or scale factor. +* "Regex Text Chopper" is a node that cuts text based on regex. +* "Auto Save Workflow" can save the current workflow to a .json file automatically. +""" +version = "1.1.0" license = {text = "MIT License"} [project.urls] diff --git a/stuff/auto_save_workflow.png b/stuff/auto_save_workflow.png new file mode 100644 index 0000000..90db641 Binary files /dev/null and b/stuff/auto_save_workflow.png differ