From 5a8d04186516625c752c604a0e8a8369faac5feb Mon Sep 17 00:00:00 2001 From: AWWalker <61161204+EmAySee@users.noreply.github.com> Date: Wed, 18 Mar 2026 16:20:23 -0400 Subject: [PATCH] YAU - 4 --- .editorconfig | 2 +- .gitattributes | 2 +- .gitignore | 38 ++++---- __init__.py | 10 +- node.zip | Bin 70697 -> 70695 bytes py/EmAySee_AnyPassthrough.py | 20 ++-- py/EmAySee_CalculateAspectRatio.py | 2 +- py/EmAySee_CheckboxFloatNode.py | 6 +- py/EmAySee_ConditionalResize.py | 16 ++-- py/EmAySee_ContextManager.py | 14 +-- py/EmAySee_ContextManagerRWNodes_V3.py | 12 +-- py/EmAySee_ContextManager_V2.py | 4 +- py/EmAySee_ContextWriter_V5.py | 12 +-- py/EmAySee_DeepReasoningConnector.py | 2 +- py/EmAySee_DynamicStringSelectorNode.py | 6 +- py/EmAySee_EphemeralPreview.py | 16 ++-- py/EmAySee_GetModelPath.py | 4 +- py/EmAySee_HostPinger.py | 46 ++++----- py/EmAySee_ImagePassthrough.py | 18 ++-- py/EmAySee_ImagePreviewPassthrough.py | 30 +++--- py/EmAySee_IntegerStringSelectorNode.py | 8 +- py/EmAySee_IsGreaterThan.py | 18 ++-- py/EmAySee_LLMOutputCleaner.py | 12 +-- py/EmAySee_LoRAMetadataExtractor.py | 60 ++++++------ py/EmAySee_LoRAMetadataExtractorTagDrop.py | 64 ++++++------- py/EmAySee_LoraFilePicker.py | 88 ++++++++--------- py/EmAySee_LoraLoaderFromInput.py | 10 +- py/EmAySee_LoraSelectionNodes.py | 6 +- py/EmAySee_LoraSelector.py | 10 +- py/EmAySee_Lora_Path-Name-Selector.py | 90 +++++++++--------- py/EmAySee_MetadataFormatter.py | 42 ++++---- py/EmAySee_ProbabilityStringSelectorNode.py | 14 +-- py/EmAySee_QwenPixelAligner.py | 18 ++-- py/EmAySee_RandomIntFromList.py | 18 ++-- py/EmAySee_RandomIntFromList2.py | 18 ++-- py/EmAySee_RandomIntegerFromListNode.py | 2 +- ...domIntegerFromTogglesNode_PremadeLabels.py | 6 +- py/EmAySee_RandomLoraSelector.py | 10 +- py/EmAySee_RandomStringSelectorNode.py | 10 +- py/EmAySee_RandomStringSelectorNode2.py | 10 +- ...ySee_RandomStringSelectorNodeFourChoice.py | 14 +-- ...See_RandomStringSelectorNodeThreeChoice.py | 12 +-- py/EmAySee_RemoveDuplicatesFromString.py | 40 ++++---- py/EmAySee_RemoveDuplicatesFromString_V2.py | 36 +++---- py/EmAySee_RemoveWordsFromText.py | 36 +++---- py/EmAySee_RepaintKSampler.py | 12 +-- py/EmAySee_SaveImage.py | 20 ++-- py/EmAySee_SaveTextToFile.py | 42 ++++---- py/EmAySee_SelectiveModelUnloader.py | 6 +- py/EmAySee_StringPoseSelectorNode.py | 8 +- py/EmAySee_StringTupleInputNode.py | 4 +- ...itToOobaboogaAPI-withKey-ThinkingParser.py | 12 +-- ...AySee_SubmitToOobaboogaAPI-withKey-test.py | 48 +++++----- py/EmAySee_SubmitToOobaboogaAPI-withKey.py | 46 ++++----- py/EmAySee_SubmitToOobaboogaAPI.py | 44 ++++----- ...mitToOobaboogaAPIWithKey_WithThinkParse.py | 8 +- py/EmAySee_ToggleIntNode.py | 4 +- py/EmAySee_VarTextReplacer.py | 44 ++++----- py/EmAySee_VeryUniqueStringSelectorNode.py | 8 +- py/EmAySee_VisualMachine_SaveImage.py | 8 +- ...e_WANVAECompatibleAspectRatioCalculator.py | 10 +- py/EmAySee_WD14TagFilter.py | 14 +-- py/emaysee_lora_filepath-name-selector.x | 90 +++++++++--------- py/emaysee_multiplier_node.py | 26 ++--- 64 files changed, 683 insertions(+), 683 deletions(-) diff --git a/.editorconfig b/.editorconfig index 2f7ebc3..d4a2c44 100755 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -# http://editorconfig.org +# http://editorconfig.org root = true diff --git a/.gitattributes b/.gitattributes index 20e628e..dfe0770 100755 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,2 @@ -# Auto detect text files and perform LF normalization +# Auto detect text files and perform LF normalization * text=auto diff --git a/.gitignore b/.gitignore index 7a0f7bc..d2d9a89 100755 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,17 @@ -# Byte-compiled / optimized / DLL files +# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class -# OSX useful to ignore +# OSX useful to ignore *.DS_Store .AppleDouble .LSOverride -# Thumbnails +# Thumbnails ._* -# Files that might appear in the root of a volume +# Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 @@ -20,17 +20,17 @@ __pycache__/ .VolumeIcon.icns .com.apple.timemachine.donotpresent -# Directories potentially created on remote AFP share +# Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk -# C extensions +# C extensions *.so -# Distribution / packaging +# Distribution / packaging .Python env/ venv/ @@ -49,17 +49,17 @@ var/ .installed.cfg *.egg -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec -# Installer logs +# Installer logs pip-log.txt pip-delete-this-directory.txt -# Unit test / coverage reports +# Unit test / coverage reports htmlcov/ .tox/ .coverage @@ -71,30 +71,30 @@ coverage.xml .hypothesis/ .pytest_cache/ -# Translations +# Translations *.mo *.pot -# Django stuff: +# Django stuff: *.log -# Sphinx documentation +# Sphinx documentation docs/_build/ -# IntelliJ Idea +# IntelliJ Idea .idea *.iml *.ipr *.iws -# PyBuilder +# PyBuilder target/ -# Cookiecutter +# Cookiecutter output/ python_boilerplate/ cookiecutter-pypackage-env/ -# vscode settings +# vscode settings .history/ *.code-workspace diff --git a/__init__.py b/__init__.py index 368a25e..17440ab 100755 --- a/__init__.py +++ b/__init__.py @@ -4,7 +4,7 @@ import os import sys from .pysssss import init, get_ext_dir -# High-Compatibility ANSI Colors +# High-Compatibility ANSI Colors MAGENTA_BRIGHT = "\033[95m" BLUE_BRIGHT = "\033[94m" CYAN_BRIGHT = "\033[96m" @@ -13,8 +13,8 @@ CYAN = "\033[1;36m" MAGENTA = "\033[1;35m" RESET = "\033[0m" -# 5-Line Gradient ASCII Header: EmAySee (Block Style) -# Each line is wrapped individually to force the color shift +# 5-Line Gradient ASCII Header: EmAySee (Block Style) +# Each line is wrapped individually to force the color shift banner = f""" {MAGENTA_BRIGHT} ███████╗███╗ ███╗ █████╗ ██╗ ██╗ ██████╗███████╗███████╗{RESET} {MAGENTA_BRIGHT} ██╔════╝████╗ ████║██╔══██╗╚██╗ ██╔╝██╔════╝██╔════╝██╔════╝{RESET} @@ -49,14 +49,14 @@ if init(): if hasattr(module, "NODE_CLASS_MAPPINGS") and getattr(module, "NODE_CLASS_MAPPINGS") is not None: NODE_CLASS_MAPPINGS.update(module.NODE_CLASS_MAPPINGS) - # Success Checkmark + # Success Checkmark print(f" {GREEN}[✓]{RESET} {module_name}") if hasattr(module, "NODE_DISPLAY_NAME_MAPPINGS") and getattr(module, "NODE_DISPLAY_NAME_MAPPINGS") is not None: NODE_DISPLAY_NAME_MAPPINGS.update(module.NODE_DISPLAY_NAME_MAPPINGS) except Exception as e: - # Failure Cross + # Failure Cross print(f" \033[1;31m[✗]\033[0m {module_name} : Error -> {str(e)}") print(f"{MAGENTA}----------------------------------------------------------{RESET}") diff --git a/node.zip b/node.zip index 465442c05f2fabc72ff7f86b4c70f784fc9345ac..eb9a79ca50c0d3b722a1c01ab22ad1100647780b 100755 GIT binary patch delta 19 bcmZ3vf@S#%mJQmTn{_;W+c#HTSnmt~RM-f( delta 19 bcmZ3!f@S3jmJQmTo3%Xs+BcV9*x(ERRJ{nb diff --git a/py/EmAySee_AnyPassthrough.py b/py/EmAySee_AnyPassthrough.py index b262dfa..b2cd6ce 100755 --- a/py/EmAySee_AnyPassthrough.py +++ b/py/EmAySee_AnyPassthrough.py @@ -8,38 +8,38 @@ class EmAySee_AnyPassthrough: def INPUT_TYPES(s): return { "required": { - # Input connection that accepts any data type (*). - # You can rename this input visually in the UI. + # Input connection that accepts any data type (*). + # You can rename this input visually in the UI. "input_any": ("*",), } } - # The node outputs the same type as the input (any type) + # The node outputs the same type as the input (any type) RETURN_TYPES = ("*",) - # You can rename the output connection visually in the UI as well. + # You can rename the output connection visually in the UI as well. RETURN_NAMES = ("output_any",) - # Category for the ComfyUI menu + # Category for the ComfyUI menu CATEGORY = "EmAySee_Utils" - # Title displayed on the node + # Title displayed on the node TITLE = "EmAySee Any Passthrough" - # The method that will be executed when the node runs + # The method that will be executed when the node runs FUNCTION = "pass_through_any" def pass_through_any(self, input_any): """ Simply returns the input data of any type. """ - # The node's logic is just to return the input data. + # The node's logic is just to return the input data. return (input_any,) -# Mapping of node class name to the class +# Mapping of node class name to the class NODE_CLASS_MAPPINGS = { "EmAySee_AnyPassthrough": EmAySee_AnyPassthrough } -# Mapping of node class name to the display name in the UI +# Mapping of node class name to the display name in the UI NODE_DISPLAY_NAME_MAPPINGS = { "EmAySee_AnyPassthrough": "EmAySee Any Passthrough" } diff --git a/py/EmAySee_CalculateAspectRatio.py b/py/EmAySee_CalculateAspectRatio.py index abd4dbc..7dc901e 100755 --- a/py/EmAySee_CalculateAspectRatio.py +++ b/py/EmAySee_CalculateAspectRatio.py @@ -18,7 +18,7 @@ class EmAySee_CalculateAspectRatio: def EmAySee_calculate_dimensions(self, image, output_height): batch_size, original_height, original_width, channels = image.shape - # Calculate the new width based on the aspect ratio + # Calculate the new width based on the aspect ratio ratio = original_width / original_height output_width = int(output_height * ratio) diff --git a/py/EmAySee_CheckboxFloatNode.py b/py/EmAySee_CheckboxFloatNode.py index 6c977be..b521530 100755 --- a/py/EmAySee_CheckboxFloatNode.py +++ b/py/EmAySee_CheckboxFloatNode.py @@ -17,10 +17,10 @@ class EmAySee_CheckboxFloatNode: FUNCTION = "checkbox_to_float" def checkbox_to_float(self, checkbox_input): - if checkbox_input: # If checkbox_input is True (checked) + if checkbox_input: # If checkbox_input is True (checked) return (1.0,) - else: # If checkbox_input is False (unchecked) - return (0.0,) # Or you could return (0.0,) if you prefer 0 when unchecked + else: # If checkbox_input is False (unchecked) + return (0.0,) # Or you could return (0.0,) if you prefer 0 when unchecked NODE_CLASS_MAPPINGS = { "EmAySee_CheckboxFloatNode": EmAySee_CheckboxFloatNode diff --git a/py/EmAySee_ConditionalResize.py b/py/EmAySee_ConditionalResize.py index 3d89f9c..f066a17 100755 --- a/py/EmAySee_ConditionalResize.py +++ b/py/EmAySee_ConditionalResize.py @@ -35,14 +35,14 @@ class EmAySee_ConditionalResize: if method == "lanczos": s = samples.clone() - # Lanczos kernel implementation usually requires custom handling or specific libraries in standard Torch. - # Using bicubic as a fallback for standard torch interpolate if lanczos isn't explicitly defined in context, - # but standard ComfyUI `common_upscale` handles this. - # To keep this dependency-free and simple torch: - # We will use bicubic for lanczos slot here or implement standard resize. - # Actually, simpler to map standard torch modes. - # Let's use bicubic for 'lanczos' to avoid external dependencies, - # or simple bicubic with antialias=True if supported. + # Lanczos kernel implementation usually requires custom handling or specific libraries in standard Torch. + # Using bicubic as a fallback for standard torch interpolate if lanczos isn't explicitly defined in context, + # but standard ComfyUI `common_upscale` handles this. + # To keep this dependency-free and simple torch: + # We will use bicubic for lanczos slot here or implement standard resize. + # Actually, simpler to map standard torch modes. + # Let's use bicubic for 'lanczos' to avoid external dependencies, + # or simple bicubic with antialias=True if supported. mode = "bicubic" else: mode = method diff --git a/py/EmAySee_ContextManager.py b/py/EmAySee_ContextManager.py index 753fb47..12cf938 100755 --- a/py/EmAySee_ContextManager.py +++ b/py/EmAySee_ContextManager.py @@ -21,13 +21,13 @@ class EmAySee_ContextManager: CATEGORY = "EmAySee/Automation" def manage_context(self, file_path, history_count, mode, force_reset, new_generation=""): - # Force absolute path to avoid "Ghost" file issues in ComfyUI temp folders + # Force absolute path to avoid "Ghost" file issues in ComfyUI temp folders abs_path = os.path.abspath(file_path) if force_reset and os.path.exists(abs_path): os.remove(abs_path) - # Helper to read blocks + # Helper to read blocks def get_blocks(path): if not os.path.exists(path) or os.path.getsize(path) == 0: return [] @@ -39,11 +39,11 @@ class EmAySee_ContextManager: blocks = get_blocks(abs_path) if mode == "Read_Context": - # SEEDING: If file is empty, use the input wire as the current state + # SEEDING: If file is empty, use the input wire as the current state if not blocks: if new_generation and new_generation.strip(): seed = new_generation.strip() - # Write immediately so the Append node sees it later in the same cycle + # Write immediately so the Append node sees it later in the same cycle with open(abs_path, 'w', encoding='utf-8') as f: f.write(seed + "\n\n") f.flush() @@ -56,17 +56,17 @@ class EmAySee_ContextManager: if mode == "Append_and_Prune": if new_generation and new_generation.strip(): - # Avoid doubling up if Read_Context already seeded it + # Avoid doubling up if Read_Context already seeded it if not blocks or blocks[-1] != new_generation.strip(): blocks.append(new_generation.strip()) pruned = blocks[-history_count:] - # ATOMIC WRITE + # ATOMIC WRITE with open(abs_path, 'w', encoding='utf-8') as f: f.write("\n\n".join(pruned)) f.flush() - os.fsync(f.fileno()) # Forces Windows/Linux to write to disk NOW + os.fsync(f.fileno()) # Forces Windows/Linux to write to disk NOW return (new_generation if new_generation else "",) diff --git a/py/EmAySee_ContextManagerRWNodes_V3.py b/py/EmAySee_ContextManagerRWNodes_V3.py index 5708ba5..6ce564c 100755 --- a/py/EmAySee_ContextManagerRWNodes_V3.py +++ b/py/EmAySee_ContextManagerRWNodes_V3.py @@ -16,7 +16,7 @@ def get_blocks(path): return [] with open(path, 'r', encoding='utf-8') as f: data = f.read() - # V5 RE-DESIGNED REGEX: Looks for PROMPT and the LAST occurrence of STATE in a chunk + # V5 RE-DESIGNED REGEX: Looks for PROMPT and the LAST occurrence of STATE in a chunk found = re.findall(r"(PROMPT:.*?STATE:.*?)(?=PROMPT:|\Z)", data, re.IGNORECASE | re.DOTALL) return [b.strip() for b in found if b.strip()] @@ -70,12 +70,12 @@ class EmAySee_ContextWriter: text = new_generation.strip() - # --- V5 REPAIR LOGIC --- - # 1. Ensure PROMPT header + # --- V5 REPAIR LOGIC --- + # 1. Ensure PROMPT header if not re.search(r"^PROMPT:", text, re.IGNORECASE): text = "PROMPT: " + text - # 2. Check for STATE header (case insensitive) + # 2. Check for STATE header (case insensitive) if not re.search(r"STATE:", text, re.IGNORECASE): print("--- [V5 WRITER] Repairing Missing STATE ---") text = text + "\n\nSTATE: [Repaired Placeholder]" @@ -83,10 +83,10 @@ class EmAySee_ContextWriter: abs_path = os.path.join(get_comfy_root(), file_name) blocks = get_blocks(abs_path) - # Deduplication check + # Deduplication check if not (blocks and blocks[-1][:100] == text[:100]): blocks.append(text) - # Keep only the requested history depth + # Keep only the requested history depth atomic_write(abs_path, blocks[-history_count:]) return (text,) diff --git a/py/EmAySee_ContextManager_V2.py b/py/EmAySee_ContextManager_V2.py index 15761d3..8edef1d 100755 --- a/py/EmAySee_ContextManager_V2.py +++ b/py/EmAySee_ContextManager_V2.py @@ -21,7 +21,7 @@ class EmAySee_ContextManager_V2: CATEGORY = "EmAySee_Automation" def manage_context(self, file_path, history_count, mode, force_reset, new_generation=""): - # ABSOLUTE PATH LOGGING + # ABSOLUTE PATH LOGGING abs_path = os.path.abspath(file_path) print(f"--- [EmAySee V2] DEBUG: Working Directory is {os.getcwd()} ---") print(f"--- [EmAySee V2] DEBUG: Looking for file at {abs_path} ---") @@ -42,7 +42,7 @@ class EmAySee_ContextManager_V2: with open(path, 'r', encoding='utf-8') as f: data = f.read() - # This regex is strict. If your file doesn't have "PROMPT:" it will return nothing. + # This regex is strict. If your file doesn't have "PROMPT:" it will return nothing. found = re.findall(r"(PROMPT:.*?STATE:.*?)(?=PROMPT:|\Z)", data, re.DOTALL) print(f"--- [EmAySee V2] DEBUG: Found {len(found)} valid PROMPT/STATE blocks. ---") return [b.strip() for b in found if b.strip()] diff --git a/py/EmAySee_ContextWriter_V5.py b/py/EmAySee_ContextWriter_V5.py index ec3061e..39fd8d8 100755 --- a/py/EmAySee_ContextWriter_V5.py +++ b/py/EmAySee_ContextWriter_V5.py @@ -16,28 +16,28 @@ class EmAySee_ContextWriter_V5: CATEGORY = "EmAySee_Automation/Context" def write_context(self, file_path, text_to_append, max_history): - # 1. Truncation Guard + # 1. Truncation Guard if "[REASONING TRUNCATED]" in text_to_append: raise ValueError(f"CRITICAL ERROR: LLM Output was truncated. Workflow halted to prevent history corruption.") - # 2. Path Validation + # 2. Path Validation if not os.path.exists(os.path.dirname(file_path)): os.makedirs(os.path.dirname(file_path), mode=0o777, exist_ok=True) - # 3. Read existing to manage history length + # 3. Read existing to manage history length history = [] if os.path.exists(file_path): with open(file_path, "r", encoding="utf-8") as f: content = f.read() history = [block for block in content.split("---") if "STATE:" in block] - # 4. Append New Entry + # 4. Append New Entry history.append(text_to_append) - # 5. Keep only the last N entries + # 5. Keep only the last N entries history = history[-max_history:] - # 6. Rewrite file + # 6. Rewrite file with open(file_path, "w", encoding="utf-8") as f: f.write("---".join(history)) diff --git a/py/EmAySee_DeepReasoningConnector.py b/py/EmAySee_DeepReasoningConnector.py index 2fe77ef..83b2fbd 100755 --- a/py/EmAySee_DeepReasoningConnector.py +++ b/py/EmAySee_DeepReasoningConnector.py @@ -121,7 +121,7 @@ class EmAySee_DeepReasoningConnector: if stop_on_error: raise e return (f"Error: {str(e)}", "", "ERROR") - # Parsing results + # Parsing results think_pattern = r'<(?:think|thought)>(.*?)' think_match = re.search(think_pattern, full_content, flags=re.DOTALL | re.IGNORECASE) diff --git a/py/EmAySee_DynamicStringSelectorNode.py b/py/EmAySee_DynamicStringSelectorNode.py index 192d808..7c6bde9 100755 --- a/py/EmAySee_DynamicStringSelectorNode.py +++ b/py/EmAySee_DynamicStringSelectorNode.py @@ -7,7 +7,7 @@ class EmAySee_DynamicStringSelectorNode: @classmethod def INPUT_TYPES(s): return {"required": { - "string_input_1": ("STRING", {"default": "Initial Input"}), # Added initial input + "string_input_1": ("STRING", {"default": "Initial Input"}), # Added initial input "select_index": ("INT", {"default": 1, "min": 1, "max": 1, "step": 1, "display": "slider", "label": "Select Input"}), "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), }, "optional": {}} @@ -20,7 +20,7 @@ class EmAySee_DynamicStringSelectorNode: @classmethod def IS_CHANGED(s, *args): - return float("nan") # Never cache, so the node updates every time + return float("nan") # Never cache, so the node updates every time @classmethod def VALIDATE_INPUTS(s, select_index, seed, **kwargs): @@ -31,7 +31,7 @@ class EmAySee_DynamicStringSelectorNode: def EmAySee_function(self, select_index, seed, **kwargs): if not kwargs: - return ("",) # No inputs connected + return ("",) # No inputs connected random.seed(seed) #seed the random generator return (list(kwargs.values())[select_index - 1],) diff --git a/py/EmAySee_EphemeralPreview.py b/py/EmAySee_EphemeralPreview.py index 98d0e43..b5f6c12 100755 --- a/py/EmAySee_EphemeralPreview.py +++ b/py/EmAySee_EphemeralPreview.py @@ -16,34 +16,34 @@ class EphemeralPreview: RETURN_TYPES = () FUNCTION = "preview_no_cache" OUTPUT_NODE = True - CATEGORY = "EmAySee_Tools" # Added per your preference + CATEGORY = "EmAySee_Tools" # Added per your preference def preview_no_cache(self, images): results = [] - # We use a fixed filename to encourage the browser to reuse the memory slot - # slightly changing the query param (subfolder) to force a refresh but try to keep it lean. + # We use a fixed filename to encourage the browser to reuse the memory slot + # slightly changing the query param (subfolder) to force a refresh but try to keep it lean. for i, image in enumerate(images): i_np = 255. * image.cpu().numpy() img = Image.fromarray(np.clip(i_np, 0, 255).astype(np.uint8)) - # Save to a fixed filename in temp to avoid disk bloat + # Save to a fixed filename in temp to avoid disk bloat filename = f"ephemeral_monitor_{i}.png" file = os.path.join(self.output_dir, filename) img.save(file, optimize=True, compress_level=4) - # We append a random int to the subfolder only to trigger the UI update, - # but the file on disk remains constant. + # We append a random int to the subfolder only to trigger the UI update, + # but the file on disk remains constant. results.append({ "filename": filename, "subfolder": "", "type": self.type }) - # This return structure mimics a PreviewImage node + # This return structure mimics a PreviewImage node return {"ui": {"images": results}} -# Register the node +# Register the node NODE_CLASS_MAPPINGS = { "EmAySee_EphemeralPreview": EphemeralPreview } diff --git a/py/EmAySee_GetModelPath.py b/py/EmAySee_GetModelPath.py index a61f7b2..96cd458 100755 --- a/py/EmAySee_GetModelPath.py +++ b/py/EmAySee_GetModelPath.py @@ -15,8 +15,8 @@ class EmAySee_GetModelPath: FUNCTION = "EmAySee_get_path" def EmAySee_get_path(self, model): - # A model object in ComfyUI typically has the file path in a specific attribute - # We assume the first path is the one you need. + # A model object in ComfyUI typically has the file path in a specific attribute + # We assume the first path is the one you need. model_path = model.model_filenames[0] return (model_path,) diff --git a/py/EmAySee_HostPinger.py b/py/EmAySee_HostPinger.py index 8a320ab..61c350e 100755 --- a/py/EmAySee_HostPinger.py +++ b/py/EmAySee_HostPinger.py @@ -11,27 +11,27 @@ class EmAySee_HostPinger: def INPUT_TYPES(s): return { "required": { - "host": ("STRING", {"default": "google.com"}), # Hostname or IP address to ping + "host": ("STRING", {"default": "google.com"}), # Hostname or IP address to ping } } - RETURN_TYPES = ("INT",) # Output is a single integer (1 or 0) - RETURN_NAMES = ("is_up",) # Name for the output - CATEGORY = "EmAySee_Network" # Category in the ComfyUI menu - TITLE = "EmAySee Host Pinger" # Title displayed on the node + RETURN_TYPES = ("INT",) # Output is a single integer (1 or 0) + RETURN_NAMES = ("is_up",) # Name for the output + CATEGORY = "EmAySee_Network" # Category in the ComfyUI menu + TITLE = "EmAySee Host Pinger" # Title displayed on the node - FUNCTION = "EmAySee_check_host" # The method that will be executed + FUNCTION = "EmAySee_check_host" # The method that will be executed def EmAySee_check_host(self, host): """ Executes a system ping command to check if the host is reachable. Returns 1 if successful, 0 otherwise. """ - # Determine the correct ping command based on the operating system + # Determine the correct ping command based on the operating system param = '-n' if platform.system().lower() == 'windows' else '-c' - count = '1' # Number of ping requests + count = '1' # Number of ping requests timeout_param = '-w' if platform.system().lower() == 'windows' else '-W' - timeout_value = '1000' if platform.system().lower() == 'windows' else '1' # Timeout in ms (Windows) or seconds (Linux/macOS) + timeout_value = '1000' if platform.system().lower() == 'windows' else '1' # Timeout in ms (Windows) or seconds (Linux/macOS) command = [ 'ping', @@ -40,28 +40,28 @@ class EmAySee_HostPinger: host ] - is_up = 0 # Default to 0 (down) + is_up = 0 # Default to 0 (down) try: - # Execute the ping command - # capture_output=True captures stdout and stderr - # text=True decodes stdout/stderr as text - # timeout sets a maximum time to wait for the command to complete + # Execute the ping command + # capture_output=True captures stdout and stderr + # text=True decodes stdout/stderr as text + # timeout sets a maximum time to wait for the command to complete result = subprocess.run( command, capture_output=True, text=True, - timeout=2, # Set a reasonable timeout for the subprocess itself - check=False # Do not raise an exception for non-zero exit codes (like host unreachable) + timeout=2, # Set a reasonable timeout for the subprocess itself + check=False # Do not raise an exception for non-zero exit codes (like host unreachable) ) - # Check the return code - # A return code of 0 usually indicates success (host is reachable) + # Check the return code + # A return code of 0 usually indicates success (host is reachable) if result.returncode == 0: is_up = 1 else: - # You can optionally print stderr for debugging if the ping fails - # print(f"Ping command failed for {host}: {result.stderr}") + # You can optionally print stderr for debugging if the ping fails + # print(f"Ping command failed for {host}: {result.stderr}") is_up = 0 except FileNotFoundError: @@ -74,14 +74,14 @@ class EmAySee_HostPinger: print(f"An unexpected error occurred while pinging {host}: {e}") is_up = 0 - return (is_up,) # Return the result as a tuple + return (is_up,) # Return the result as a tuple -# Mapping of node class name to the class +# Mapping of node class name to the class NODE_CLASS_MAPPINGS = { "EmAySee_HostPinger": EmAySee_HostPinger } -# Mapping of node class name to the display name in the UI +# Mapping of node class name to the display name in the UI NODE_DISPLAY_NAME_MAPPINGS = { "EmAySee_HostPinger": "EmAySee Host Pinger" } diff --git a/py/EmAySee_ImagePassthrough.py b/py/EmAySee_ImagePassthrough.py index 47df991..4e54b25 100755 --- a/py/EmAySee_ImagePassthrough.py +++ b/py/EmAySee_ImagePassthrough.py @@ -8,37 +8,37 @@ class EmAySee_ImagePassthrough: def INPUT_TYPES(s): return { "required": { - # Input image connection. You can rename this input visually in the UI. + # Input image connection. You can rename this input visually in the UI. "input_image": ("IMAGE",), } } - # The node outputs an image + # The node outputs an image RETURN_TYPES = ("IMAGE",) - # You can rename the output connection visually in the UI as well. + # You can rename the output connection visually in the UI as well. RETURN_NAMES = ("output_image",) - # Category for the ComfyUI menu + # Category for the ComfyUI menu CATEGORY = "EmAySee_Utils" - # Title displayed on the node + # Title displayed on the node TITLE = "EmAySee Image Passthrough" - # The method that will be executed when the node runs + # The method that will be executed when the node runs FUNCTION = "pass_through" def pass_through(self, input_image): """ Simply returns the input image. """ - # The node's logic is just to return the input image. + # The node's logic is just to return the input image. return (input_image,) -# Mapping of node class name to the class +# Mapping of node class name to the class NODE_CLASS_MAPPINGS = { "EmAySee_ImagePassthrough": EmAySee_ImagePassthrough } -# Mapping of node class name to the display name in the UI +# Mapping of node class name to the display name in the UI NODE_DISPLAY_NAME_MAPPINGS = { "EmAySee_ImagePassthrough": "EmAySee Image Passthrough" } diff --git a/py/EmAySee_ImagePreviewPassthrough.py b/py/EmAySee_ImagePreviewPassthrough.py index f92d0af..229cc71 100755 --- a/py/EmAySee_ImagePreviewPassthrough.py +++ b/py/EmAySee_ImagePreviewPassthrough.py @@ -6,7 +6,7 @@ import time class EmAySee_ImagePreviewPassthrough: def __init__(self): - # Get the temp directory from ComfyUI + # Get the temp directory from ComfyUI self.output_dir = folder_paths.get_temp_directory() self.type = "temp" @@ -24,39 +24,39 @@ class EmAySee_ImagePreviewPassthrough: CATEGORY = "EmAySee/Utils" def preview_and_passthrough(self, image): - # 1. --- Prepare images for preview --- - # This logic is adapted from ComfyUI's PreviewImage node + # 1. --- Prepare images for preview --- + # This logic is adapted from ComfyUI's PreviewImage node - # Convert tensor to PIL images + # Convert tensor to PIL images images = [] for i in range(image.shape[0]): img_tensor = image[i] img = (img_tensor * 255.0).cpu().numpy().astype(np.uint8) images.append(Image.fromarray(img)) - # Save images to temp folder and get filenames + # Save images to temp folder and get filenames preview_images = [] for img in images: - # Generate a unique filename based on current time + # Generate a unique filename based on current time filename = f"emaysee_preview_{time.time_ns()}.png" filepath = os.path.join(self.output_dir, filename) - # Save the image - img.save(filepath, format='PNG', compress_level=4) # Use low compression for speed + # Save the image + img.save(filepath, format='PNG', compress_level=4) # Use low compression for speed - # Add to preview list for the UI + # Add to preview list for the UI preview_images.append({ "filename": filename, - "subfolder": "", # We are using the temp folder directly + "subfolder": "", # We are using the temp folder directly "type": self.type }) - # 2. --- Return results --- - # This dictionary structure tells ComfyUI what to display in the UI - # and what to pass to the output port. + # 2. --- Return results --- + # This dictionary structure tells ComfyUI what to display in the UI + # and what to pass to the output port. return { - "ui": { "images": preview_images }, # This triggers the inline preview - "result": (image,) # This is the passthrough output + "ui": { "images": preview_images }, # This triggers the inline preview + "result": (image,) # This is the passthrough output } NODE_CLASS_MAPPINGS = { diff --git a/py/EmAySee_IntegerStringSelectorNode.py b/py/EmAySee_IntegerStringSelectorNode.py index 01a46c5..53acf6f 100755 --- a/py/EmAySee_IntegerStringSelectorNode.py +++ b/py/EmAySee_IntegerStringSelectorNode.py @@ -15,7 +15,7 @@ class EmAySee_IntegerStringSelectorNode: "min": 1, "max": 4, "step": 1, - "display": "slider", # Or "number" + "display": "slider", # Or "number" "label": "Select Input (1-4)" }), }, @@ -37,13 +37,13 @@ class EmAySee_IntegerStringSelectorNode: elif select_index == 4: return (string_input_4,) else: - return ("",) # Should not happen given input constraints, but as a failsafe + return ("",) # Should not happen given input constraints, but as a failsafe -NODE_CLASS_MAPPINGS = { # Moved to after the class definition +NODE_CLASS_MAPPINGS = { # Moved to after the class definition "EmAySee_IntegerStringSelectorNode": EmAySee_IntegerStringSelectorNode } -NODE_DISPLAY_NAME_MAPPINGS = { # Moved to after the class definition +NODE_DISPLAY_NAME_MAPPINGS = { # Moved to after the class definition "EmAySee_IntegerStringSelectorNode": "EmAySee Integer String Selector" } \ No newline at end of file diff --git a/py/EmAySee_IsGreaterThan.py b/py/EmAySee_IsGreaterThan.py index 10f4d66..7d56207 100755 --- a/py/EmAySee_IsGreaterThan.py +++ b/py/EmAySee_IsGreaterThan.py @@ -19,10 +19,10 @@ class EmAySee_GreaterThanNode: } } - RETURN_TYPES = ("INT",) # The node will return an integer (0 or 1) - RETURN_NAMES = ("RESULT",) # The output port will be named 'RESULT' - FUNCTION = "compare_floats" # The name of the function that executes the node's logic - CATEGORY = "Logic" # The category where the node will appear in ComfyUI + RETURN_TYPES = ("INT",) # The node will return an integer (0 or 1) + RETURN_NAMES = ("RESULT",) # The output port will be named 'RESULT' + FUNCTION = "compare_floats" # The name of the function that executes the node's logic + CATEGORY = "Logic" # The category where the node will appear in ComfyUI def compare_floats(self, A, B): """ @@ -30,17 +30,17 @@ class EmAySee_GreaterThanNode: Returns 1 if A is greater than B, otherwise returns 0. """ if A > B: - return (1,) # Return as a tuple, as ComfyUI expects + return (1,) # Return as a tuple, as ComfyUI expects else: - return (0,) # Return as a tuple + return (0,) # Return as a tuple -# A dictionary of all custom nodes available in this file. -# The key is the node class name, and the value is the class itself. +# A dictionary of all custom nodes available in this file. +# The key is the node class name, and the value is the class itself. NODE_CLASS_MAPPINGS = { "EmAySee_GreaterThanNode": EmAySee_GreaterThanNode } -# A dictionary that provides more user-friendly names for the nodes in the UI. +# A dictionary that provides more user-friendly names for the nodes in the UI. NODE_DISPLAY_NAME_MAPPINGS = { "EmAySee_GreaterThanNode": "EmAySee Greater Than (Float)" } diff --git a/py/EmAySee_LLMOutputCleaner.py b/py/EmAySee_LLMOutputCleaner.py index b597970..78b8265 100755 --- a/py/EmAySee_LLMOutputCleaner.py +++ b/py/EmAySee_LLMOutputCleaner.py @@ -15,15 +15,15 @@ class EmAySee_LLMOutputCleaner: CATEGORY = "EmAySee/Utils" def clean_text(self, text): - # 1. Remove ... blocks (DeepSeek/Reasoning models) - # flags=re.DOTALL ensures the dot matches newlines inside the think block + # 1. Remove ... blocks (DeepSeek/Reasoning models) + # flags=re.DOTALL ensures the dot matches newlines inside the think block cleaned = re.sub(r'.*?', '', text, flags=re.DOTALL) - # 2. Remove common "Here is the thinking process:" preambles if they exist - # (Optional, but helps with some chatty models) - # cleaned = re.sub(r'(?i)here[\'s\s]+the\s+thinking.*?:', '', cleaned) + # 2. Remove common "Here is the thinking process:" preambles if they exist + # (Optional, but helps with some chatty models) + # cleaned = re.sub(r'(?i)here[\'s\s]+the\s+thinking.*?:', '', cleaned) - # 3. Clean up extra whitespace left behind + # 3. Clean up extra whitespace left behind cleaned = cleaned.strip() return (cleaned,) diff --git a/py/EmAySee_LoRAMetadataExtractor.py b/py/EmAySee_LoRAMetadataExtractor.py index bfa8b42..5fcdefd 100755 --- a/py/EmAySee_LoRAMetadataExtractor.py +++ b/py/EmAySee_LoRAMetadataExtractor.py @@ -2,11 +2,11 @@ import json import os from safetensors import safe_open -# Node class mapping and display name (how it appears in ComfyUI) +# Node class mapping and display name (how it appears in ComfyUI) NODE_CLASS_MAPPINGS = {} NODE_DISPLAY_NAME_MAPPINGS = {} -class EmAySee_LoRAMetadataExtractor: # Prepend EmAySee_ to the class name +class EmAySee_LoRAMetadataExtractor: # Prepend EmAySee_ to the class name """ A ComfyUI custom node to extract and display training metadata from a .safetensors LoRA file. @@ -17,19 +17,19 @@ class EmAySee_LoRAMetadataExtractor: # Prepend EmAySee_ to the class name def __init__(self): pass - # Define the return types of the node's outputs - # This node will output a single STRING + # Define the return types of the node's outputs + # This node will output a single STRING RETURN_TYPES = ("STRING",) - # Define the names of the node's outputs + # Define the names of the node's outputs RETURN_NAMES = ("metadata_json_string",) - # Define the category where the node will appear in ComfyUI + # Define the category where the node will appear in ComfyUI FUNCTION = "extract_metadata" - CATEGORY = "Mac Custom Nodes/LoRA Tools" # You can change this category if you prefer + CATEGORY = "Mac Custom Nodes/LoRA Tools" # You can change this category if you prefer - # Define the input types of the node - # This node requires a file path to a .safetensors file + # Define the input types of the node + # This node requires a file path to a .safetensors file @classmethod def INPUT_TYPES(s): return { @@ -37,10 +37,10 @@ class EmAySee_LoRAMetadataExtractor: # Prepend EmAySee_ to the class name "lora_filepath": ("STRING", {"multiline": False, "default": ""}), }, "optional": { - # Optional input for a file upload, if you prefer to drag-and-drop - # Note: ComfyUI's file input might not directly give a full path easily - # Manual string path is often more reliable for existing files. - "lora_upload": ("LORA", {"forceInput": True}), # This allows drag-and-drop, but you'd still need to get the path + # Optional input for a file upload, if you prefer to drag-and-drop + # Note: ComfyUI's file input might not directly give a full path easily + # Manual string path is often more reliable for existing files. + "lora_upload": ("LORA", {"forceInput": True}), # This allows drag-and-drop, but you'd still need to get the path } } @@ -59,7 +59,7 @@ class EmAySee_LoRAMetadataExtractor: # Prepend EmAySee_ to the class name if not lora_filepath: return ("Error: LoRA filepath cannot be empty.",) - # Ensure the file exists + # Ensure the file exists if not os.path.exists(lora_filepath): return (f"Error: File not found at '{lora_filepath}'. Please check the path.",) if not os.path.isfile(lora_filepath): @@ -70,58 +70,58 @@ class EmAySee_LoRAMetadataExtractor: # Prepend EmAySee_ to the class name metadata = f.metadata() output_data = {} - # Attempt to get the full Kohya_ss config if nested under __metadata__ (newer Kohya) + # Attempt to get the full Kohya_ss config if nested under __metadata__ (newer Kohya) if "__metadata__" in metadata: try: output_data = json.loads(metadata["__metadata__"]) - # Add a flag to indicate it's the full config + # Add a flag to indicate it's the full config output_data["_metadata_source"] = "Full Kohya_ss Config (__metadata__)" except json.JSONDecodeError: output_data["_error"] = "Failed to parse __metadata__ as JSON." output_data["_raw_metadata"] = metadata["__metadata__"] output_data["_metadata_source"] = "Raw __metadata__ (Parse Error)" else: - # If not, extract individual ss_ prefixed keys and their values + # If not, extract individual ss_ prefixed keys and their values extracted_params = {} for key, value in metadata.items(): - # First, attempt general type conversion (int, float, bool, None) + # First, attempt general type conversion (int, float, bool, None) if isinstance(value, str): if value.lower() == "true": value = True elif value.lower() == "false": value = False - elif value.lower() == "none": # Handle "None" as Python None + elif value.lower() == "none": # Handle "None" as Python None value = None else: try: - if "." in value: # Likely a float + if "." in value: # Likely a float value = float(value) - else: # Try integer + else: # Try integer value = int(value) except ValueError: - pass # Keep as string if not a simple number/boolean + pass # Keep as string if not a simple number/boolean - # Then, specifically parse known nested JSON strings + # Then, specifically parse known nested JSON strings if key in ["ss_tag_frequency", "ss_bucket_info", "ss_datasets"]: - if isinstance(value, str): # Only try to parse if it's still a string + if isinstance(value, str): # Only try to parse if it's still a string try: value = json.loads(value) except json.JSONDecodeError: - # If parsing fails, keep it as a string but note the error + # If parsing fails, keep it as a string but note the error value = f"ERROR_PARSING_JSON: {value}" - # Store the processed value + # Store the processed value extracted_params[key] = value output_data = extracted_params output_data["_metadata_source"] = "Extracted ss_ Prefixed Keys" - # Return the data as a pretty-printed JSON string + # Return the data as a pretty-printed JSON string return (json.dumps(output_data, indent=2),) except Exception as e: return (f"An unexpected error occurred during metadata extraction: {e}",) -# Register the node -NODE_CLASS_MAPPINGS["EmAySee_LoRAMetadataExtractor"] = EmAySee_LoRAMetadataExtractor # Prepend EmAySee_ -NODE_DISPLAY_NAME_MAPPINGS["EmAySee_LoRA Metadata Extractor"] = "EmAySee_LoRA Metadata Extractor" # Prepend EmAySee_ +# Register the node +NODE_CLASS_MAPPINGS["EmAySee_LoRAMetadataExtractor"] = EmAySee_LoRAMetadataExtractor # Prepend EmAySee_ +NODE_DISPLAY_NAME_MAPPINGS["EmAySee_LoRA Metadata Extractor"] = "EmAySee_LoRA Metadata Extractor" # Prepend EmAySee_ diff --git a/py/EmAySee_LoRAMetadataExtractorTagDrop.py b/py/EmAySee_LoRAMetadataExtractorTagDrop.py index 2ed83b9..b4bde3e 100755 --- a/py/EmAySee_LoRAMetadataExtractorTagDrop.py +++ b/py/EmAySee_LoRAMetadataExtractorTagDrop.py @@ -2,10 +2,10 @@ import json import os from safetensors import safe_open -# Node class mapping and display name (how it appears in ComfyUI) -# These mappings are defined here for direct node loading. +# Node class mapping and display name (how it appears in ComfyUI) +# These mappings are defined here for direct node loading. -class EmAySee_LoRAMetadataExtractorTagDrop: # Prepend EmAySee_ to the class name +class EmAySee_LoRAMetadataExtractorTagDrop: # Prepend EmAySee_ to the class name """ A ComfyUI custom node to extract and display training metadata from a .safetensors LoRA file. @@ -17,31 +17,31 @@ class EmAySee_LoRAMetadataExtractorTagDrop: # Prepend EmAySee_ to the class nam def __init__(self): pass - # Define the return types of the node's outputs - # This node will output a single STRING + # Define the return types of the node's outputs + # This node will output a single STRING RETURN_TYPES = ("STRING",) - # Define the names of the node's outputs + # Define the names of the node's outputs RETURN_NAMES = ("metadata_json_string",) - # Define the category where the node will appear in ComfyUI + # Define the category where the node will appear in ComfyUI FUNCTION = "extract_metadata" - CATEGORY = "EmAySee/LoRA Tools" # Updated category for consistency with your prefix + CATEGORY = "EmAySee/LoRA Tools" # Updated category for consistency with your prefix - # Define the input types of the node - # This node requires a file path to a .safetensors file + # Define the input types of the node + # This node requires a file path to a .safetensors file @classmethod def INPUT_TYPES(s): return { "required": { "lora_filepath": ("STRING", {"multiline": False, "default": ""}), - "exclude_tag_frequency": ("BOOLEAN", {"default": False}), # New toggle input + "exclude_tag_frequency": ("BOOLEAN", {"default": False}), # New toggle input }, "optional": { - # Optional input for a file upload, if you prefer to drag-and-drop - # Note: ComfyUI's file input might not directly give a full path easily - # Manual string path is often more reliable for existing files. - "lora_upload": ("LORA", {"forceInput": True}), # This allows drag-and-drop, but you'd still need to get the path + # Optional input for a file upload, if you prefer to drag-and-drop + # Note: ComfyUI's file input might not directly give a full path easily + # Manual string path is often more reliable for existing files. + "lora_upload": ("LORA", {"forceInput": True}), # This allows drag-and-drop, but you'd still need to get the path } } @@ -61,7 +61,7 @@ class EmAySee_LoRAMetadataExtractorTagDrop: # Prepend EmAySee_ to the class nam if not lora_filepath: return ("Error: LoRA filepath cannot be empty.",) - # Ensure the file exists + # Ensure the file exists if not os.path.exists(lora_filepath): return (f"Error: File not found at '{lora_filepath}'. Please check the path.",) if not os.path.isfile(lora_filepath): @@ -72,69 +72,69 @@ class EmAySee_LoRAMetadataExtractorTagDrop: # Prepend EmAySee_ to the class nam metadata = f.metadata() output_data = {} - # Attempt to get the full Kohya_ss config if nested under __metadata__ (newer Kohya) + # Attempt to get the full Kohya_ss config if nested under __metadata__ (newer Kohya) if "__metadata__" in metadata: try: output_data = json.loads(metadata["__metadata__"]) - # Add a flag to indicate it's the full config + # Add a flag to indicate it's the full config output_data["_metadata_source"] = "Full Kohya_ss Config (__metadata__)" except json.JSONDecodeError: output_data["_error"] = "Failed to parse __metadata__ as JSON." output_data["_raw_metadata"] = metadata["__metadata__"] output_data["_metadata_source"] = "Raw __metadata__ (Parse Error)" else: - # If not, extract individual ss_ prefixed keys and their values + # If not, extract individual ss_ prefixed keys and their values extracted_params = {} for key, value in metadata.items(): - # First, attempt general type conversion (int, float, bool, None) + # First, attempt general type conversion (int, float, bool, None) if isinstance(value, str): if value.lower() == "true": value = True elif value.lower() == "false": value = False - elif value.lower() == "none": # Handle "None" as Python None + elif value.lower() == "none": # Handle "None" as Python None value = None else: try: - if "." in value: # Likely a float + if "." in value: # Likely a float value = float(value) - else: # Try integer + else: # Try integer value = int(value) except ValueError: - pass # Keep as string if not a simple number/boolean + pass # Keep as string if not a simple number/boolean - # Then, specifically parse known nested JSON strings + # Then, specifically parse known nested JSON strings if key in ["ss_tag_frequency", "ss_bucket_info", "ss_datasets"]: - if isinstance(value, str): # Only try to parse if it's still a string + if isinstance(value, str): # Only try to parse if it's still a string try: value = json.loads(value) except json.JSONDecodeError: - # If parsing fails, keep it as a string but note the error + # If parsing fails, keep it as a string but note the error value = f"ERROR_PARSING_JSON: {value}" - # Store the processed value + # Store the processed value extracted_params[key] = value output_data = extracted_params output_data["_metadata_source"] = "Extracted ss_ Prefixed Keys" - # Apply the toggle: remove ss_tag_frequency if exclude_tag_frequency is True + # Apply the toggle: remove ss_tag_frequency if exclude_tag_frequency is True if exclude_tag_frequency: if "ss_tag_frequency" in output_data: del output_data["ss_tag_frequency"] - # If the full config was loaded, check for ss_tag_frequency within it + # If the full config was loaded, check for ss_tag_frequency within it if "_metadata_source" in output_data and output_data["_metadata_source"] == "Full Kohya_ss Config (__metadata__)": if "ss_tag_frequency" in output_data: del output_data["ss_tag_frequency"] - # Return the data as a pretty-printed JSON string + # Return the data as a pretty-printed JSON string return (json.dumps(output_data, indent=2),) except Exception as e: return (f"An unexpected error occurred during metadata extraction: {e}",) -# Register the node +# Register the node NODE_CLASS_MAPPINGS = { "EmAySee_LoRAMetadataExtractorTagDrop": EmAySee_LoRAMetadataExtractorTagDrop, } diff --git a/py/EmAySee_LoraFilePicker.py b/py/EmAySee_LoraFilePicker.py index d5afeb9..8ea53e1 100755 --- a/py/EmAySee_LoraFilePicker.py +++ b/py/EmAySee_LoraFilePicker.py @@ -1,8 +1,8 @@ import os -import folder_paths # ComfyUI utility to get model paths +import folder_paths # ComfyUI utility to get model paths -# Define the main class for your custom node. -# The class name will be used in NODE_CLASS_MAPPINGS. +# Define the main class for your custom node. +# The class name will be used in NODE_CLASS_MAPPINGS. class EmAySee_LoraFilePicker: """ A ComfyUI custom node to pick LORA files from configured paths. @@ -10,7 +10,7 @@ class EmAySee_LoraFilePicker: the selected filename and its full file path. """ def __init__(self): - # Constructor for the node. No special initialization needed for this node. + # Constructor for the node. No special initialization needed for this node. pass @classmethod @@ -19,38 +19,38 @@ class EmAySee_LoraFilePicker: Defines the input types (widgets and connections) for the node. This method is a class method and must return a dictionary. """ - # Get all LORA files from ComfyUI's configured LORA paths. - # This will return a list of relative filenames (e.g., "my_lora.safetensors", - # "subdir/another_lora.pt"). The 'return_full_paths=True' argument is removed - # for compatibility with older ComfyUI versions. + # Get all LORA files from ComfyUI's configured LORA paths. + # This will return a list of relative filenames (e.g., "my_lora.safetensors", + # "subdir/another_lora.pt"). The 'return_full_paths=True' argument is removed + # for compatibility with older ComfyUI versions. lora_files = folder_paths.get_filename_list("loras") - # Add a "None" option at the beginning of the list. - # This allows the user to explicitly select no LORA. + # Add a "None" option at the beginning of the list. + # This allows the user to explicitly select no LORA. lora_files.insert(0, "None") return { "required": { - # "lora_name" is the name of the input port that will appear on the node. - # (lora_files,) creates a dropdown widget in the ComfyUI interface, - # populated with the list of LORA files. + # "lora_name" is the name of the input port that will appear on the node. + # (lora_files,) creates a dropdown widget in the ComfyUI interface, + # populated with the list of LORA files. "lora_name": (lora_files,), }, } - # Define the types of data that this node will output. - # RETURN_TYPES must be a tuple of ComfyUI data types (e.g., "STRING", "IMAGE", "MODEL"). - RETURN_TYPES = ("STRING", "STRING",) # Outputs two strings. + # Define the types of data that this node will output. + # RETURN_TYPES must be a tuple of ComfyUI data types (e.g., "STRING", "IMAGE", "MODEL"). + RETURN_TYPES = ("STRING", "STRING",) # Outputs two strings. - # Define the names for the output ports. These names will appear on the node. + # Define the names for the output ports. These names will appear on the node. RETURN_NAMES = ("filename", "full_filepath",) - # Specifies the Python function within this class that ComfyUI should call - # when the node is executed in a workflow. + # Specifies the Python function within this class that ComfyUI should call + # when the node is executed in a workflow. FUNCTION = "pick_lora_file" - # Defines the category under which this node will appear in the ComfyUI "Add Node" menu. - # Using "EmAySee/" as a prefix helps organize your custom nodes. + # Defines the category under which this node will appear in the ComfyUI "Add Node" menu. + # Using "EmAySee/" as a prefix helps organize your custom nodes. CATEGORY = "EmAySee/File Utilities" def pick_lora_file(self, lora_name): @@ -59,57 +59,57 @@ class EmAySee_LoraFilePicker: It takes the selected LORA filename as input and returns the filename and its corresponding full file path. """ - # If the "None" option is selected in the dropdown, return empty strings. + # If the "None" option is selected in the dropdown, return empty strings. if lora_name == "None": return ("", "") full_lora_path = None - # Get all configured base directories for LORAs. - # folder_paths.get_folder_paths("loras") returns a list of absolute paths - # to the directories where LORAs are stored. + # Get all configured base directories for LORAs. + # folder_paths.get_folder_paths("loras") returns a list of absolute paths + # to the directories where LORAs are stored. lora_folders = folder_paths.get_folder_paths("loras") - # Iterate through each LORA folder to find the selected LORA file. + # Iterate through each LORA folder to find the selected LORA file. for folder in lora_folders: - # Construct a potential full path by joining the base folder and the selected LORA name. + # Construct a potential full path by joining the base folder and the selected LORA name. potential_path = os.path.join(folder, lora_name) - # Verify if this constructed path actually exists and points to a file. + # Verify if this constructed path actually exists and points to a file. if os.path.exists(potential_path) and os.path.isfile(potential_path): full_lora_path = potential_path break - # Also check if the lora_name itself contains subdirectories (e.g., "subdir/lora.safetensors") - # and the folder is a base path. - # This handles cases where the dropdown might show "subdir/lora.safetensors" directly. + # Also check if the lora_name itself contains subdirectories (e.g., "subdir/lora.safetensors") + # and the folder is a base path. + # This handles cases where the dropdown might show "subdir/lora.safetensors" directly. if os.path.exists(os.path.join(folder, lora_name)) and os.path.isfile(os.path.join(folder, lora_name)): full_lora_path = os.path.join(folder, lora_name) break - # If after all attempts, the file still cannot be located, print a warning - # and return empty strings to ensure the workflow doesn't crash. + # If after all attempts, the file still cannot be located, print a warning + # and return empty strings to ensure the workflow doesn't crash. if full_lora_path is None: print(f"Warning: LORA file '{lora_name}' not found in known ComfyUI LORA paths.") return ("", "") - # Return the selected filename and its successfully determined full path. + # Return the selected filename and its successfully determined full path. return (lora_name, full_lora_path) -# NODE_CLASS_MAPPINGS is a mandatory dictionary that ComfyUI uses to discover -# and register your custom nodes. The keys are internal identifiers, and the -# values are the Python classes that define your nodes. +# NODE_CLASS_MAPPINGS is a mandatory dictionary that ComfyUI uses to discover +# and register your custom nodes. The keys are internal identifiers, and the +# values are the Python classes that define your nodes. NODE_CLASS_MAPPINGS = { "EmAySee_LoraFilePicker": EmAySee_LoraFilePicker, } -# NODE_DISPLAY_NAME_MAPPINGS is an optional dictionary that provides -# more user-friendly names for your nodes in the ComfyUI interface. -# This name will appear in the "Add Node" menu. +# NODE_DISPLAY_NAME_MAPPINGS is an optional dictionary that provides +# more user-friendly names for your nodes in the ComfyUI interface. +# This name will appear in the "Add Node" menu. NODE_DISPLAY_NAME_MAPPINGS = { "EmAySee_LoraFilePicker": "EmAySee Lora File Picker", } -# WEB_DIRECTORY can be defined if your node has custom web components (JavaScript/CSS). -# For this node, it's not strictly necessary, but it's a common practice to include it -# if you plan to extend the node with a custom UI later. -# WEB_DIRECTORY = "./web" # Uncomment and create a 'web' folder if needed for JS/CSS +# WEB_DIRECTORY can be defined if your node has custom web components (JavaScript/CSS). +# For this node, it's not strictly necessary, but it's a common practice to include it +# if you plan to extend the node with a custom UI later. +# WEB_DIRECTORY = "./web" # Uncomment and create a 'web' folder if needed for JS/CSS diff --git a/py/EmAySee_LoraLoaderFromInput.py b/py/EmAySee_LoraLoaderFromInput.py index 1982a67..d419134 100755 --- a/py/EmAySee_LoraLoaderFromInput.py +++ b/py/EmAySee_LoraLoaderFromInput.py @@ -9,7 +9,7 @@ class EmAySee_LoraLoaderFromInput: "required": { "model": ("MODEL",), "clip": ("CLIP",), - # This input will connect to the output of the EmAySee Lora Name Selector + # This input will connect to the output of the EmAySee Lora Name Selector "lora_filename": ("LORA",), "strength_model": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), "strength_clip": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), @@ -22,16 +22,16 @@ class EmAySee_LoraLoaderFromInput: def EmAySee_load_lora(self, model, clip, lora_filename, strength_model, strength_clip): - # 1. Find the full path of the Lora file using the filename from input + # 1. Find the full path of the Lora file using the filename from input lora_path = folder_paths.get_full_path("loras", lora_filename) - # 2. Check if the file exists before proceeding + # 2. Check if the file exists before proceeding if lora_path is None: - # We print an error but return the original model/clip to prevent the workflow from crashing + # We print an error but return the original model/clip to prevent the workflow from crashing print(f"[EmAySee_LoraLoaderFromInput] Lora file not found: {lora_filename}. Skipping Lora application.") return (model, clip) - # 3. Load and apply the Lora using ComfyUI's standard utility function + # 3. Load and apply the Lora using ComfyUI's standard utility function model_lora, clip_lora = comfy.sd.load_lora(model, clip, lora_path, strength_model, strength_clip) return (model_lora, clip_lora) diff --git a/py/EmAySee_LoraSelectionNodes.py b/py/EmAySee_LoraSelectionNodes.py index c8fac48..b967eba 100755 --- a/py/EmAySee_LoraSelectionNodes.py +++ b/py/EmAySee_LoraSelectionNodes.py @@ -101,7 +101,7 @@ class EmAySee_LoraStacker20: "optional": {} } - # Create inputs for 20 Loras with individual strength controls + # Create inputs for 20 Loras with individual strength controls for i in range(1, 21): inputs["optional"][f"lora_{i}"] = ("LORA_OBJECT",) inputs["required"][f"strength_model_{i}"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}) @@ -117,13 +117,13 @@ class EmAySee_LoraStacker20: current_model = model current_clip = clip - # Iterate through all 20 possible slots + # Iterate through all 20 possible slots for i in range(1, 21): lora_obj = kwargs.get(f"lora_{i}", None) str_model = kwargs.get(f"strength_model_{i}", 1.0) str_clip = kwargs.get(f"strength_clip_{i}", 1.0) - # Only apply if a Lora Object is connected to the slot + # Only apply if a Lora Object is connected to the slot if lora_obj is not None: if str_model == 0 and str_clip == 0: continue diff --git a/py/EmAySee_LoraSelector.py b/py/EmAySee_LoraSelector.py index af038cb..be9d54b 100755 --- a/py/EmAySee_LoraSelector.py +++ b/py/EmAySee_LoraSelector.py @@ -7,11 +7,11 @@ class EmAySee_LoraSelector: """ @classmethod def INPUT_TYPES(s): - # Define the controls as part of the INPUT_TYPES but specify that they should - # be rendered as internal widgets (input_is_widget=True) + # Define the controls as part of the INPUT_TYPES but specify that they should + # be rendered as internal widgets (input_is_widget=True) return { "required": { - # This input is used solely for the internal widget rendering. + # This input is used solely for the internal widget rendering. "lora_name": ("LORA", {"default": "NONE", "input_is_widget": True, "widget_name": "lora_name_select"}), "strength_model": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01, "input_is_widget": True, "widget_name": "strength_model_select"}), "strength_clip": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01, "input_is_widget": True, "widget_name": "strength_clip_select"}), @@ -19,14 +19,14 @@ class EmAySee_LoraSelector: "optional": {}, } - # The output now includes the filename and the two strength values as separate outputs + # The output now includes the filename and the two strength values as separate outputs RETURN_TYPES = ("LORA", "FLOAT", "FLOAT",) RETURN_NAMES = ("lora_filename", "strength_model", "strength_clip",) CATEGORY = "EmAySee/Loaders" FUNCTION = "EmAySee_select_lora" def EmAySee_select_lora(self, lora_name, strength_model, strength_clip): - # The node receives values from its internal widgets and passes them out. + # The node receives values from its internal widgets and passes them out. return (lora_name, strength_model, strength_clip,) NODE_CLASS_MAPPINGS = { diff --git a/py/EmAySee_Lora_Path-Name-Selector.py b/py/EmAySee_Lora_Path-Name-Selector.py index 9122155..407ef67 100755 --- a/py/EmAySee_Lora_Path-Name-Selector.py +++ b/py/EmAySee_Lora_Path-Name-Selector.py @@ -1,8 +1,8 @@ import os -import folder_paths # ComfyUI utility to get model paths +import folder_paths # ComfyUI utility to get model paths -# Define the main class for your custom node. -# The class name will be used in NODE_CLASS_MAPPINGS. +# Define the main class for your custom node. +# The class name will be used in NODE_CLASS_MAPPINGS. class EmAySee_LoraFilePicker: """ A ComfyUI custom node to pick LORA files from configured paths. @@ -10,7 +10,7 @@ class EmAySee_LoraFilePicker: the selected filename and its full file path. """ def __init__(self): - # Constructor for the node. No special initialization needed for this node. + # Constructor for the node. No special initialization needed for this node. pass @classmethod @@ -19,37 +19,37 @@ class EmAySee_LoraFilePicker: Defines the input types (widgets and connections) for the node. This method is a class method and must return a dictionary. """ - # Get all LORA files from ComfyUI's configured LORA paths. - # folder_paths.get_filename_list("loras") returns a list of relative filenames - # (e.g., "my_lora.safetensors", "subdir/another_lora.pt"). + # Get all LORA files from ComfyUI's configured LORA paths. + # folder_paths.get_filename_list("loras") returns a list of relative filenames + # (e.g., "my_lora.safetensors", "subdir/another_lora.pt"). lora_files = folder_paths.get_filename_list("loras") - # Add a "None" option at the beginning of the list. - # This allows the user to explicitly select no LORA. + # Add a "None" option at the beginning of the list. + # This allows the user to explicitly select no LORA. lora_files.insert(0, "None") return { "required": { - # "lora_name" is the name of the input port that will appear on the node. - # (lora_files,) creates a dropdown widget in the ComfyUI interface, - # populated with the list of LORA files. + # "lora_name" is the name of the input port that will appear on the node. + # (lora_files,) creates a dropdown widget in the ComfyUI interface, + # populated with the list of LORA files. "lora_name": (lora_files,), }, } - # Define the types of data that this node will output. - # RETURN_TYPES must be a tuple of ComfyUI data types (e.g., "STRING", "IMAGE", "MODEL"). - RETURN_TYPES = ("STRING", "STRING",) # Outputs two strings. + # Define the types of data that this node will output. + # RETURN_TYPES must be a tuple of ComfyUI data types (e.g., "STRING", "IMAGE", "MODEL"). + RETURN_TYPES = ("STRING", "STRING",) # Outputs two strings. - # Define the names for the output ports. These names will appear on the node. + # Define the names for the output ports. These names will appear on the node. RETURN_NAMES = ("filename", "full_filepath",) - # Specifies the Python function within this class that ComfyUI should call - # when the node is executed in a workflow. + # Specifies the Python function within this class that ComfyUI should call + # when the node is executed in a workflow. FUNCTION = "pick_lora_file" - # Defines the category under which this node will appear in the ComfyUI "Add Node" menu. - # Using "EmAySee/" as a prefix helps organize your custom nodes. + # Defines the category under which this node will appear in the ComfyUI "Add Node" menu. + # Using "EmAySee/" as a prefix helps organize your custom nodes. CATEGORY = "EmAySee/File Utilities" def pick_lora_file(self, lora_name): @@ -58,60 +58,60 @@ class EmAySee_LoraFilePicker: It takes the selected LORA filename as input and returns the filename and its corresponding full file path. """ - # If the "None" option is selected in the dropdown, return empty strings. + # If the "None" option is selected in the dropdown, return empty strings. if lora_name == "None": return ("", "") full_lora_path = None - # Attempt to find the full path of the selected LORA file. - # folder_paths.get_filename_list("loras", return_full_paths=True) - # returns a list of absolute paths for all LORA files that ComfyUI has discovered. + # Attempt to find the full path of the selected LORA file. + # folder_paths.get_filename_list("loras", return_full_paths=True) + # returns a list of absolute paths for all LORA files that ComfyUI has discovered. for lora_path in folder_paths.get_filename_list("loras", return_full_paths=True): - # Check if the base name of the current lora_path matches the selected lora_name, - # or if the full lora_path ends with the selected lora_name (this handles - # cases where lora_name might include subdirectories, e.g., "my_folder/my_lora.safetensors"). + # Check if the base name of the current lora_path matches the selected lora_name, + # or if the full lora_path ends with the selected lora_name (this handles + # cases where lora_name might include subdirectories, e.g., "my_folder/my_lora.safetensors"). if os.path.basename(lora_path) == lora_name or lora_path.endswith(lora_name): full_lora_path = lora_path break - # Fallback mechanism: If the LORA was not found by direct matching (e.g., due to - # inconsistencies in how names are reported vs. stored, or new files not yet indexed). + # Fallback mechanism: If the LORA was not found by direct matching (e.g., due to + # inconsistencies in how names are reported vs. stored, or new files not yet indexed). if full_lora_path is None: - # Get all configured base directories for LORAs. + # Get all configured base directories for LORAs. lora_folders = folder_paths.get_folder_paths("loras") for folder in lora_folders: - # Construct a potential full path by joining the base folder and the selected LORA name. + # Construct a potential full path by joining the base folder and the selected LORA name. potential_path = os.path.join(folder, lora_name) - # Verify if this constructed path actually exists and points to a file. + # Verify if this constructed path actually exists and points to a file. if os.path.exists(potential_path) and os.path.isfile(potential_path): full_lora_path = potential_path break - # If after all attempts, the file still cannot be located, print a warning - # and return empty strings to ensure the workflow doesn't crash. + # If after all attempts, the file still cannot be located, print a warning + # and return empty strings to ensure the workflow doesn't crash. if full_lora_path is None: print(f"Warning: LORA file '{lora_name}' not found in known ComfyUI LORA paths.") return ("", "") - # Return the selected filename and its successfully determined full path. + # Return the selected filename and its successfully determined full path. return (lora_name, full_lora_path) -# NODE_CLASS_MAPPINGS is a mandatory dictionary that ComfyUI uses to discover -# and register your custom nodes. The keys are internal identifiers, and the -# values are the Python classes that define your nodes. +# NODE_CLASS_MAPPINGS is a mandatory dictionary that ComfyUI uses to discover +# and register your custom nodes. The keys are internal identifiers, and the +# values are the Python classes that define your nodes. NODE_CLASS_MAPPINGS = { "EmAySee_LoraFilePicker": EmAySee_LoraFilePicker, } -# NODE_DISPLAY_NAME_MAPPINGS is an optional dictionary that provides -# more user-friendly names for your nodes in the ComfyUI interface. -# This name will appear in the "Add Node" menu. +# NODE_DISPLAY_NAME_MAPPINGS is an optional dictionary that provides +# more user-friendly names for your nodes in the ComfyUI interface. +# This name will appear in the "Add Node" menu. NODE_DISPLAY_NAME_MAPPINGS = { "EmAySee_LoraFilePicker": "EmAySee Lora File Picker", } -# WEB_DIRECTORY can be defined if your node has custom web components (JavaScript/CSS). -# For this node, it's not strictly necessary, but it's a common practice to include it -# if you plan to extend the node with a custom UI later. -# WEB_DIRECTORY = "./web" # Uncomment and create a 'web' folder if needed for JS/CSS \ No newline at end of file +# WEB_DIRECTORY can be defined if your node has custom web components (JavaScript/CSS). +# For this node, it's not strictly necessary, but it's a common practice to include it +# if you plan to extend the node with a custom UI later. +# WEB_DIRECTORY = "./web" # Uncomment and create a 'web' folder if needed for JS/CSS \ No newline at end of file diff --git a/py/EmAySee_MetadataFormatter.py b/py/EmAySee_MetadataFormatter.py index 220db58..fb3462f 100755 --- a/py/EmAySee_MetadataFormatter.py +++ b/py/EmAySee_MetadataFormatter.py @@ -1,6 +1,6 @@ import json -# Node class mapping and display name (how it appears in ComfyUI) +# Node class mapping and display name (how it appears in ComfyUI) NODE_CLASS_MAPPINGS = {} NODE_DISPLAY_NAME_MAPPINGS = {} @@ -13,17 +13,17 @@ class EmAySee_MetadataFormatter: def __init__(self): pass - # Define the return types of the node's outputs + # Define the return types of the node's outputs RETURN_TYPES = ("STRING",) - # Define the names of the node's outputs + # Define the names of the node's outputs RETURN_NAMES = ("formatted_metadata_string",) - # Define the category where the node will appear in ComfyUI + # Define the category where the node will appear in ComfyUI FUNCTION = "format_metadata" - CATEGORY = "Mac Custom Nodes/LoRA Tools" # Consistent with your other node + CATEGORY = "Mac Custom Nodes/LoRA Tools" # Consistent with your other node - # Define the input types of the node + # Define the input types of the node @classmethod def INPUT_TYPES(s): return { @@ -38,39 +38,39 @@ class EmAySee_MetadataFormatter: a readable line-by-line string. """ try: - # Parse the input JSON string + # Parse the input JSON string metadata = json.loads(metadata_json_string) except json.JSONDecodeError as e: return (f"Error: Invalid JSON input for metadata: {e}",) formatted_lines = [] - # Add a header for clarity + # Add a header for clarity formatted_lines.append("--- LoRA Training Parameters ---") - # Sort keys for consistent output order + # Sort keys for consistent output order sorted_keys = sorted(metadata.keys()) for key in sorted_keys: value = metadata[key] - display_key = key.replace("ss_", "") # Remove ss_ prefix for display + display_key = key.replace("ss_", "") # Remove ss_ prefix for display - # Skip internal metadata source flag + # Skip internal metadata source flag if key == "_metadata_source": continue - # Handle specific nested JSON fields for better readability + # Handle specific nested JSON fields for better readability if key == "ss_tag_frequency": formatted_lines.append(f"\n{display_key.replace('_', ' ').title()}:") - # Parse the nested tag frequency JSON string - if isinstance(value, dict): # Should already be parsed by Extractor node + # Parse the nested tag frequency JSON string + if isinstance(value, dict): # Should already be parsed by Extractor node for sub_key, sub_value in value.items(): formatted_lines.append(f" - {sub_key}: {sub_value}") else: formatted_lines.append(f" (Raw data, could not parse: {value})") elif key == "ss_bucket_info": formatted_lines.append(f"\n{display_key.replace('_', ' ').title()}:") - if isinstance(value, dict): # Should already be parsed by Extractor node + if isinstance(value, dict): # Should already be parsed by Extractor node if "buckets" in value and isinstance(value["buckets"], dict): formatted_lines.append(" Buckets:") for bucket_id, bucket_data in value["buckets"].items(): @@ -83,11 +83,11 @@ class EmAySee_MetadataFormatter: formatted_lines.append(f" (Raw data, could not parse: {value})") elif key == "ss_datasets": formatted_lines.append(f"\n{display_key.replace('_', ' ').title()}:") - if isinstance(value, list): # Should already be parsed by Extractor node + if isinstance(value, list): # Should already be parsed by Extractor node for i, dataset_entry in enumerate(value): formatted_lines.append(f" Dataset Entry {i}:") for ds_key, ds_val in dataset_entry.items(): - if isinstance(ds_val, dict): # For nested dicts like tag_frequency within datasets + if isinstance(ds_val, dict): # For nested dicts like tag_frequency within datasets formatted_lines.append(f" - {ds_key.replace('_', ' ').title()}:") for sub_key, sub_val in ds_val.items(): formatted_lines.append(f" - {sub_key}: {sub_val}") @@ -96,11 +96,11 @@ class EmAySee_MetadataFormatter: else: formatted_lines.append(f" (Raw data, could not parse: {value})") else: - # Format other parameters - # Attempt to make values more readable (e.g., float to scientific notation if very small) + # Format other parameters + # Attempt to make values more readable (e.g., float to scientific notation if very small) display_value = value if isinstance(value, float) and value < 0.001 and value != 0.0: - display_value = f"{value:.1e}" # Scientific notation for small floats + display_value = f"{value:.1e}" # Scientific notation for small floats formatted_lines.append(f"{display_key.replace('_', ' ').title()}: {display_value}") @@ -108,6 +108,6 @@ class EmAySee_MetadataFormatter: return ("\n".join(formatted_lines),) -# Register the node +# Register the node NODE_CLASS_MAPPINGS["EmAySee_MetadataFormatter"] = EmAySee_MetadataFormatter NODE_DISPLAY_NAME_MAPPINGS["EmAySee_LoRA Metadata Formatter"] = "EmAySee_LoRA Metadata Formatter" diff --git a/py/EmAySee_ProbabilityStringSelectorNode.py b/py/EmAySee_ProbabilityStringSelectorNode.py index a2c27fb..555dd92 100755 --- a/py/EmAySee_ProbabilityStringSelectorNode.py +++ b/py/EmAySee_ProbabilityStringSelectorNode.py @@ -10,10 +10,10 @@ class EmAySee_ProbabilityStringSelectorNode: "string_input_1": ("STRING", {"default": "Option 1 - Less Likely"}), "string_input_2": ("STRING", {"default": "Option 2 - More Likely"}), "probability_option_1": ("FLOAT", { - "default": 0.1, # Default probability of 10% for option 1 - "min": 0.0, # Minimum probability is 0% - "max": 1.0, # Maximum probability is 100% - "step": 0.01, # Probability step of 1% + "default": 0.1, # Default probability of 10% for option 1 + "min": 0.0, # Minimum probability is 0% + "max": 1.0, # Maximum probability is 100% + "step": 0.01, # Probability step of 1% "label": "Probability of Option 1 (0.0-1.0)" }), }, @@ -26,13 +26,13 @@ class EmAySee_ProbabilityStringSelectorNode: FUNCTION = "EmAySee_function" def EmAySee_function(self, string_input_1, string_input_2, probability_option_1): - # Generate a random float between 0.0 and 1.0 + # Generate a random float between 0.0 and 1.0 random_value = random.random() if random_value < probability_option_1: - return (string_input_1,) # Select string_input_1 if random_value is less than the probability + return (string_input_1,) # Select string_input_1 if random_value is less than the probability else: - return (string_input_2,) # Otherwise, select string_input_2 + return (string_input_2,) # Otherwise, select string_input_2 NODE_CLASS_MAPPINGS = { "EmAySee_ProbabilityStringSelectorNode": EmAySee_ProbabilityStringSelectorNode diff --git a/py/EmAySee_QwenPixelAligner.py b/py/EmAySee_QwenPixelAligner.py index 2809c1f..f7b7756 100755 --- a/py/EmAySee_QwenPixelAligner.py +++ b/py/EmAySee_QwenPixelAligner.py @@ -4,7 +4,7 @@ import torch.nn.functional as F class EmAySee_QwenPixelAligner: @classmethod def INPUT_TYPES(s): - # Generate the list of resolutions from 784 to 2240 in steps of 112 + # Generate the list of resolutions from 784 to 2240 in steps of 112 resolutions = [str(x) for x in range(784, 2241, 112)] return { @@ -25,33 +25,33 @@ class EmAySee_QwenPixelAligner: target_edge = int(longest_edge) B, H, W, C = image.shape - # Calculate aspect ratio and new dimensions + # Calculate aspect ratio and new dimensions current_longest = max(H, W) - # Scale factor to match the longest edge target + # Scale factor to match the longest edge target scale_factor = target_edge / current_longest target_h = H * scale_factor target_w = W * scale_factor - # Snap dimensions to the nearest alignment multiple + # Snap dimensions to the nearest alignment multiple new_height = int(round(target_h / align_val) * align_val) new_width = int(round(target_w / align_val) * align_val) - # Ensure dimensions are at least one alignment block + # Ensure dimensions are at least one alignment block new_height = max(align_val, new_height) new_width = max(align_val, new_width) - # Permute image to [B, C, H, W] for interpolation + # Permute image to [B, C, H, W] for interpolation img_permuted = image.permute(0, 3, 1, 2) - # Resize using bicubic interpolation + # Resize using bicubic interpolation resized_img = F.interpolate(img_permuted, size=(new_height, new_width), mode="bicubic", align_corners=False) - # Permute back to [B, H, W, C] + # Permute back to [B, H, W, C] final_image = resized_img.permute(0, 2, 3, 1) - # Create resolution string + # Create resolution string res_text = f"{new_width}x{new_height}" return (final_image, new_width, new_height, res_text) diff --git a/py/EmAySee_RandomIntFromList.py b/py/EmAySee_RandomIntFromList.py index 47a0a8b..0f46874 100755 --- a/py/EmAySee_RandomIntFromList.py +++ b/py/EmAySee_RandomIntFromList.py @@ -1,5 +1,5 @@ import random -import time # Import the time module +import time # Import the time module class EmAySee_RandomIntFromList: """ @@ -21,10 +21,10 @@ class EmAySee_RandomIntFromList: }), "seed": ("INT", { "default": 0, - "min": 0, # Minimum seed value - "step": 1, # Increment/decrement step - "display": "number" # Optional, for better UI - # Can add "forceInput": True to *require* a connection. + "min": 0, # Minimum seed value + "step": 1, # Increment/decrement step + "display": "number" # Optional, for better UI + # Can add "forceInput": True to *require* a connection. }), }, "optional": { @@ -50,12 +50,12 @@ class EmAySee_RandomIntFromList: if not numbers: return (0,) - # Seed handling: + # Seed handling: if use_system_time: - # Use system time * number of milliseconds for a more unique seed each run - seed = int(time.time() * 1000) # Use milliseconds for more variation + # Use system time * number of milliseconds for a more unique seed each run + seed = int(time.time() * 1000) # Use milliseconds for more variation - random.seed(seed) # Set the seed *before* calling random.choice + random.seed(seed) # Set the seed *before* calling random.choice random_int = random.choice(numbers) return (random_int,) diff --git a/py/EmAySee_RandomIntFromList2.py b/py/EmAySee_RandomIntFromList2.py index 20f3564..673ea09 100755 --- a/py/EmAySee_RandomIntFromList2.py +++ b/py/EmAySee_RandomIntFromList2.py @@ -1,5 +1,5 @@ import random -import time # Import the time module +import time # Import the time module class EmAySee_RandomIntFromList: """ @@ -21,10 +21,10 @@ class EmAySee_RandomIntFromList: }), "seed": ("INT", { "default": 0, - "min": 0, # Minimum seed value - "step": 1, # Increment/decrement step - "display": "number" # Optional, for better UI - # Can add "forceInput": True to *require* a connection. + "min": 0, # Minimum seed value + "step": 1, # Increment/decrement step + "display": "number" # Optional, for better UI + # Can add "forceInput": True to *require* a connection. }), }, "optional": { @@ -50,12 +50,12 @@ class EmAySee_RandomIntFromList: if not numbers: return (0,) - # Seed handling: + # Seed handling: if use_system_time: - # Use system time * number of milliseconds for a more unique seed each run - seed = int(time.time() * 1000) # Use milliseconds for more variation + # Use system time * number of milliseconds for a more unique seed each run + seed = int(time.time() * 1000) # Use milliseconds for more variation - random.seed(seed) # Set the seed *before* calling random.choice + random.seed(seed) # Set the seed *before* calling random.choice random_int = random.choice(numbers) return (random_int,) diff --git a/py/EmAySee_RandomIntegerFromListNode.py b/py/EmAySee_RandomIntegerFromListNode.py index a1bf3c8..71b0f44 100755 --- a/py/EmAySee_RandomIntegerFromListNode.py +++ b/py/EmAySee_RandomIntegerFromListNode.py @@ -32,7 +32,7 @@ class EmAySee_RandomIntegerFromListNode: return (selected_integer,) else: print("EmAySee Random Integer From List: No valid integers found in input string. Returning 0.") - return (0,) # Or you could return (None,) if you prefer None when no valid integers + return (0,) # Or you could return (None,) if you prefer None when no valid integers NODE_CLASS_MAPPINGS = { "EmAySee_RandomIntegerFromListNode": EmAySee_RandomIntegerFromListNode diff --git a/py/EmAySee_RandomIntegerFromTogglesNode_PremadeLabels.py b/py/EmAySee_RandomIntegerFromTogglesNode_PremadeLabels.py index e33b7b9..1a4465b 100755 --- a/py/EmAySee_RandomIntegerFromTogglesNode_PremadeLabels.py +++ b/py/EmAySee_RandomIntegerFromTogglesNode_PremadeLabels.py @@ -8,7 +8,7 @@ class EmAySee_RandomIntegerFromTogglesNode_PremadeLabels: def INPUT_TYPES(s): return {"required": { "node_description": ("STRING", {"multiline": True, "default": "Select options below to choose from randomly."}), - "option_1": ("TOGGLE", {"default": False, "label": "Option 1: Label for Option 1"}), # Example labels + "option_1": ("TOGGLE", {"default": False, "label": "Option 1: Label for Option 1"}), # Example labels "option_2": ("BOOLEAN", {"default": False, "label": "Option 2: Label for Option 2"}), "option_3": ("BOOLEAN", {"default": False, "label": "Option 3: Label for Option 3"}), "option_4": ("BOOLEAN", {"default": False, "label": "Option 4: Label for Option 4"}), @@ -36,10 +36,10 @@ class EmAySee_RandomIntegerFromTogglesNode_PremadeLabels: FUNCTION = "get_random_integer_from_toggles" - def get_random_integer_from_toggles(self, node_description, **kwargs): # Only node_description and kwargs now + def get_random_integer_from_toggles(self, node_description, **kwargs): # Only node_description and kwargs now selected_integers = [] for i in range(1, 21): - toggle_name = f"option_{i}" # Toggle input names are now option_1, option_2,... + toggle_name = f"option_{i}" # Toggle input names are now option_1, option_2,... if kwargs.get(toggle_name, False): selected_integers.append(i) diff --git a/py/EmAySee_RandomLoraSelector.py b/py/EmAySee_RandomLoraSelector.py index dd1e940..1003d7f 100755 --- a/py/EmAySee_RandomLoraSelector.py +++ b/py/EmAySee_RandomLoraSelector.py @@ -18,7 +18,7 @@ class EmAySee_RandomLoraLoader: } } - # Dynamically create 10 LoRA slots with activation toggles + # Dynamically create 10 LoRA slots with activation toggles for i in range(1, 11): inputs["required"][f"lora_name_{i}"] = (lora_list,) inputs["required"][f"lora_active_{i}"] = ("BOOLEAN", {"default": True}) @@ -32,7 +32,7 @@ class EmAySee_RandomLoraLoader: def load_random_lora(self, model, clip, strength_model, strength_clip, seed, **kwargs): lora_options = [] - # Collect all active LoRAs from the inputs + # Collect all active LoRAs from the inputs for i in range(1, 11): lora_name = kwargs.get(f"lora_name_{i}") is_active = kwargs.get(f"lora_active_{i}") @@ -42,17 +42,17 @@ class EmAySee_RandomLoraLoader: selected_lora_name = "None" selected_lora_index = 0 - # If no valid loras are active or strength is 0, pass through the original model/clip + # If no valid loras are active or strength is 0, pass through the original model/clip if not lora_options or (strength_model == 0 and strength_clip == 0): return (model, clip, selected_lora_name, selected_lora_index) - # Use the provided seed for reproducibility + # Use the provided seed for reproducibility random.seed(seed) selected_lora = random.choice(lora_options) selected_lora_name = selected_lora["name"] selected_lora_index = selected_lora["index"] - # --- LoRA Loading Logic (adapted from the default LoraLoader) --- + # --- LoRA Loading Logic (adapted from the default LoraLoader) --- lora_path = folder_paths.get_full_path("loras", selected_lora_name) lora = comfy.utils.load_torch_file(lora_path, safe_load=True) model_lora, clip_lora = comfy.sd.load_lora_for_models(model, clip, lora, strength_model, strength_clip) diff --git a/py/EmAySee_RandomStringSelectorNode.py b/py/EmAySee_RandomStringSelectorNode.py index 4a6aabb..43a52de 100755 --- a/py/EmAySee_RandomStringSelectorNode.py +++ b/py/EmAySee_RandomStringSelectorNode.py @@ -1,7 +1,7 @@ import comfy.sd import torch import numpy as np -import random # Import the random module +import random # Import the random module class EmAySee_RandomStringSelectorNode: @classmethod @@ -14,18 +14,18 @@ class EmAySee_RandomStringSelectorNode: RETURN_TYPES = ("STRING",) RETURN_NAMES = ("RANDOM_SELECTED_STRING",) - CATEGORY = "EmAySee_Utils" # Or a category you prefer + CATEGORY = "EmAySee_Utils" # Or a category you prefer FUNCTION = "EmAySee_function" def EmAySee_function(self, string_input_1, string_input_2): - # Generate a random number (0 or 1) + # Generate a random number (0 or 1) random_choice = random.randint(0, 1) if random_choice == 0: - return (string_input_1,) # Return the first input if random_choice is 0 + return (string_input_1,) # Return the first input if random_choice is 0 else: - return (string_input_2,) # Return the second input if random_choice is 1 + return (string_input_2,) # Return the second input if random_choice is 1 NODE_CLASS_MAPPINGS = { "EmAySee_RandomStringSelectorNode": EmAySee_RandomStringSelectorNode diff --git a/py/EmAySee_RandomStringSelectorNode2.py b/py/EmAySee_RandomStringSelectorNode2.py index 6abccbe..660c6ea 100755 --- a/py/EmAySee_RandomStringSelectorNode2.py +++ b/py/EmAySee_RandomStringSelectorNode2.py @@ -1,7 +1,7 @@ import comfy.sd import torch import numpy as np -import random # Import the random module +import random # Import the random module class EmAySee_RandomStringSelectorNode: @classmethod @@ -14,18 +14,18 @@ class EmAySee_RandomStringSelectorNode: RETURN_TYPES = ("STRING",) RETURN_NAMES = ("RANDOM_SELECTED_STRING",) - CATEGORY = "EmAySee_Utils" # Or a category you prefer + CATEGORY = "EmAySee_Utils" # Or a category you prefer FUNCTION = "EmAySee_function" def EmAySee_function(self, string_input_1, string_input_2): - # Generate a random number (0 or 1) + # Generate a random number (0 or 1) random_choice = random.randint(0, 1) if random_choice == 0: - return (string_input_1,) # Return the first input if random_choice is 0 + return (string_input_1,) # Return the first input if random_choice is 0 else: - return (string_input_2,) # Return the second input if random_choice is 1 + return (string_input_2,) # Return the second input if random_choice is 1 NODE_CLASS_MAPPINGS = { "EmAySee_RandomStringSelectorNode": EmAySee_RandomStringSelectorNode diff --git a/py/EmAySee_RandomStringSelectorNodeFourChoice.py b/py/EmAySee_RandomStringSelectorNodeFourChoice.py index 80bdf95..360edb1 100755 --- a/py/EmAySee_RandomStringSelectorNodeFourChoice.py +++ b/py/EmAySee_RandomStringSelectorNodeFourChoice.py @@ -1,7 +1,7 @@ import comfy.sd import torch import numpy as np -import random # Import the random module +import random # Import the random module class EmAySee_RandomStringSelectorNodeFourChoice: @classmethod @@ -16,22 +16,22 @@ class EmAySee_RandomStringSelectorNodeFourChoice: RETURN_TYPES = ("STRING",) RETURN_NAMES = ("RANDOM_SELECTED_STRING",) - CATEGORY = "EmAySee_Utils" # Or a category you prefer + CATEGORY = "EmAySee_Utils" # Or a category you prefer FUNCTION = "EmAySee_function" def EmAySee_function(self, string_input_1, string_input_2, string_input_3, string_input_4): - # Generate a random number (0 , 1 , 2 , 3) + # Generate a random number (0 , 1 , 2 , 3) random_choice = random.randint(0, 3) print(random_choice) if random_choice == 0: - return (string_input_1,) # Return the first input if random_choice is 0 + return (string_input_1,) # Return the first input if random_choice is 0 if random_choice == 1: - return (string_input_2,) # Return the first input if random_choice is 1 + return (string_input_2,) # Return the first input if random_choice is 1 if random_choice == 2: - return (string_input_3,) # Return the first input if random_choice is 2 + return (string_input_3,) # Return the first input if random_choice is 2 else: - return (string_input_4,) # Return the second input if random_choice is 3 + return (string_input_4,) # Return the second input if random_choice is 3 NODE_CLASS_MAPPINGS = { "EmAySee_RandomStringSelectorNodeFourChoice": EmAySee_RandomStringSelectorNodeFourChoice diff --git a/py/EmAySee_RandomStringSelectorNodeThreeChoice.py b/py/EmAySee_RandomStringSelectorNodeThreeChoice.py index ef845fa..fa1387a 100755 --- a/py/EmAySee_RandomStringSelectorNodeThreeChoice.py +++ b/py/EmAySee_RandomStringSelectorNodeThreeChoice.py @@ -1,7 +1,7 @@ import comfy.sd import torch import numpy as np -import random # Import the random module +import random # Import the random module class EmAySee_RandomStringSelectorNodeThreeChoice: @classmethod @@ -15,20 +15,20 @@ class EmAySee_RandomStringSelectorNodeThreeChoice: RETURN_TYPES = ("STRING",) RETURN_NAMES = ("RANDOM_SELECTED_STRING",) - CATEGORY = "EmAySee_Utils" # Or a category you prefer + CATEGORY = "EmAySee_Utils" # Or a category you prefer FUNCTION = "EmAySee_function" def EmAySee_function(self, string_input_1, string_input_2, string_input_3): - # Generate a random number (0 , 1 , 2 ) + # Generate a random number (0 , 1 , 2 ) random_choice = random.randint(0, 2) if random_choice == 0: - return (string_input_1,) # Return the first input if random_choice is 0 + return (string_input_1,) # Return the first input if random_choice is 0 if random_choice == 1: - return (string_input_2,) # Return the first input if random_choice is 1 + return (string_input_2,) # Return the first input if random_choice is 1 else: - return (string_input_3,) # Return the first input if random_choice is 2 + return (string_input_3,) # Return the first input if random_choice is 2 NODE_CLASS_MAPPINGS = { diff --git a/py/EmAySee_RemoveDuplicatesFromString.py b/py/EmAySee_RemoveDuplicatesFromString.py index d448004..bee9e31 100755 --- a/py/EmAySee_RemoveDuplicatesFromString.py +++ b/py/EmAySee_RemoveDuplicatesFromString.py @@ -7,50 +7,50 @@ class EmAySee_RemoveDuplicatesFromString: def INPUT_TYPES(s): return { "required": { - "input_string": ("STRING", {"multiline": True, "default": "apple, banana, apple, orange, banana, grape"}), # Comma-separated list of words + "input_string": ("STRING", {"multiline": True, "default": "apple, banana, apple, orange, banana, grape"}), # Comma-separated list of words } } - RETURN_TYPES = ("STRING",) # Output is a single string - RETURN_NAMES = ("unique_list_string",) # Name for the output - CATEGORY = "EmAySee_Text" # Category in the ComfyUI menu - TITLE = "EmAySee Remove Duplicates From String" # Title displayed on the node + RETURN_TYPES = ("STRING",) # Output is a single string + RETURN_NAMES = ("unique_list_string",) # Name for the output + CATEGORY = "EmAySee_Text" # Category in the ComfyUI menu + TITLE = "EmAySee Remove Duplicates From String" # Title displayed on the node - FUNCTION = "EmAySee_process_string" # The method that will be executed + FUNCTION = "EmAySee_process_string" # The method that will be executed def EmAySee_process_string(self, input_string): """ Splits the input string, removes duplicates, and rejoins. """ if not input_string: - return ("",) # Return an empty string if input is empty + return ("",) # Return an empty string if input is empty - # 1. Split the string by comma - # strip() removes leading/trailing whitespace from each word + # 1. Split the string by comma + # strip() removes leading/trailing whitespace from each word words = [word.strip() for word in input_string.split(',')] - # 2. Use a set to automatically handle uniqueness - # Sets do not preserve order, but for a simple list of words, - # this is often acceptable. If order is crucial, more complex - # logic would be needed (e.g., iterating and adding to a new list - # only if not already present). + # 2. Use a set to automatically handle uniqueness + # Sets do not preserve order, but for a simple list of words, + # this is often acceptable. If order is crucial, more complex + # logic would be needed (e.g., iterating and adding to a new list + # only if not already present). unique_words_set = set(words) - # 3. Convert the set back to a list (optional, but useful for joining) - # Sorting here makes the output consistent, otherwise order is arbitrary. + # 3. Convert the set back to a list (optional, but useful for joining) + # Sorting here makes the output consistent, otherwise order is arbitrary. unique_words_list = sorted(list(unique_words_set)) - # 4. Join the unique words back into a comma-separated string + # 4. Join the unique words back into a comma-separated string output_string = ", ".join(unique_words_list) - return (output_string,) # Return the result as a tuple + return (output_string,) # Return the result as a tuple -# Mapping of node class name to the class +# Mapping of node class name to the class NODE_CLASS_MAPPINGS = { "EmAySee_RemoveDuplicatesFromString": EmAySee_RemoveDuplicatesFromString } -# Mapping of node class name to the display name in the UI +# Mapping of node class name to the display name in the UI NODE_DISPLAY_NAME_MAPPINGS = { "EmAySee_RemoveDuplicatesFromString": "EmAySee Remove Duplicates From String" } \ No newline at end of file diff --git a/py/EmAySee_RemoveDuplicatesFromString_V2.py b/py/EmAySee_RemoveDuplicatesFromString_V2.py index 19e71dc..a4a31ba 100755 --- a/py/EmAySee_RemoveDuplicatesFromString_V2.py +++ b/py/EmAySee_RemoveDuplicatesFromString_V2.py @@ -8,17 +8,17 @@ class EmAySee_RemoveDuplicatesFromStringV2: def INPUT_TYPES(s): return { "required": { - "input_string": ("STRING", {"multiline": True, "default": "apple, banana, apple, orange, banana, grape"}), # Comma-separated list of words - "sort_output": ("BOOLEAN", {"default": True}), # New: Toggle to sort the unique words alphabetically + "input_string": ("STRING", {"multiline": True, "default": "apple, banana, apple, orange, banana, grape"}), # Comma-separated list of words + "sort_output": ("BOOLEAN", {"default": True}), # New: Toggle to sort the unique words alphabetically } } - RETURN_TYPES = ("STRING",) # Output is a single string - RETURN_NAMES = ("unique_list_string",) # Name for the output - CATEGORY = "EmAySee_Text" # Category in the ComfyUI menu - TITLE = "EmAySee Remove Duplicates From String" # Title displayed on the node + RETURN_TYPES = ("STRING",) # Output is a single string + RETURN_NAMES = ("unique_list_string",) # Name for the output + CATEGORY = "EmAySee_Text" # Category in the ComfyUI menu + TITLE = "EmAySee Remove Duplicates From String" # Title displayed on the node - FUNCTION = "EmAySee_process_string" # The method that will be executed + FUNCTION = "EmAySee_process_string" # The method that will be executed def EmAySee_process_string(self, input_string, sort_output): """ @@ -26,31 +26,31 @@ class EmAySee_RemoveDuplicatesFromStringV2: Can optionally sort the unique words. """ if not input_string: - return ("",) # Return an empty string if input is empty + return ("",) # Return an empty string if input is empty - # 1. Split the string by comma and strip whitespace from each word + # 1. Split the string by comma and strip whitespace from each word words = [word.strip() for word in input_string.split(',')] - # 2. Remove duplicates while preserving original order (for Python 3.7+ dicts are insertion-ordered) - # This method uses dict.fromkeys() to create a dict where keys are unique words - # and then converts the keys back to a list, preserving their first-seen order. + # 2. Remove duplicates while preserving original order (for Python 3.7+ dicts are insertion-ordered) + # This method uses dict.fromkeys() to create a dict where keys are unique words + # and then converts the keys back to a list, preserving their first-seen order. unique_words_list = list(dict.fromkeys(words)) - # 3. If sorting is requested, sort the list alphabetically + # 3. If sorting is requested, sort the list alphabetically if sort_output: - unique_words_list.sort() # Sorts in-place alphabetically + unique_words_list.sort() # Sorts in-place alphabetically - # 4. Join the unique words back into a comma-separated string + # 4. Join the unique words back into a comma-separated string output_string = ", ".join(unique_words_list) - return (output_string,) # Return the result as a tuple + return (output_string,) # Return the result as a tuple -# Mapping of node class name to the class +# Mapping of node class name to the class NODE_CLASS_MAPPINGS = { "EmAySee_RemoveDuplicatesFromStringV2": EmAySee_RemoveDuplicatesFromStringV2 } -# Mapping of node class name to the display name in the UI +# Mapping of node class name to the display name in the UI NODE_DISPLAY_NAME_MAPPINGS = { "EmAySee_RemoveDuplicatesFromStringV2": "EmAySee Remove Duplicates From StringV2" } diff --git a/py/EmAySee_RemoveWordsFromText.py b/py/EmAySee_RemoveWordsFromText.py index 0e33f53..e842305 100755 --- a/py/EmAySee_RemoveWordsFromText.py +++ b/py/EmAySee_RemoveWordsFromText.py @@ -8,47 +8,47 @@ class EmAySee_RemoveWordsFromText: def INPUT_TYPES(s): return { "required": { - "main_text": ("STRING", {"multiline": True, "default": "A dog, a cat, and a bird."}), # The main text to be parsed - "words_to_remove": ("STRING", {"multiline": True, "default": "dog, cat, and"}), # Comma-separated list of words to remove + "main_text": ("STRING", {"multiline": True, "default": "A dog, a cat, and a bird."}), # The main text to be parsed + "words_to_remove": ("STRING", {"multiline": True, "default": "dog, cat, and"}), # Comma-separated list of words to remove } } - RETURN_TYPES = ("STRING",) # Output is a single string - RETURN_NAMES = ("cleaned_text",) # Name for the output - CATEGORY = "EmAySee_Text" # Category in the ComfyUI menu - TITLE = "EmAySee Remove Words From Text" # Title displayed on the node + RETURN_TYPES = ("STRING",) # Output is a single string + RETURN_NAMES = ("cleaned_text",) # Name for the output + CATEGORY = "EmAySee_Text" # Category in the ComfyUI menu + TITLE = "EmAySee Remove Words From Text" # Title displayed on the node - FUNCTION = "EmAySee_process_text" # The method that will be executed + FUNCTION = "EmAySee_process_text" # The method that will be executed def EmAySee_process_text(self, main_text, words_to_remove): """ Removes each word from the 'words_to_remove' list from the 'main_text' string. """ if not main_text or not words_to_remove: - return (main_text,) # Return the original text if either input is empty + return (main_text,) # Return the original text if either input is empty - # 1. Split the string of words to remove by comma, and strip whitespace. - # We escape special characters for use in a regex pattern. + # 1. Split the string of words to remove by comma, and strip whitespace. + # We escape special characters for use in a regex pattern. words_list = [re.escape(word.strip()) for word in words_to_remove.split(',')] - # 2. Join the words with a pipe '|' to create a regex OR pattern. - # \b ensures we match whole words only, avoiding partial matches. - # For example, we don't want to remove "cat" from "caterpillar". + # 2. Join the words with a pipe '|' to create a regex OR pattern. + # \b ensures we match whole words only, avoiding partial matches. + # For example, we don't want to remove "cat" from "caterpillar". pattern = r"\b(" + "|".join(words_list) + r")\b" - # 3. Use re.sub to find and replace all matching words with an empty string. - # We also replace multiple spaces that might be left behind. + # 3. Use re.sub to find and replace all matching words with an empty string. + # We also replace multiple spaces that might be left behind. cleaned_text = re.sub(pattern, "", main_text, flags=re.IGNORECASE) cleaned_text = re.sub(r'\s{2,}', ' ', cleaned_text).strip() - return (cleaned_text,) # Return the cleaned string as a tuple + return (cleaned_text,) # Return the cleaned string as a tuple -# Mapping of node class name to the class +# Mapping of node class name to the class NODE_CLASS_MAPPINGS = { "EmAySee_RemoveWordsFromText": EmAySee_RemoveWordsFromText } -# Mapping of node class name to the display name in the UI +# Mapping of node class name to the display name in the UI NODE_DISPLAY_NAME_MAPPINGS = { "EmAySee_RemoveWordsFromText": "EmAySee Remove Words From Text" } \ No newline at end of file diff --git a/py/EmAySee_RepaintKSampler.py b/py/EmAySee_RepaintKSampler.py index 99b6e9b..e6a621a 100755 --- a/py/EmAySee_RepaintKSampler.py +++ b/py/EmAySee_RepaintKSampler.py @@ -30,20 +30,20 @@ class EmAySee_RepaintKSampler: FUNCTION = "EmAySee_function" def EmAySee_function(self, model, positive, negative, latent_image, mask, sampler_name, scheduler, steps, cfg, denoise): - # Invert the mask because in ComfyUI, white usually means "keep" and black means "change" for inpainting + # Invert the mask because in ComfyUI, white usually means "keep" and black means "change" for inpainting inverted_mask = 1.0 - mask.unsqueeze(1) - # Apply the mask to the latent image + # Apply the mask to the latent image masked_latent = latent_image["samples"] * inverted_mask - # Create an empty latent image for the masked area (this will be filled by the sampler) + # Create an empty latent image for the masked area (this will be filled by the sampler) initial_noise = torch.randn_like(latent_image["samples"]) * denoise - # Combine the masked original and the initial noise for the masked area - # Where the mask is white (1.0), we take the noise; where it's black (0.0), we take the original latent + # Combine the masked original and the initial noise for the masked area + # Where the mask is white (1.0), we take the noise; where it's black (0.0), we take the original latent combined_latent = masked_latent + initial_noise * mask.unsqueeze(1) - # Perform the K-sampling with the combined latent as the starting point + # Perform the K-sampling with the combined latent as the starting point sampler = ComfyUISampler(model) samples = sampler.sample(noise=combined_latent, positive=positive, diff --git a/py/EmAySee_SaveImage.py b/py/EmAySee_SaveImage.py index f44d2e1..df99323 100755 --- a/py/EmAySee_SaveImage.py +++ b/py/EmAySee_SaveImage.py @@ -22,7 +22,7 @@ class EmAySee_SaveImage: "images": ("IMAGE",), "subfolder": ("STRING", {"default": ""}), "name_postfix": ("STRING", {"default": "image_"}), - "timestamp_format": (["none", "YYYYMMDDHHMMSS"], {"default": "YYYYMMDDHHMMSS"}), # Removed .f option + "timestamp_format": (["none", "YYYYMMDDHHMMSS"], {"default": "YYYYMMDDHHMMSS"}), # Removed .f option "name_prefix": ("STRING", {"default": ""}), "separator": ("STRING", {"default": "_"}), }, @@ -48,31 +48,31 @@ class EmAySee_SaveImage: i = 255. * image.cpu().numpy() img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) - # --- Filename Construction --- + # --- Filename Construction --- filename_parts = [] - # Add name prefix (if provided) + # Add name prefix (if provided) if name_prefix: filename_parts.append(name_prefix) - # Add timestamp (if selected) + # Add timestamp (if selected) if timestamp_format != "none": if timestamp_format == "YYYYMMDDHHMMSS": - timestamp = time.strftime("%Y%m%d%H%M%S") # Keep only YYYYMMDDHHMMSS - # No else needed, as the only other option is "none" + timestamp = time.strftime("%Y%m%d%H%M%S") # Keep only YYYYMMDDHHMMSS + # No else needed, as the only other option is "none" filename_parts.append(timestamp) - # Add the base filename postfix + # Add the base filename postfix filename_parts.append(name_postfix) - # Join the parts with the separator + # Join the parts with the separator file_name = separator.join(filename_parts) - file = f"{file_name}" # comfyui counter + file = f"{file_name}" # comfyui counter file_path = os.path.join(full_output_folder, f"{file}.png") filepaths.append(file_path) - # --- Metadata Handling --- + # --- Metadata Handling --- if extra_pnginfo is not None: pnginfo = PngImagePlugin.PngInfo() for k, v in extra_pnginfo.items(): diff --git a/py/EmAySee_SaveTextToFile.py b/py/EmAySee_SaveTextToFile.py index 3ff53bf..72f9d5f 100755 --- a/py/EmAySee_SaveTextToFile.py +++ b/py/EmAySee_SaveTextToFile.py @@ -1,5 +1,5 @@ import os -import folder_paths # ComfyUI utility to get paths +import folder_paths # ComfyUI utility to get paths class EmAySee_SaveTextToFile: """ @@ -9,48 +9,48 @@ class EmAySee_SaveTextToFile: def INPUT_TYPES(s): return { "required": { - "text_to_save": ("STRING", {"multiline": True}), # The text content to save - "filename": ("STRING", {"default": "output_text"}), # The desired filename (without extension) - "subfolder": ("STRING", {"default": ""}), # Optional subfolder within the output directory + "text_to_save": ("STRING", {"multiline": True}), # The text content to save + "filename": ("STRING", {"default": "output_text"}), # The desired filename (without extension) + "subfolder": ("STRING", {"default": ""}), # Optional subfolder within the output directory } } - # Define RETURN_TYPES as an empty tuple since this node has no direct outputs - # This is required by ComfyUI even for nodes without outputs. + # Define RETURN_TYPES as an empty tuple since this node has no direct outputs + # This is required by ComfyUI even for nodes without outputs. RETURN_TYPES = () - # RETURN_NAMES = () # RETURN_NAMES is not strictly necessary if RETURN_TYPES is empty + # RETURN_NAMES = () # RETURN_NAMES is not strictly necessary if RETURN_TYPES is empty - CATEGORY = "EmAySee_Utils" # Category in the ComfyUI menu - TITLE = "EmAySee Save Text to File" # Title displayed on the node + CATEGORY = "EmAySee_Utils" # Category in the ComfyUI menu + TITLE = "EmAySee Save Text to File" # Title displayed on the node - FUNCTION = "EmAySee_save_text" # The method that will be executed + FUNCTION = "EmAySee_save_text" # The method that will be executed def EmAySee_save_text(self, text_to_save, filename, subfolder): """ Saves the text content to a file in the specified subfolder. """ - # Get the base output directory for ComfyUI - output_dir = folder_paths.get_output_paths(None)[0] # Get the first output path + # Get the base output directory for ComfyUI + output_dir = folder_paths.get_output_paths(None)[0] # Get the first output path - # Construct the full directory path + # Construct the full directory path if subfolder: save_dir = os.path.join(output_dir, subfolder) else: save_dir = output_dir - # Ensure the directory exists, create if necessary + # Ensure the directory exists, create if necessary os.makedirs(save_dir, exist_ok=True) - # Construct the full file path with .txt extension - # Sanitize filename to remove potentially problematic characters + # Construct the full file path with .txt extension + # Sanitize filename to remove potentially problematic characters safe_filename = "".join(c for c in filename if c.isalnum() or c in (' ', '_', '-')).rstrip() if not safe_filename: - safe_filename = "output_text" # Fallback if filename becomes empty after sanitization + safe_filename = "output_text" # Fallback if filename becomes empty after sanitization file_path = os.path.join(save_dir, f"{safe_filename}.txt") try: - # Save the text content to the file + # Save the text content to the file with open(file_path, "w", encoding="utf-8") as f: f.write(text_to_save) @@ -61,15 +61,15 @@ class EmAySee_SaveTextToFile: except Exception as e: print(f"An unexpected error occurred while saving text: {e}") - # This node doesn't return any specific output values, so return an empty tuple. + # This node doesn't return any specific output values, so return an empty tuple. return () -# Mapping of node class name to the class +# Mapping of node class name to the class NODE_CLASS_MAPPINGS = { "EmAySee_SaveTextToFile": EmAySee_SaveTextToFile } -# Mapping of node class name to the display name in the UI +# Mapping of node class name to the display name in the UI NODE_DISPLAY_NAME_MAPPINGS = { "EmAySee_SaveTextToFile": "EmAySee Save Text to File" } diff --git a/py/EmAySee_SelectiveModelUnloader.py b/py/EmAySee_SelectiveModelUnloader.py index f83a9ea..0435aeb 100755 --- a/py/EmAySee_SelectiveModelUnloader.py +++ b/py/EmAySee_SelectiveModelUnloader.py @@ -101,17 +101,17 @@ class EmAySee_SelectiveModelUnloader: gc.collect() comfy.model_management.soft_empty_cache() - # Replicating the "Free model and node cache" button backend logic: + # Replicating the "Free model and node cache" button backend logic: if not exclude_checkpoints: comfy.model_management.unload_all_models() if hasattr(comfy.model_management, "loaded_models"): comfy.model_management.loaded_models.clear() - # Clear the execution cache (Node Cache) + # Clear the execution cache (Node Cache) if hasattr(comfy.model_management, "node_cache"): comfy.model_management.node_cache.clear() - # Brute force memory cleanup + # Brute force memory cleanup try: comfy.model_management.cleanup_models(keep_free_mem=0) except: diff --git a/py/EmAySee_StringPoseSelectorNode.py b/py/EmAySee_StringPoseSelectorNode.py index b32decf..16f5acf 100755 --- a/py/EmAySee_StringPoseSelectorNode.py +++ b/py/EmAySee_StringPoseSelectorNode.py @@ -16,7 +16,7 @@ class EmAySee_StringPoseSelectorNode: "min": 1, "max": 5, "step": 1, - "display": "slider", # Or "number" + "display": "slider", # Or "number" "label": "Select Input (1-5)" }), }, @@ -40,13 +40,13 @@ class EmAySee_StringPoseSelectorNode: elif select_index == 5: return (string_input_5,) else: - return ("",) # Should not happen given input constraints, but as a failsafe + return ("",) # Should not happen given input constraints, but as a failsafe -NODE_CLASS_MAPPINGS = { # Moved to after the class definition +NODE_CLASS_MAPPINGS = { # Moved to after the class definition "EmAySee_StringPoseSelectorNode": EmAySee_StringPoseSelectorNode } -NODE_DISPLAY_NAME_MAPPINGS = { # Moved to after the class definition +NODE_DISPLAY_NAME_MAPPINGS = { # Moved to after the class definition "EmAySee_StringPoseSelectorNode": "EmAySee Pose Option Selector" } \ No newline at end of file diff --git a/py/EmAySee_StringTupleInputNode.py b/py/EmAySee_StringTupleInputNode.py index f18877d..84168ef 100755 --- a/py/EmAySee_StringTupleInputNode.py +++ b/py/EmAySee_StringTupleInputNode.py @@ -17,8 +17,8 @@ class EmAySee_StringTupleInputNode: FUNCTION = "create_string_tuple" def create_string_tuple(self, string_list): - lines = string_list.strip().split('\n') # Split multiline string into lines - string_tuple = tuple(line.strip() for line in lines) # Create a tuple of strings, stripping whitespace from each line + lines = string_list.strip().split('\n') # Split multiline string into lines + string_tuple = tuple(line.strip() for line in lines) # Create a tuple of strings, stripping whitespace from each line return (string_tuple,) NODE_CLASS_MAPPINGS = { diff --git a/py/EmAySee_SubmitToOobaboogaAPI-withKey-ThinkingParser.py b/py/EmAySee_SubmitToOobaboogaAPI-withKey-ThinkingParser.py index 1523db8..5497c97 100755 --- a/py/EmAySee_SubmitToOobaboogaAPI-withKey-ThinkingParser.py +++ b/py/EmAySee_SubmitToOobaboogaAPI-withKey-ThinkingParser.py @@ -35,7 +35,7 @@ class EmAySee_SubmitToOobaboogaAPIWithKeyThinker: "Authorization": f"Bearer {api_key}" } - # Construct the payload for OpenAI-compatible API + # Construct the payload for OpenAI-compatible API data = { "messages": [ {"role": "system", "content": system_prompt}, @@ -45,7 +45,7 @@ class EmAySee_SubmitToOobaboogaAPIWithKeyThinker: "temperature": temperature, "top_p": top_p, "seed": seed, - "mode": "instruct", # Helpful for some Ooba configs + "mode": "instruct", # Helpful for some Ooba configs "stream": False } @@ -55,20 +55,20 @@ class EmAySee_SubmitToOobaboogaAPIWithKeyThinker: result_json = response.json() - # Extract content from OpenAI format + # Extract content from OpenAI format if "choices" in result_json and len(result_json["choices"]) > 0: raw_content = result_json["choices"][0]["message"]["content"] else: raw_content = "" print(f"EmAySee_SubmitToOobaboogaAPIWithKeyThinker: No content in response: {result_json}") - # --- Logic to separate content from clean text --- + # --- Logic to separate content from clean text --- - # 1. Extract thinking content + # 1. Extract thinking content think_match = re.search(r'(.*?)', raw_content, flags=re.DOTALL) thinking_content = think_match.group(1).strip() if think_match else "" - # 2. Remove thinking tags to get clean text + # 2. Remove thinking tags to get clean text clean_text = re.sub(r'.*?', '', raw_content, flags=re.DOTALL).strip() return (clean_text, thinking_content) diff --git a/py/EmAySee_SubmitToOobaboogaAPI-withKey-test.py b/py/EmAySee_SubmitToOobaboogaAPI-withKey-test.py index 0f05cf0..375980a 100755 --- a/py/EmAySee_SubmitToOobaboogaAPI-withKey-test.py +++ b/py/EmAySee_SubmitToOobaboogaAPI-withKey-test.py @@ -2,7 +2,7 @@ import requests import json import time -class EmAySee_SubmitToOobaboogaAPIWithKey_test: # Class name now prefixed with EmAySee_ +class EmAySee_SubmitToOobaboogaAPIWithKey_test: # Class name now prefixed with EmAySee_ def __init__(self): pass @@ -12,23 +12,23 @@ class EmAySee_SubmitToOobaboogaAPIWithKey_test: # Class name now prefixed with "required": { "apikey_input": ("STRING", {"default": "Enter your api key here"}), "text_input": ("STRING", {"default": "Enter your prompt for text-generation-webui here"}), - "api_endpoint": ("STRING", ["http://10.0.0.71:5000/v1/completions","http://10.0.0.76:5000/v1/completions"],{"default": "http://10.0.0.71:5000/v1/completions"}), # Default oobabooga COMPletions API endpoint - Non-streaming - "temperature": ("FLOAT", {"default": 0.7, "min": 0.01, "max": 2.0, "step": 0.01}), # Common oobabooga default - "top_p": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default - "top_k": ("INT", {"default": 0, "min": 0, "max": 200}), # 0 means disabled in oobabooga - "min_p": ("INT", {"default": 0, "min": 0, "max": 200}), # 0 means disabled in oobabooga - "typical_p": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default - "repetition_penalty": ("FLOAT", {"default": 1.1, "min": 0.0, "max": 2.0, "step": 0.01}), # Common oobabooga default + "api_endpoint": ("STRING", ["http://10.0.0.71:5000/v1/completions","http://10.0.0.76:5000/v1/completions"],{"default": "http://10.0.0.71:5000/v1/completions"}), # Default oobabooga COMPletions API endpoint - Non-streaming + "temperature": ("FLOAT", {"default": 0.7, "min": 0.01, "max": 2.0, "step": 0.01}), # Common oobabooga default + "top_p": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default + "top_k": ("INT", {"default": 0, "min": 0, "max": 200}), # 0 means disabled in oobabooga + "min_p": ("INT", {"default": 0, "min": 0, "max": 200}), # 0 means disabled in oobabooga + "typical_p": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default + "repetition_penalty": ("FLOAT", {"default": 1.1, "min": 0.0, "max": 2.0, "step": 0.01}), # Common oobabooga default "max_new_tokens": ( - ["25", "50", "75", "100", "125", "150", "175", "200", "250", "300", "350", "400", "450", "500", "550", "600", "650", "700", "750", "800"], # Dropdown options - {"default": "200"} # Default, adjust as needed + ["25", "50", "75", "100", "125", "150", "175", "200", "250", "300", "350", "400", "450", "500", "550", "600", "650", "700", "750", "800"], # Dropdown options + {"default": "200"} # Default, adjust as needed ), - "seed": ("INT", {"default": -1, "label": "Seed (-1 for random)"}), # Added Seed - common in completions API + "seed": ("INT", {"default": -1, "label": "Seed (-1 for random)"}), # Added Seed - common in completions API "delay_seconds": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.1, "label": "Delay (seconds)"}), "show_api_response": ("BOOLEAN", {"default": False, "label": "Show API Response in Console"}), }, "optional": { - # You can add 'stopping_strings' or other completion-specific parameters here later if needed + # You can add 'stopping_strings' or other completion-specific parameters here later if needed } } @@ -37,28 +37,28 @@ class EmAySee_SubmitToOobaboogaAPIWithKey_test: # Class name now prefixed with OUTPUT_NODE = True FUNCTION = "submitToOobabooga" - def submitToOobabooga(self, apikey_input, api_endpoint, text_input, temperature, top_p, top_k, typical_p, repetition_penalty, max_new_tokens, seed, delay_seconds, show_api_response, **kwargs): # Removed history_, added seed + def submitToOobabooga(self, apikey_input, api_endpoint, text_input, temperature, top_p, top_k, typical_p, repetition_penalty, max_new_tokens, seed, delay_seconds, show_api_response, **kwargs): # Removed history_, added seed """ Submits a request to the text-generation-webui (oobabooga) /v1/completions API for non-streaming text generation. Waits for the full response before returning. """ payload = { "prompt": text_input, - "max_tokens": int(max_new_tokens), # Use 'max_tokens' for /v1/completions + "max_tokens": int(max_new_tokens), # Use 'max_tokens' for /v1/completions "temperature": temperature, "top_p": top_p, "top_k": top_k, "min_p": top_k, "typical_p": typical_p, "repetition_penalty": repetition_penalty, - "seed": seed, # Added seed to payload - "stream": False # **Ensuring non-streaming: explicitly set stream to False** - # Add other oobabooga /v1/completions parameters here as needed, consult API docs + "seed": seed, # Added seed to payload + "stream": False # **Ensuring non-streaming: explicitly set stream to False** + # Add other oobabooga /v1/completions parameters here as needed, consult API docs } headers = { 'Content-Type': 'application/json', - 'Authorization': f'Bearer {apikey_input}' # <-- ADD THIS LINE + 'Authorization': f'Bearer {apikey_input}' # <-- ADD THIS LINE } try: time.sleep(delay_seconds) @@ -76,12 +76,12 @@ class EmAySee_SubmitToOobaboogaAPIWithKey_test: # Class name now prefixed with print("Oobabooga API Response Content:") print(api_response) - # Response parsing for /v1/completions endpoint - VERIFY AGAINST DOCS! + # Response parsing for /v1/completions endpoint - VERIFY AGAINST DOCS! if isinstance(api_response, dict) and 'choices' in api_response and api_response['choices']: - api_response_text = api_response['choices'][0]['text'] # Expecting text directly under 'choices'-> 0 -> 'text' for /v1/completions - elif isinstance(api_response, dict) and 'text' in api_response: # Fallback if API just returns text directly + api_response_text = api_response['choices'][0]['text'] # Expecting text directly under 'choices'-> 0 -> 'text' for /v1/completions + elif isinstance(api_response, dict) and 'text' in api_response: # Fallback if API just returns text directly api_response_text = api_response['text'] - elif isinstance(api_response, str): # Fallback if API returns plain text string + elif isinstance(api_response, str): # Fallback if API returns plain text string api_response_text = api_response else: error_message = f"Unexpected Oobabooga API response format for /v1/completions. JSON: {api_response}" @@ -98,9 +98,9 @@ class EmAySee_SubmitToOobaboogaAPIWithKey_test: # Class name now prefixed with NODE_CLASS_MAPPINGS = { - "EmAySee_SubmitToOobaboogaAPIWithKey_test": EmAySee_SubmitToOobaboogaAPIWithKey_test # Class mapping with alias + "EmAySee_SubmitToOobaboogaAPIWithKey_test": EmAySee_SubmitToOobaboogaAPIWithKey_test # Class mapping with alias } NODE_DISPLAY_NAME_MAPPINGS = { - "EmAySee_SubmitToOobaboogaAPIWithKey_test": "EmAySee Submit to Oobabooga API Node With API Key_test" # Display name with alias + "EmAySee_SubmitToOobaboogaAPIWithKey_test": "EmAySee Submit to Oobabooga API Node With API Key_test" # Display name with alias } \ No newline at end of file diff --git a/py/EmAySee_SubmitToOobaboogaAPI-withKey.py b/py/EmAySee_SubmitToOobaboogaAPI-withKey.py index 55f9946..453c56e 100755 --- a/py/EmAySee_SubmitToOobaboogaAPI-withKey.py +++ b/py/EmAySee_SubmitToOobaboogaAPI-withKey.py @@ -2,7 +2,7 @@ import requests import json import time -class EmAySee_SubmitToOobaboogaAPIWithKey: # Class name now prefixed with EmAySee_ +class EmAySee_SubmitToOobaboogaAPIWithKey: # Class name now prefixed with EmAySee_ def __init__(self): pass @@ -12,22 +12,22 @@ class EmAySee_SubmitToOobaboogaAPIWithKey: # Class name now prefixed with EmAy "required": { "apikey_input": ("STRING", {"default": "Enter your api key here"}), "text_input": ("STRING", {"default": "Enter your prompt for text-generation-webui here"}), - "api_endpoint": ("STRING", {"default": "http://10.0.0.57:5000/v1/completions"}), # Default oobabooga COMPletions API endpoint - Non-streaming - "temperature": ("FLOAT", {"default": 0.7, "min": 0.01, "max": 2.0, "step": 0.01}), # Common oobabooga default - "top_p": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default - "top_k": ("INT", {"default": 0, "min": 0, "max": 200}), # 0 means disabled in oobabooga - "typical_p": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default - "repetition_penalty": ("FLOAT", {"default": 1.1, "min": 0.0, "max": 2.0, "step": 0.01}), # Common oobabooga default + "api_endpoint": ("STRING", {"default": "http://10.0.0.57:5000/v1/completions"}), # Default oobabooga COMPletions API endpoint - Non-streaming + "temperature": ("FLOAT", {"default": 0.7, "min": 0.01, "max": 2.0, "step": 0.01}), # Common oobabooga default + "top_p": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default + "top_k": ("INT", {"default": 0, "min": 0, "max": 200}), # 0 means disabled in oobabooga + "typical_p": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default + "repetition_penalty": ("FLOAT", {"default": 1.1, "min": 0.0, "max": 2.0, "step": 0.01}), # Common oobabooga default "max_new_tokens": ( - ["25", "50", "75", "100", "125", "150", "175", "200", "250", "300", "350", "400", "450", "500", "550", "600", "650", "700", "750", "800", "1024", "2048", "4096"], # Dropdown options - {"default": "300"} # Default, adjust as needed + ["25", "50", "75", "100", "125", "150", "175", "200", "250", "300", "350", "400", "450", "500", "550", "600", "650", "700", "750", "800", "1024", "2048", "4096"], # Dropdown options + {"default": "300"} # Default, adjust as needed ), - "seed": ("INT", {"default": -1, "label": "Seed (-1 for random)"}), # Added Seed - common in completions API + "seed": ("INT", {"default": -1, "label": "Seed (-1 for random)"}), # Added Seed - common in completions API "delay_seconds": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.1, "label": "Delay (seconds)"}), "show_api_response": ("BOOLEAN", {"default": False, "label": "Show API Response in Console"}), }, "optional": { - # You can add 'stopping_strings' or other completion-specific parameters here later if needed + # You can add 'stopping_strings' or other completion-specific parameters here later if needed } } @@ -36,27 +36,27 @@ class EmAySee_SubmitToOobaboogaAPIWithKey: # Class name now prefixed with EmAy OUTPUT_NODE = True FUNCTION = "submitToOobabooga" - def submitToOobabooga(self, apikey_input, api_endpoint, text_input, temperature, top_p, top_k, typical_p, repetition_penalty, max_new_tokens, seed, delay_seconds, show_api_response, **kwargs): # Removed history_, added seed + def submitToOobabooga(self, apikey_input, api_endpoint, text_input, temperature, top_p, top_k, typical_p, repetition_penalty, max_new_tokens, seed, delay_seconds, show_api_response, **kwargs): # Removed history_, added seed """ Submits a request to the text-generation-webui (oobabooga) /v1/completions API for non-streaming text generation. Waits for the full response before returning. """ payload = { "prompt": text_input, - "max_tokens": int(max_new_tokens), # Use 'max_tokens' for /v1/completions + "max_tokens": int(max_new_tokens), # Use 'max_tokens' for /v1/completions "temperature": temperature, "top_p": top_p, "top_k": top_k, "typical_p": typical_p, "repetition_penalty": repetition_penalty, - "seed": seed, # Added seed to payload - "stream": False # **Ensuring non-streaming: explicitly set stream to False** - # Add other oobabooga /v1/completions parameters here as needed, consult API docs + "seed": seed, # Added seed to payload + "stream": False # **Ensuring non-streaming: explicitly set stream to False** + # Add other oobabooga /v1/completions parameters here as needed, consult API docs } headers = { 'Content-Type': 'application/json', - 'Authorization': f'Bearer {apikey_input}' # <-- ADD THIS LINE + 'Authorization': f'Bearer {apikey_input}' # <-- ADD THIS LINE } try: time.sleep(delay_seconds) @@ -74,12 +74,12 @@ class EmAySee_SubmitToOobaboogaAPIWithKey: # Class name now prefixed with EmAy print("Oobabooga API Response Content:") print(api_response) - # Response parsing for /v1/completions endpoint - VERIFY AGAINST DOCS! + # Response parsing for /v1/completions endpoint - VERIFY AGAINST DOCS! if isinstance(api_response, dict) and 'choices' in api_response and api_response['choices']: - api_response_text = api_response['choices'][0]['text'] # Expecting text directly under 'choices'-> 0 -> 'text' for /v1/completions - elif isinstance(api_response, dict) and 'text' in api_response: # Fallback if API just returns text directly + api_response_text = api_response['choices'][0]['text'] # Expecting text directly under 'choices'-> 0 -> 'text' for /v1/completions + elif isinstance(api_response, dict) and 'text' in api_response: # Fallback if API just returns text directly api_response_text = api_response['text'] - elif isinstance(api_response, str): # Fallback if API returns plain text string + elif isinstance(api_response, str): # Fallback if API returns plain text string api_response_text = api_response else: error_message = f"Unexpected Oobabooga API response format for /v1/completions. JSON: {api_response}" @@ -96,9 +96,9 @@ class EmAySee_SubmitToOobaboogaAPIWithKey: # Class name now prefixed with EmAy NODE_CLASS_MAPPINGS = { - "EmAySee_SubmitToOobaboogaAPIWithKey": EmAySee_SubmitToOobaboogaAPIWithKey # Class mapping with alias + "EmAySee_SubmitToOobaboogaAPIWithKey": EmAySee_SubmitToOobaboogaAPIWithKey # Class mapping with alias } NODE_DISPLAY_NAME_MAPPINGS = { - "EmAySee_SubmitToOobaboogaAPIWithKey": "EmAySee Submit to Oobabooga API Node With API Key" # Display name with alias + "EmAySee_SubmitToOobaboogaAPIWithKey": "EmAySee Submit to Oobabooga API Node With API Key" # Display name with alias } \ No newline at end of file diff --git a/py/EmAySee_SubmitToOobaboogaAPI.py b/py/EmAySee_SubmitToOobaboogaAPI.py index 2e0e75a..48e747f 100755 --- a/py/EmAySee_SubmitToOobaboogaAPI.py +++ b/py/EmAySee_SubmitToOobaboogaAPI.py @@ -2,7 +2,7 @@ import requests import json import time -class EmAySee_SubmitToOobaboogaAPI: # Class name now prefixed with EmAySee_ +class EmAySee_SubmitToOobaboogaAPI: # Class name now prefixed with EmAySee_ def __init__(self): pass @@ -11,22 +11,22 @@ class EmAySee_SubmitToOobaboogaAPI: # Class name now prefixed with EmAySee_ return { "required": { "text_input": ("STRING", {"default": "Enter your prompt for text-generation-webui here"}), - "api_endpoint": ("STRING", {"default": "http://127.0.0.1:5000/v1/completions"}), # Default oobabooga COMPletions API endpoint - Non-streaming - "temperature": ("FLOAT", {"default": 0.7, "min": 0.01, "max": 2.0, "step": 0.01}), # Common oobabooga default - "top_p": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default - "top_k": ("INT", {"default": 0, "min": 0, "max": 200}), # 0 means disabled in oobabooga - "typical_p": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default - "repetition_penalty": ("FLOAT", {"default": 1.1, "min": 0.0, "max": 2.0, "step": 0.01}), # Common oobabooga default + "api_endpoint": ("STRING", {"default": "http://127.0.0.1:5000/v1/completions"}), # Default oobabooga COMPletions API endpoint - Non-streaming + "temperature": ("FLOAT", {"default": 0.7, "min": 0.01, "max": 2.0, "step": 0.01}), # Common oobabooga default + "top_p": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default + "top_k": ("INT", {"default": 0, "min": 0, "max": 200}), # 0 means disabled in oobabooga + "typical_p": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default + "repetition_penalty": ("FLOAT", {"default": 1.1, "min": 0.0, "max": 2.0, "step": 0.01}), # Common oobabooga default "max_new_tokens": ( - ["50", "100", "150", "200", "250", "300", "350", "400", "450", "500", "550", "600", "650", "700", "750", "800"], # Dropdown options - {"default": "200"} # Default, adjust as needed + ["50", "100", "150", "200", "250", "300", "350", "400", "450", "500", "550", "600", "650", "700", "750", "800"], # Dropdown options + {"default": "200"} # Default, adjust as needed ), - "seed": ("INT", {"default": -1, "label": "Seed (-1 for random)"}), # Added Seed - common in completions API + "seed": ("INT", {"default": -1, "label": "Seed (-1 for random)"}), # Added Seed - common in completions API "delay_seconds": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.1, "label": "Delay (seconds)"}), "show_api_response": ("BOOLEAN", {"default": False, "label": "Show API Response in Console"}), }, "optional": { - # You can add 'stopping_strings' or other completion-specific parameters here later if needed + # You can add 'stopping_strings' or other completion-specific parameters here later if needed } } @@ -35,22 +35,22 @@ class EmAySee_SubmitToOobaboogaAPI: # Class name now prefixed with EmAySee_ OUTPUT_NODE = True FUNCTION = "submitToOobabooga" - def submitToOobabooga(self, api_endpoint, text_input, temperature, top_p, top_k, typical_p, repetition_penalty, max_new_tokens, seed, delay_seconds, show_api_response, **kwargs): # Removed history_, added seed + def submitToOobabooga(self, api_endpoint, text_input, temperature, top_p, top_k, typical_p, repetition_penalty, max_new_tokens, seed, delay_seconds, show_api_response, **kwargs): # Removed history_, added seed """ Submits a request to the text-generation-webui (oobabooga) /v1/completions API for non-streaming text generation. Waits for the full response before returning. """ payload = { "prompt": text_input, - "max_tokens": int(max_new_tokens), # Use 'max_tokens' for /v1/completions + "max_tokens": int(max_new_tokens), # Use 'max_tokens' for /v1/completions "temperature": temperature, "top_p": top_p, "top_k": top_k, "typical_p": typical_p, "repetition_penalty": repetition_penalty, - "seed": seed, # Added seed to payload - "stream": False # **Ensuring non-streaming: explicitly set stream to False** - # Add other oobabooga /v1/completions parameters here as needed, consult API docs + "seed": seed, # Added seed to payload + "stream": False # **Ensuring non-streaming: explicitly set stream to False** + # Add other oobabooga /v1/completions parameters here as needed, consult API docs } headers = {'Content-Type': 'application/json'} @@ -71,12 +71,12 @@ class EmAySee_SubmitToOobaboogaAPI: # Class name now prefixed with EmAySee_ print("Oobabooga API Response Content:") print(api_response) - # Response parsing for /v1/completions endpoint - VERIFY AGAINST DOCS! + # Response parsing for /v1/completions endpoint - VERIFY AGAINST DOCS! if isinstance(api_response, dict) and 'choices' in api_response and api_response['choices']: - api_response_text = api_response['choices'][0]['text'] # Expecting text directly under 'choices'-> 0 -> 'text' for /v1/completions - elif isinstance(api_response, dict) and 'text' in api_response: # Fallback if API just returns text directly + api_response_text = api_response['choices'][0]['text'] # Expecting text directly under 'choices'-> 0 -> 'text' for /v1/completions + elif isinstance(api_response, dict) and 'text' in api_response: # Fallback if API just returns text directly api_response_text = api_response['text'] - elif isinstance(api_response, str): # Fallback if API returns plain text string + elif isinstance(api_response, str): # Fallback if API returns plain text string api_response_text = api_response else: error_message = f"Unexpected Oobabooga API response format for /v1/completions. JSON: {api_response}" @@ -93,9 +93,9 @@ class EmAySee_SubmitToOobaboogaAPI: # Class name now prefixed with EmAySee_ NODE_CLASS_MAPPINGS = { - "EmAySee_SubmitToOobaboogaAPI": EmAySee_SubmitToOobaboogaAPI # Class mapping with alias + "EmAySee_SubmitToOobaboogaAPI": EmAySee_SubmitToOobaboogaAPI # Class mapping with alias } NODE_DISPLAY_NAME_MAPPINGS = { - "EmAySee_SubmitToOobaboogaAPI": "EmAySee Submit to Oobabooga API Node" # Display name with alias + "EmAySee_SubmitToOobaboogaAPI": "EmAySee Submit to Oobabooga API Node" # Display name with alias } \ No newline at end of file diff --git a/py/EmAySee_SubmitToOobaboogaAPIWithKey_WithThinkParse.py b/py/EmAySee_SubmitToOobaboogaAPIWithKey_WithThinkParse.py index 3dd31c0..2f5d14a 100755 --- a/py/EmAySee_SubmitToOobaboogaAPIWithKey_WithThinkParse.py +++ b/py/EmAySee_SubmitToOobaboogaAPIWithKey_WithThinkParse.py @@ -26,7 +26,7 @@ class EmAySee_SubmitToOobaboogaAPIWithKey_WithThinkParse: "dry_base": ("FLOAT", {"default": 1.75, "min": 1.0, "max": 5.0, "step": 0.05}), "dry_allowed_length": ("INT", {"default": 2, "min": 0, "max": 20}), "enable_thinking": ("BOOLEAN", {"default": True}), - # New Parameter: reasoning_effort + # New Parameter: reasoning_effort "reasoning_effort": (["low", "medium", "high"], {"default": "medium"}), "guidance_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 5.0, "step": 0.1}), "seed": ("INT", {"default": -1, "min": -1, "max": 0xffffffffffffffff}), @@ -71,7 +71,7 @@ class EmAySee_SubmitToOobaboogaAPIWithKey_WithThinkParse: "dry_base": dry_base, "dry_allowed_length": dry_allowed_length, "enable_thinking": enable_thinking, - "reasoning_effort": reasoning_effort, # Added here + "reasoning_effort": reasoning_effort, # Added here "guidance_scale": guidance_scale, "negative_prompt": negative_prompt, "instruction_template": instruction_template, @@ -95,11 +95,11 @@ class EmAySee_SubmitToOobaboogaAPIWithKey_WithThinkParse: raw_content = "" print(f"EmAySee_SubmitToOobaboogaAPIWithKey: No content in response: {result_json}") - # 1. Extract thinking content + # 1. Extract thinking content think_match = re.search(r'(.*?)', raw_content, flags=re.DOTALL) thinking_content = think_match.group(1).strip() if think_match else "" - # 2. Remove thinking tags to get clean text + # 2. Remove thinking tags to get clean text clean_text = re.sub(r'.*?', '', raw_content, flags=re.DOTALL).strip() return (clean_text, thinking_content) diff --git a/py/EmAySee_ToggleIntNode.py b/py/EmAySee_ToggleIntNode.py index a54ffa9..7dd3765 100755 --- a/py/EmAySee_ToggleIntNode.py +++ b/py/EmAySee_ToggleIntNode.py @@ -19,9 +19,9 @@ class EmAySee_ToggleIntNode: FUNCTION = "toggle_to_integer" def toggle_to_integer(self, toggle_input, int_if_on, int_if_off): - if toggle_input: # If toggle_input is True (toggle is ON) + if toggle_input: # If toggle_input is True (toggle is ON) return (int_if_on,) - else: # If toggle_input is False (toggle is OFF) + else: # If toggle_input is False (toggle is OFF) return (int_if_off,) NODE_CLASS_MAPPINGS = { diff --git a/py/EmAySee_VarTextReplacer.py b/py/EmAySee_VarTextReplacer.py index 97c67dd..9cf3397 100755 --- a/py/EmAySee_VarTextReplacer.py +++ b/py/EmAySee_VarTextReplacer.py @@ -8,26 +8,26 @@ class EmAySee_VarTextReplacer: def INPUT_TYPES(s): return { "required": { - "main_text": ("STRING", {"default": "This is %var1%, that is %var2%, and here is %var3%. We also have %var4%, %var5%, %var6%, %var7%, %var8%, %var9%, and finally %var10%.", "multiline": True}), # Main text with placeholders - "var1": ("STRING", {"default": "Variable 1"}), # Input variable for %var1% - "var2": ("STRING", {"default": "Variable 2"}), # Input variable for %var2% - "var3": ("STRING", {"default": "Variable 3"}), # Input variable for %var3% - "var4": ("STRING", {"default": "Variable 4"}), # Input variable for %var4% - "var5": ("STRING", {"default": "Variable 5"}), # Input variable for %var5% - "var6": ("STRING", {"default": "Variable 6"}), # Input variable for %var6% - "var7": ("STRING", {"default": "Variable 7"}), # Input variable for %var7% - "var8": ("STRING", {"default": "Variable 8"}), # Input variable for %var8% - "var9": ("STRING", {"default": "Variable 9"}), # Input variable for %var9% - "var10": ("STRING", {"default": "Variable 10"}), # Input variable for %var10% + "main_text": ("STRING", {"default": "This is %var1%, that is %var2%, and here is %var3%. We also have %var4%, %var5%, %var6%, %var7%, %var8%, %var9%, and finally %var10%.", "multiline": True}), # Main text with placeholders + "var1": ("STRING", {"default": "Variable 1"}), # Input variable for %var1% + "var2": ("STRING", {"default": "Variable 2"}), # Input variable for %var2% + "var3": ("STRING", {"default": "Variable 3"}), # Input variable for %var3% + "var4": ("STRING", {"default": "Variable 4"}), # Input variable for %var4% + "var5": ("STRING", {"default": "Variable 5"}), # Input variable for %var5% + "var6": ("STRING", {"default": "Variable 6"}), # Input variable for %var6% + "var7": ("STRING", {"default": "Variable 7"}), # Input variable for %var7% + "var8": ("STRING", {"default": "Variable 8"}), # Input variable for %var8% + "var9": ("STRING", {"default": "Variable 9"}), # Input variable for %var9% + "var10": ("STRING", {"default": "Variable 10"}), # Input variable for %var10% } } - RETURN_TYPES = ("STRING",) # Output is a single string - RETURN_NAMES = ("replaced_text",) # Name for the output - CATEGORY = "EmAySee_Text" # Category in the ComfyUI menu - TITLE = "EmAySee Var Text Replacer" # Title displayed on the node + RETURN_TYPES = ("STRING",) # Output is a single string + RETURN_NAMES = ("replaced_text",) # Name for the output + CATEGORY = "EmAySee_Text" # Category in the ComfyUI menu + TITLE = "EmAySee Var Text Replacer" # Title displayed on the node - FUNCTION = "EmAySee_replace_placeholders" # The method that will be executed + FUNCTION = "EmAySee_replace_placeholders" # The method that will be executed def EmAySee_replace_placeholders(self, main_text, var1, var2, var3, var4, var5, var6, var7, var8, var9, var10): """ @@ -35,7 +35,7 @@ class EmAySee_VarTextReplacer: """ output_text = main_text - # Replace the specific variables we defined in INPUT_TYPES + # Replace the specific variables we defined in INPUT_TYPES output_text = output_text.replace("%var1%", str(var1)) output_text = output_text.replace("%var2%", str(var2)) output_text = output_text.replace("%var3%", str(var3)) @@ -47,17 +47,17 @@ class EmAySee_VarTextReplacer: output_text = output_text.replace("%var9%", str(var9)) output_text = output_text.replace("%var10%", str(var10)) - # Note: Using str() just in case a non-string input is ever connected, - # though INPUT_TYPES specifies STRING. + # Note: Using str() just in case a non-string input is ever connected, + # though INPUT_TYPES specifies STRING. - return (output_text,) # Return the result as a tuple + return (output_text,) # Return the result as a tuple -# Mapping of node class name to the class +# Mapping of node class name to the class NODE_CLASS_MAPPINGS = { "EmAySee_VarTextReplacer": EmAySee_VarTextReplacer } -# Mapping of node class name to the display name in the UI +# Mapping of node class name to the display name in the UI NODE_DISPLAY_NAME_MAPPINGS = { "EmAySee_VarTextReplacer": "EmAySee Var Text Replacer" } diff --git a/py/EmAySee_VeryUniqueStringSelectorNode.py b/py/EmAySee_VeryUniqueStringSelectorNode.py index e613b69..97e7dfa 100755 --- a/py/EmAySee_VeryUniqueStringSelectorNode.py +++ b/py/EmAySee_VeryUniqueStringSelectorNode.py @@ -14,17 +14,17 @@ class EmAySee_StringSelectorNode: RETURN_TYPES = ("STRING",) RETURN_NAMES = ("SELECTED_STRING",) - CATEGORY = "EmAySee_Utils" # Or a category you prefer + CATEGORY = "EmAySee_Utils" # Or a category you prefer - FUNCTION = "EmAySee_function" # <-- IMPORTANT: Back to string for FUNCTION + FUNCTION = "EmAySee_function" # <-- IMPORTANT: Back to string for FUNCTION - def EmAySee_function(self, string_input_1, string_input_2, select_input): # Ensure defined AFTER FUNCTION is set + def EmAySee_function(self, string_input_1, string_input_2, select_input): # Ensure defined AFTER FUNCTION is set if select_input == "string_input_1": return (string_input_1,) elif select_input == "string_input_2": return (string_input_2,) else: - return ("",) # Should not happen, but default to empty string + return ("",) # Should not happen, but default to empty string NODE_CLASS_MAPPINGS = { "EmAySee_VeryUniqueStringSelectorNode": EmAySee_StringSelectorNode diff --git a/py/EmAySee_VisualMachine_SaveImage.py b/py/EmAySee_VisualMachine_SaveImage.py index b649a5b..ee65bcf 100755 --- a/py/EmAySee_VisualMachine_SaveImage.py +++ b/py/EmAySee_VisualMachine_SaveImage.py @@ -36,8 +36,8 @@ class EmAySee_VisualMachine_SaveImageAndText_V2: i = 255. * image.cpu().numpy() img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) - # 1. Handle Naming - # Uses the prefix as the base name; extension replacement happens here + # 1. Handle Naming + # Uses the prefix as the base name; extension replacement happens here file_base = f"{filename_prefix}" image_file = f"{file_base}.png" log_file = f"{file_base}.txt" @@ -45,10 +45,10 @@ class EmAySee_VisualMachine_SaveImageAndText_V2: image_path = os.path.join(full_output_folder, image_file) log_path = os.path.join(full_output_folder, log_file) - # 2. Save the Image + # 2. Save the Image img.save(image_path, pnginfo=None, compress_level=4) - # 3. Save the Companion Text File + # 3. Save the Companion Text File entry = ( f"--- SPECTRE V2 METADATA ---\n" f"FILENAME: {image_file}\n" diff --git a/py/EmAySee_WANVAECompatibleAspectRatioCalculator.py b/py/EmAySee_WANVAECompatibleAspectRatioCalculator.py index 343a6c5..9d1869e 100755 --- a/py/EmAySee_WANVAECompatibleAspectRatioCalculator.py +++ b/py/EmAySee_WANVAECompatibleAspectRatioCalculator.py @@ -21,18 +21,18 @@ class EmAySee_VAECompatibleAspectRatioCalculator: FUNCTION = "EmAySee_calculate_dimensions" def EmAySee_calculate_dimensions(self, image, output_height): - # We'll use the first image in the batch to determine dimensions. + # We'll use the first image in the batch to determine dimensions. batch_size, original_height, original_width, channels = image.shape - # Calculate the ideal width based on the original aspect ratio. + # Calculate the ideal width based on the original aspect ratio. ratio = original_width / original_height - # Calculate the new width based on the desired height and aspect ratio. - # This new width must be a multiple of 64 to be VAE-compatible. + # Calculate the new width based on the desired height and aspect ratio. + # This new width must be a multiple of 64 to be VAE-compatible. vae_stride = 64 calculated_width = output_height * ratio - # Round the width to the nearest multiple of the VAE stride. + # Round the width to the nearest multiple of the VAE stride. output_width = int(round(calculated_width / vae_stride) * vae_stride) return (output_width, output_height,) diff --git a/py/EmAySee_WD14TagFilter.py b/py/EmAySee_WD14TagFilter.py index f57cc22..82d7479 100755 --- a/py/EmAySee_WD14TagFilter.py +++ b/py/EmAySee_WD14TagFilter.py @@ -17,24 +17,24 @@ class EmAySee_WD14TagFilter: FUNCTION = "EmAySee_filter_tags" def EmAySee_filter_tags(self, wd14_tags_input, tags_to_remove): - # 1. Normalize the input WD14 tags string (replace newlines with commas, split) - # This assumes the WD14 output is a string of tags separated by commas, spaces, or newlines. + # 1. Normalize the input WD14 tags string (replace newlines with commas, split) + # This assumes the WD14 output is a string of tags separated by commas, spaces, or newlines. input_tags = re.split(r'[,\s\n]+', wd14_tags_input.lower().strip()) input_tags = [tag.strip() for tag in input_tags if tag.strip()] - # 2. Normalize the tags to remove string - # Split the string in the text box by commas or newlines + # 2. Normalize the tags to remove string + # Split the string in the text box by commas or newlines remove_list_raw = re.split(r'[,\n]+', tags_to_remove.lower().strip()) tags_to_remove_set = {tag.strip() for tag in remove_list_raw if tag.strip()} - # 3. Perform the filtering + # 3. Perform the filtering filtered_tags = [] for tag in input_tags: - # Only keep the tag if it is NOT in the set of tags to remove + # Only keep the tag if it is NOT in the set of tags to remove if tag not in tags_to_remove_set: filtered_tags.append(tag) - # 4. Join the remaining tags back into a single string + # 4. Join the remaining tags back into a single string filtered_tags_output = ", ".join(filtered_tags) return (filtered_tags_output,) diff --git a/py/emaysee_lora_filepath-name-selector.x b/py/emaysee_lora_filepath-name-selector.x index 9122155..407ef67 100755 --- a/py/emaysee_lora_filepath-name-selector.x +++ b/py/emaysee_lora_filepath-name-selector.x @@ -1,8 +1,8 @@ import os -import folder_paths # ComfyUI utility to get model paths +import folder_paths # ComfyUI utility to get model paths -# Define the main class for your custom node. -# The class name will be used in NODE_CLASS_MAPPINGS. +# Define the main class for your custom node. +# The class name will be used in NODE_CLASS_MAPPINGS. class EmAySee_LoraFilePicker: """ A ComfyUI custom node to pick LORA files from configured paths. @@ -10,7 +10,7 @@ class EmAySee_LoraFilePicker: the selected filename and its full file path. """ def __init__(self): - # Constructor for the node. No special initialization needed for this node. + # Constructor for the node. No special initialization needed for this node. pass @classmethod @@ -19,37 +19,37 @@ class EmAySee_LoraFilePicker: Defines the input types (widgets and connections) for the node. This method is a class method and must return a dictionary. """ - # Get all LORA files from ComfyUI's configured LORA paths. - # folder_paths.get_filename_list("loras") returns a list of relative filenames - # (e.g., "my_lora.safetensors", "subdir/another_lora.pt"). + # Get all LORA files from ComfyUI's configured LORA paths. + # folder_paths.get_filename_list("loras") returns a list of relative filenames + # (e.g., "my_lora.safetensors", "subdir/another_lora.pt"). lora_files = folder_paths.get_filename_list("loras") - # Add a "None" option at the beginning of the list. - # This allows the user to explicitly select no LORA. + # Add a "None" option at the beginning of the list. + # This allows the user to explicitly select no LORA. lora_files.insert(0, "None") return { "required": { - # "lora_name" is the name of the input port that will appear on the node. - # (lora_files,) creates a dropdown widget in the ComfyUI interface, - # populated with the list of LORA files. + # "lora_name" is the name of the input port that will appear on the node. + # (lora_files,) creates a dropdown widget in the ComfyUI interface, + # populated with the list of LORA files. "lora_name": (lora_files,), }, } - # Define the types of data that this node will output. - # RETURN_TYPES must be a tuple of ComfyUI data types (e.g., "STRING", "IMAGE", "MODEL"). - RETURN_TYPES = ("STRING", "STRING",) # Outputs two strings. + # Define the types of data that this node will output. + # RETURN_TYPES must be a tuple of ComfyUI data types (e.g., "STRING", "IMAGE", "MODEL"). + RETURN_TYPES = ("STRING", "STRING",) # Outputs two strings. - # Define the names for the output ports. These names will appear on the node. + # Define the names for the output ports. These names will appear on the node. RETURN_NAMES = ("filename", "full_filepath",) - # Specifies the Python function within this class that ComfyUI should call - # when the node is executed in a workflow. + # Specifies the Python function within this class that ComfyUI should call + # when the node is executed in a workflow. FUNCTION = "pick_lora_file" - # Defines the category under which this node will appear in the ComfyUI "Add Node" menu. - # Using "EmAySee/" as a prefix helps organize your custom nodes. + # Defines the category under which this node will appear in the ComfyUI "Add Node" menu. + # Using "EmAySee/" as a prefix helps organize your custom nodes. CATEGORY = "EmAySee/File Utilities" def pick_lora_file(self, lora_name): @@ -58,60 +58,60 @@ class EmAySee_LoraFilePicker: It takes the selected LORA filename as input and returns the filename and its corresponding full file path. """ - # If the "None" option is selected in the dropdown, return empty strings. + # If the "None" option is selected in the dropdown, return empty strings. if lora_name == "None": return ("", "") full_lora_path = None - # Attempt to find the full path of the selected LORA file. - # folder_paths.get_filename_list("loras", return_full_paths=True) - # returns a list of absolute paths for all LORA files that ComfyUI has discovered. + # Attempt to find the full path of the selected LORA file. + # folder_paths.get_filename_list("loras", return_full_paths=True) + # returns a list of absolute paths for all LORA files that ComfyUI has discovered. for lora_path in folder_paths.get_filename_list("loras", return_full_paths=True): - # Check if the base name of the current lora_path matches the selected lora_name, - # or if the full lora_path ends with the selected lora_name (this handles - # cases where lora_name might include subdirectories, e.g., "my_folder/my_lora.safetensors"). + # Check if the base name of the current lora_path matches the selected lora_name, + # or if the full lora_path ends with the selected lora_name (this handles + # cases where lora_name might include subdirectories, e.g., "my_folder/my_lora.safetensors"). if os.path.basename(lora_path) == lora_name or lora_path.endswith(lora_name): full_lora_path = lora_path break - # Fallback mechanism: If the LORA was not found by direct matching (e.g., due to - # inconsistencies in how names are reported vs. stored, or new files not yet indexed). + # Fallback mechanism: If the LORA was not found by direct matching (e.g., due to + # inconsistencies in how names are reported vs. stored, or new files not yet indexed). if full_lora_path is None: - # Get all configured base directories for LORAs. + # Get all configured base directories for LORAs. lora_folders = folder_paths.get_folder_paths("loras") for folder in lora_folders: - # Construct a potential full path by joining the base folder and the selected LORA name. + # Construct a potential full path by joining the base folder and the selected LORA name. potential_path = os.path.join(folder, lora_name) - # Verify if this constructed path actually exists and points to a file. + # Verify if this constructed path actually exists and points to a file. if os.path.exists(potential_path) and os.path.isfile(potential_path): full_lora_path = potential_path break - # If after all attempts, the file still cannot be located, print a warning - # and return empty strings to ensure the workflow doesn't crash. + # If after all attempts, the file still cannot be located, print a warning + # and return empty strings to ensure the workflow doesn't crash. if full_lora_path is None: print(f"Warning: LORA file '{lora_name}' not found in known ComfyUI LORA paths.") return ("", "") - # Return the selected filename and its successfully determined full path. + # Return the selected filename and its successfully determined full path. return (lora_name, full_lora_path) -# NODE_CLASS_MAPPINGS is a mandatory dictionary that ComfyUI uses to discover -# and register your custom nodes. The keys are internal identifiers, and the -# values are the Python classes that define your nodes. +# NODE_CLASS_MAPPINGS is a mandatory dictionary that ComfyUI uses to discover +# and register your custom nodes. The keys are internal identifiers, and the +# values are the Python classes that define your nodes. NODE_CLASS_MAPPINGS = { "EmAySee_LoraFilePicker": EmAySee_LoraFilePicker, } -# NODE_DISPLAY_NAME_MAPPINGS is an optional dictionary that provides -# more user-friendly names for your nodes in the ComfyUI interface. -# This name will appear in the "Add Node" menu. +# NODE_DISPLAY_NAME_MAPPINGS is an optional dictionary that provides +# more user-friendly names for your nodes in the ComfyUI interface. +# This name will appear in the "Add Node" menu. NODE_DISPLAY_NAME_MAPPINGS = { "EmAySee_LoraFilePicker": "EmAySee Lora File Picker", } -# WEB_DIRECTORY can be defined if your node has custom web components (JavaScript/CSS). -# For this node, it's not strictly necessary, but it's a common practice to include it -# if you plan to extend the node with a custom UI later. -# WEB_DIRECTORY = "./web" # Uncomment and create a 'web' folder if needed for JS/CSS \ No newline at end of file +# WEB_DIRECTORY can be defined if your node has custom web components (JavaScript/CSS). +# For this node, it's not strictly necessary, but it's a common practice to include it +# if you plan to extend the node with a custom UI later. +# WEB_DIRECTORY = "./web" # Uncomment and create a 'web' folder if needed for JS/CSS \ No newline at end of file diff --git a/py/emaysee_multiplier_node.py b/py/emaysee_multiplier_node.py index 6d06bee..e7481f3 100755 --- a/py/emaysee_multiplier_node.py +++ b/py/emaysee_multiplier_node.py @@ -1,4 +1,4 @@ -import torch # Often needed for ComfyUI nodes +import torch # Often needed for ComfyUI nodes class EmAySee_MultiplierNode: """ @@ -9,36 +9,36 @@ class EmAySee_MultiplierNode: def INPUT_TYPES(s): return { "required": { - "multiplier_slider": ("FLOAT", {"default": 1.0, "min": 1.0, "max": 2.0, "step": 0.01}), # Slider input - "integer_field_1": ("INT", {"default": 10, "min": -10000, "max": 10000}), # First integer input field - "integer_field_2": ("INT", {"default": 20, "min": -10000, "max": 10000}), # Second integer input field + "multiplier_slider": ("FLOAT", {"default": 1.0, "min": 1.0, "max": 2.0, "step": 0.01}), # Slider input + "integer_field_1": ("INT", {"default": 10, "min": -10000, "max": 10000}), # First integer input field + "integer_field_2": ("INT", {"default": 20, "min": -10000, "max": 10000}), # Second integer input field } } - RETURN_TYPES = ("INT", "INT",) # Two integer outputs - RETURN_NAMES = ("multiplied_output_1", "multiplied_output_2",) # Names for the outputs - CATEGORY = "EmAySee_Utils" # Category in the ComfyUI menu - TITLE = "EmAySee Multiplier Node" # Title displayed on the node + RETURN_TYPES = ("INT", "INT",) # Two integer outputs + RETURN_NAMES = ("multiplied_output_1", "multiplied_output_2",) # Names for the outputs + CATEGORY = "EmAySee_Utils" # Category in the ComfyUI menu + TITLE = "EmAySee Multiplier Node" # Title displayed on the node - FUNCTION = "EmAySee_calculate_multiplication" # The method that will be executed + FUNCTION = "EmAySee_calculate_multiplication" # The method that will be executed def EmAySee_calculate_multiplication(self, multiplier_slider, integer_field_1, integer_field_2): """ Calculates the multiplication of each integer field by the slider value. """ - # Perform the multiplication and cast to integer for the output + # Perform the multiplication and cast to integer for the output output_1 = int(integer_field_1 * multiplier_slider) output_2 = int(integer_field_2 * multiplier_slider) - # Return the results as a tuple + # Return the results as a tuple return (output_1, output_2,) -# Mapping of node class name to the class +# Mapping of node class name to the class NODE_CLASS_MAPPINGS = { "EmAySee_MultiplierNode": EmAySee_MultiplierNode } -# Mapping of node class name to the display name in the UI +# Mapping of node class name to the display name in the UI NODE_DISPLAY_NAME_MAPPINGS = { "EmAySee_MultiplierNode": "EmAySee Multiplier Node" }