Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfc47d0d53 | ||
|
|
b837f38422 | ||
|
|
5e3c8cbc44 |
@@ -8,17 +8,19 @@ on:
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
# if this is a forked repository. Skipping the workflow.
|
||||
if: github.event.repository.fork == false
|
||||
if: ${{ github.repository_owner == 'acorderob' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@main
|
||||
uses: Comfy-Org/publish-node-action@v1
|
||||
with:
|
||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
* **seed**: Connect here the seed used. By default it is -1 (random).
|
||||
* **pos_prompt**: Connect here the prompt text, or fill it as a widget.
|
||||
* **neg_prompt**: Connect here the negative prompt text, or fill it as a widget.
|
||||
* **wc_wildcards_input**: Wildcards definitions (in yaml or json format). Direct input added to the ones found in the wildcards folders. Allows wildcards to be included in the workflow.
|
||||
|
||||
Other common settings (see [below](#common-settings)) also appear as inputs or widgets.
|
||||
|
||||
@@ -40,6 +41,7 @@ With this prompt: `__quality__, 1girl, ${head:__eyes__, __hair__, __expression__
|
||||
|
||||
The default value defines strings for *Pony* and *Illustrious* models.
|
||||
* **Apply in img2img**: check if you want to do the processing in img2img processes (*does not apply to the ComfyUI node*).
|
||||
* **Add original prompts to metadata**: adds original prompts to the metadata if they have changed (*does not apply to the ComfyUI node*).
|
||||
|
||||
### Wildcard settings
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import textwrap
|
||||
import time
|
||||
from collections import namedtuple
|
||||
@@ -9,11 +10,12 @@ from enum import Enum
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
import lark
|
||||
import lark.parsers
|
||||
import numpy as np
|
||||
|
||||
from ppp_logging import DEBUG_LEVEL
|
||||
from ppp_wildcards import PPPWildcard, PPPWildcards
|
||||
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
from ppp_logging import DEBUG_LEVEL # pylint: disable=import-error
|
||||
from ppp_wildcards import PPPWildcard, PPPWildcards # pylint: disable=import-error
|
||||
|
||||
|
||||
class PromptPostProcessor: # pylint: disable=too-few-public-methods,too-many-instance-attributes
|
||||
@@ -119,7 +121,7 @@ class PromptPostProcessor: # pylint: disable=too-few-public-methods,too-many-in
|
||||
self.wil_keep_choices_order = options.get("keep_choices_order", False)
|
||||
self.wil_choice_separator = options.get("choice_separator", self.DEFAULT_CHOICE_SEPARATOR)
|
||||
self.wil_ifwildcards = self.IFWILDCARDS_CHOICES(
|
||||
options.get("if_wildcards", self.IFWILDCARDS_CHOICES.ignore.value)
|
||||
options.get("if_wildcards", self.IFWILDCARDS_CHOICES.stop.value)
|
||||
)
|
||||
# Send to negative options
|
||||
self.stn_ignore_repeats = options.get("stn_ignore_repeats", True)
|
||||
@@ -220,6 +222,11 @@ class PromptPostProcessor: # pylint: disable=too-few-public-methods,too-many-in
|
||||
for model_name, model_type_and_substrings in self.variants_definitions.items()
|
||||
if model_name not in self.SUPPORTED_MODELS
|
||||
}
|
||||
is_models_true = [k for k, v in is_models.items() if v]
|
||||
if len(is_models_true) > 1:
|
||||
self.logger.warning(
|
||||
f"Multiple model variants detected at the same time in the filename!: {', '.join(is_models_true)}"
|
||||
)
|
||||
self.system_variables.update({"_is_" + x: y for x, y in is_models.items()})
|
||||
for x in sdchecks.keys():
|
||||
if x != "":
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ from collections import OrderedDict
|
||||
from logging import Logger
|
||||
from typing import Tuple
|
||||
|
||||
from ppp_logging import DEBUG_LEVEL
|
||||
from ppp_logging import DEBUG_LEVEL # pylint: disable=import-error
|
||||
|
||||
|
||||
class PPPLRUCache:
|
||||
|
||||
+37
-57
@@ -1,5 +1,3 @@
|
||||
# pylint: disable=missing-module-docstring, missing-class-docstring, missing-function-docstring, invalid-name
|
||||
|
||||
import os
|
||||
|
||||
# pylint: disable=import-error
|
||||
@@ -48,8 +46,6 @@ class PromptPostProcessorComfyUINode:
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
"dynamicPrompts": False,
|
||||
"defaultInput": True,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
"neg_prompt": (
|
||||
@@ -58,8 +54,6 @@ class PromptPostProcessorComfyUINode:
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
"dynamicPrompts": False,
|
||||
"defaultInput": True,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
},
|
||||
@@ -77,16 +71,13 @@ class PromptPostProcessorComfyUINode:
|
||||
{
|
||||
"default": "",
|
||||
"placeholder": "full path of the model",
|
||||
"defaultInput": True,
|
||||
"forceInput": False,
|
||||
"dynamicPrompts": False,
|
||||
},
|
||||
),
|
||||
"seed": (
|
||||
"INT",
|
||||
{
|
||||
"default": -1,
|
||||
"defaultInput": True,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
"debug_level": (
|
||||
@@ -94,8 +85,6 @@ class PromptPostProcessorComfyUINode:
|
||||
{
|
||||
"default": DEBUG_LEVEL.minimal.value,
|
||||
"tooltip": "Debug level",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
"variants_definitions": (
|
||||
@@ -105,8 +94,7 @@ class PromptPostProcessorComfyUINode:
|
||||
"multiline": True,
|
||||
"placeholder": "",
|
||||
"tooltip": "Definitions for variant models to be recognized based on strings found in the full filename. Format for each line is: 'name(kind)=comma separated list of substrings (case insensitive)' with kind being one of the base model types or not specified",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
"dynamicPrompts": False,
|
||||
},
|
||||
),
|
||||
"wc_process_wildcards": (
|
||||
@@ -116,8 +104,6 @@ class PromptPostProcessorComfyUINode:
|
||||
"tooltip": "Process wildcards in the prompt",
|
||||
"label_on": "Yes",
|
||||
"label_off": "No",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
"wc_wildcards_folders": (
|
||||
@@ -125,17 +111,24 @@ class PromptPostProcessorComfyUINode:
|
||||
{
|
||||
"default": "",
|
||||
"tooltip": "Comma separated list of wildcards folders",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
"dynamicPrompts": False,
|
||||
},
|
||||
),
|
||||
"wc_wildcards_input": (
|
||||
"STRING",
|
||||
{
|
||||
"default": "",
|
||||
"multiline": True,
|
||||
"placeholder": "wildcards definitions",
|
||||
"tooltip": "Wildcards definitions in yaml/json format",
|
||||
"dynamicPrompts": False,
|
||||
},
|
||||
),
|
||||
"wc_if_wildcards": (
|
||||
[e.value for e in PromptPostProcessor.IFWILDCARDS_CHOICES],
|
||||
{
|
||||
"default": PromptPostProcessor.IFWILDCARDS_CHOICES.ignore.value,
|
||||
"default": PromptPostProcessor.IFWILDCARDS_CHOICES.stop.value,
|
||||
"tooltip": "How to handle invalid wildcards in the prompt",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
"wc_choice_separator": (
|
||||
@@ -143,8 +136,7 @@ class PromptPostProcessorComfyUINode:
|
||||
{
|
||||
"default": PromptPostProcessor.DEFAULT_CHOICE_SEPARATOR,
|
||||
"tooltip": "Default separator for selected choices",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
"dynamicPrompts": False,
|
||||
},
|
||||
),
|
||||
"wc_keep_choices_order": (
|
||||
@@ -154,8 +146,6 @@ class PromptPostProcessorComfyUINode:
|
||||
"tooltip": "Keep the order of the choices in the prompt",
|
||||
"label_on": "Yes",
|
||||
"label_off": "No",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
"stn_separator": (
|
||||
@@ -163,8 +153,7 @@ class PromptPostProcessorComfyUINode:
|
||||
{
|
||||
"default": PromptPostProcessor.DEFAULT_STN_SEPARATOR,
|
||||
"tooltip": "Separator for the content added to the negative prompt",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
"dynamicPrompts": False,
|
||||
},
|
||||
),
|
||||
"stn_ignore_repeats": (
|
||||
@@ -174,8 +163,6 @@ class PromptPostProcessorComfyUINode:
|
||||
"tooltip": "Ignore repeated content added to the negative prompt",
|
||||
"label_on": "Yes",
|
||||
"label_off": "No",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
"cleanup_extra_spaces": (
|
||||
@@ -185,8 +172,6 @@ class PromptPostProcessorComfyUINode:
|
||||
"tooltip": "Remove extra spaces",
|
||||
"label_on": "Yes",
|
||||
"label_off": "No",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
"cleanup_empty_constructs": (
|
||||
@@ -196,8 +181,6 @@ class PromptPostProcessorComfyUINode:
|
||||
"tooltip": "Remove empty constructs",
|
||||
"label_on": "Yes",
|
||||
"label_off": "No",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
"cleanup_extra_separators": (
|
||||
@@ -207,8 +190,6 @@ class PromptPostProcessorComfyUINode:
|
||||
"tooltip": "Remove extra separators",
|
||||
"label_on": "Yes",
|
||||
"label_off": "No",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
"cleanup_extra_separators2": (
|
||||
@@ -218,8 +199,6 @@ class PromptPostProcessorComfyUINode:
|
||||
"tooltip": "Remove extra separators (additional cases)",
|
||||
"label_on": "Yes",
|
||||
"label_off": "No",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
"cleanup_breaks": (
|
||||
@@ -229,8 +208,6 @@ class PromptPostProcessorComfyUINode:
|
||||
"tooltip": "Cleanup around BREAKs",
|
||||
"label_on": "Yes",
|
||||
"label_off": "No",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
"cleanup_breaks_eol": (
|
||||
@@ -240,8 +217,6 @@ class PromptPostProcessorComfyUINode:
|
||||
"tooltip": "Set BREAKs in their own line",
|
||||
"label_on": "Yes",
|
||||
"label_off": "No",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
"cleanup_ands": (
|
||||
@@ -251,8 +226,6 @@ class PromptPostProcessorComfyUINode:
|
||||
"tooltip": "Cleanup around ANDs",
|
||||
"label_on": "Yes",
|
||||
"label_off": "No",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
"cleanup_ands_eol": (
|
||||
@@ -262,8 +235,6 @@ class PromptPostProcessorComfyUINode:
|
||||
"tooltip": "Set ANDs in their own line",
|
||||
"label_on": "Yes",
|
||||
"label_off": "No",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
"cleanup_extranetwork_tags": (
|
||||
@@ -273,8 +244,6 @@ class PromptPostProcessorComfyUINode:
|
||||
"tooltip": "Clean up around extra network tags",
|
||||
"label_on": "Yes",
|
||||
"label_off": "No",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
"cleanup_merge_attention": (
|
||||
@@ -284,8 +253,6 @@ class PromptPostProcessorComfyUINode:
|
||||
"tooltip": "Merge nested attention constructs",
|
||||
"label_on": "Yes",
|
||||
"label_off": "No",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
"remove_extranetwork_tags": (
|
||||
@@ -295,8 +262,6 @@ class PromptPostProcessorComfyUINode:
|
||||
"tooltip": "Remove extra network tags",
|
||||
"label_on": "Yes",
|
||||
"label_off": "No",
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
},
|
||||
@@ -342,6 +307,7 @@ class PromptPostProcessorComfyUINode:
|
||||
variants_definitions,
|
||||
wc_process_wildcards,
|
||||
wc_wildcards_folders,
|
||||
wc_wildcards_input,
|
||||
wc_if_wildcards,
|
||||
wc_choice_separator,
|
||||
wc_keep_choices_order,
|
||||
@@ -372,6 +338,7 @@ class PromptPostProcessorComfyUINode:
|
||||
"variants_definitions": variants_definitions,
|
||||
"process_wildcards": wc_process_wildcards,
|
||||
"wildcards_folders": wc_wildcards_folders,
|
||||
"wildcards_input": wc_wildcards_input,
|
||||
"if_wildcards": wc_if_wildcards,
|
||||
"choice_separator": wc_choice_separator,
|
||||
"keep_choices_order": wc_keep_choices_order,
|
||||
@@ -403,6 +370,7 @@ class PromptPostProcessorComfyUINode:
|
||||
variants_definitions,
|
||||
wc_process_wildcards,
|
||||
wc_wildcards_folders,
|
||||
wc_wildcards_input,
|
||||
wc_if_wildcards,
|
||||
wc_choice_separator,
|
||||
wc_keep_choices_order,
|
||||
@@ -427,13 +395,14 @@ class PromptPostProcessorComfyUINode:
|
||||
self.logger.warning("Model class is not provided. System variables might not be properly set.")
|
||||
if modelname == "":
|
||||
self.logger.warning("Modelname is not provided. System variables will not be properly set.")
|
||||
# model class values in ComfyUI\comfy\supported_models.py
|
||||
env_info = {
|
||||
"app": "comfyui",
|
||||
"models_path": folder_paths.models_dir,
|
||||
"model_filename": modelname or "", # path is relative to checkpoints folder
|
||||
"model_class": modelclass,
|
||||
"is_sd1": modelclass in ("SD15", "SD15_instructpix2pix"),
|
||||
"is_sd2": modelclass in ("SD20", "SD21UnclipL", "SD21UnclipH"),
|
||||
"is_sd2": modelclass in ("SD20", "SD21UnclipL", "SD21UnclipH", "LotusD"),
|
||||
"is_sdxl": (
|
||||
modelclass in ("SDXL", "SDXLRefiner", "SDXL_instructpix2pix", "Segmind_Vega", "KOALA_700M", "KOALA_1B")
|
||||
),
|
||||
@@ -441,9 +410,18 @@ class PromptPostProcessorComfyUINode:
|
||||
"is_sd3": modelclass in ("SD3",),
|
||||
"is_flux": modelclass in ("Flux", "FluxInpaint", "FluxSchnell"),
|
||||
"is_auraflow": modelclass in ("AuraFlow",),
|
||||
"is_pixart": modelclass in ("PixArtAlpha", "PixArtSigma"),
|
||||
"is_lumina2": modelclass in ("Lumina2",),
|
||||
"is_ltxv": modelclass in ("LTXV",),
|
||||
"is_cosmos": modelclass in ("CosmosT2V", "CosmosI2V"),
|
||||
"is_genmomochi": modelclass in ("GenmoMochi",),
|
||||
"is_hunyuan": modelclass in ("HunyuanDiT", "HunyuanDiT1"),
|
||||
"is_hunyuanvideo": modelclass in ("HunyuanVideo", "HunyuanVideoI2V", "HunyuanVideoSkyreelsI2V"),
|
||||
"is_hunyuan3d": modelclass in ("Hunyuan3Dv2", "Hunyuan3Dv2mini"),
|
||||
"is_wanvideo": modelclass in ("WAN21_T2V", "WAN21_I2V", "WAN21_FunControl2V"),
|
||||
"is_hidream": modelclass in ("HiDream",),
|
||||
}
|
||||
# Also supported: SVD_img2vid, SVD3D_u, SVD3_p, Stable_Zero123, SD_X4Upscaler,
|
||||
# Stable_Cascade_C, Stable_Cascade_B, StableAudio, HunyuanDiT, HunyuanDiT1, GenmoMochi, LTXV
|
||||
# Also supported: SVD_img2vid, SVD3D_u, SVD3_p, Stable_Zero123, SD_X4Upscaler, Stable_Cascade_C, Stable_Cascade_B, StableAudio
|
||||
|
||||
if wc_wildcards_folders == "":
|
||||
wc_wildcards_folders = ",".join(folder_paths.get_folder_paths("wildcards") or [])
|
||||
@@ -477,7 +455,11 @@ class PromptPostProcessorComfyUINode:
|
||||
"cleanup_merge_attention": cleanup_merge_attention,
|
||||
"remove_extranetwork_tags": remove_extranetwork_tags,
|
||||
}
|
||||
self.wildcards_obj.refresh_wildcards(debug_level, wildcards_folders if options["process_wildcards"] else None)
|
||||
self.wildcards_obj.refresh_wildcards(
|
||||
debug_level,
|
||||
wildcards_folders if options["process_wildcards"] else None,
|
||||
wc_wildcards_input,
|
||||
)
|
||||
ppp = PromptPostProcessor(
|
||||
self.logger, self.interrupt, env_info, options, self.grammar_content, self.wildcards_obj
|
||||
)
|
||||
@@ -519,8 +501,6 @@ class PromptPostProcessorSelectVariableComfyUINode:
|
||||
"multiline": False,
|
||||
"default": "",
|
||||
"dynamicPrompts": False,
|
||||
"defaultInput": False,
|
||||
"forceInput": False,
|
||||
},
|
||||
),
|
||||
},
|
||||
|
||||
+59
-29
@@ -1,11 +1,10 @@
|
||||
import fnmatch
|
||||
import os
|
||||
import json
|
||||
from typing import Optional
|
||||
import logging
|
||||
import yaml
|
||||
|
||||
from ppp_logging import DEBUG_LEVEL
|
||||
from ppp_logging import DEBUG_LEVEL # pylint: disable=import-error
|
||||
|
||||
|
||||
def deep_freeze(obj):
|
||||
@@ -20,12 +19,11 @@ def deep_freeze(obj):
|
||||
"""
|
||||
if isinstance(obj, dict):
|
||||
return tuple((k, deep_freeze(v)) for k, v in sorted(obj.items()))
|
||||
elif isinstance(obj, list):
|
||||
if isinstance(obj, list):
|
||||
return tuple(deep_freeze(i) for i in obj)
|
||||
elif isinstance(obj, set):
|
||||
if isinstance(obj, set):
|
||||
return tuple(deep_freeze(i) for i in sorted(obj))
|
||||
else:
|
||||
return obj
|
||||
return obj
|
||||
|
||||
|
||||
class PPPWildcard:
|
||||
@@ -70,6 +68,7 @@ class PPPWildcards:
|
||||
"""
|
||||
|
||||
DEFAULT_WILDCARDS_FOLDER = "wildcards"
|
||||
LOCALINPUT_FILENAME = "#INPUT"
|
||||
|
||||
def __init__(self, logger):
|
||||
self.__logger: logging.Logger = logger
|
||||
@@ -84,31 +83,38 @@ class PPPWildcards:
|
||||
def __sizeof__(self):
|
||||
return self.wildcards.__sizeof__() + self.__wildcards_folders.__sizeof__() + self.__wildcard_files.__sizeof__()
|
||||
|
||||
def refresh_wildcards(self, debug_level: DEBUG_LEVEL, wildcards_folders: Optional[list[str]]):
|
||||
def refresh_wildcards(
|
||||
self, debug_level: DEBUG_LEVEL, wildcards_folders: Optional[list[str]], wildcards_input: str = None
|
||||
):
|
||||
"""
|
||||
Initialize the wildcards.
|
||||
"""
|
||||
self.__debug_level = debug_level
|
||||
self.__wildcards_folders = wildcards_folders
|
||||
if wildcards_folders is not None:
|
||||
# if self.debug_level != DEBUG_LEVEL.none:
|
||||
# self.logger.info("Initializing wildcards...")
|
||||
# t1 = time.time()
|
||||
for fullpath in list(self.__wildcard_files.keys()):
|
||||
self.__wildcards_folders = wildcards_folders or []
|
||||
# if self.debug_level != DEBUG_LEVEL.none:
|
||||
# self.logger.info("Refreshing wildcards...")
|
||||
# t1 = time.time()
|
||||
for fullpath in list(self.__wildcard_files.keys()):
|
||||
if fullpath != self.LOCALINPUT_FILENAME:
|
||||
path = os.path.dirname(fullpath)
|
||||
if not os.path.exists(fullpath) or not any(
|
||||
os.path.commonpath([path, folder]) == folder for folder in self.__wildcards_folders
|
||||
):
|
||||
self.__remove_wildcards_from_file(fullpath)
|
||||
for f in self.__wildcards_folders:
|
||||
self.__get_wildcards_in_directory(f, f)
|
||||
# t2 = time.time()
|
||||
# if self.debug_level != DEBUG_LEVEL.none:
|
||||
# self.logger.info(f"Wildcards init time: {t2 - t1:.3f} seconds")
|
||||
self.__remove_wildcards_from_path(fullpath)
|
||||
elif wildcards_input is None:
|
||||
self.__remove_wildcards_from_path(fullpath)
|
||||
if wildcards_folders is not None or wildcards_input is not None:
|
||||
if wildcards_folders is not None:
|
||||
for f in self.__wildcards_folders:
|
||||
self.__get_wildcards_in_directory(f, f)
|
||||
if wildcards_input is not None:
|
||||
self.__get_wildcards_in_input(wildcards_input)
|
||||
else:
|
||||
self.__wildcards_folders = []
|
||||
self.wildcards = {}
|
||||
self.__wildcard_files = {}
|
||||
# t2 = time.time()
|
||||
# if self.debug_level != DEBUG_LEVEL.none:
|
||||
# self.logger.info(f"Wildcards refresh time: {t2 - t1:.3f} seconds")
|
||||
|
||||
def get_wildcards(self, key: str) -> list[PPPWildcard]:
|
||||
"""
|
||||
@@ -161,7 +167,7 @@ class PPPWildcards:
|
||||
return None
|
||||
return current_dict
|
||||
|
||||
def __remove_wildcards_from_file(self, full_path: str, debug=True):
|
||||
def __remove_wildcards_from_path(self, full_path: str, debug=True):
|
||||
"""
|
||||
Clear all wildcards in a file.
|
||||
|
||||
@@ -169,9 +175,12 @@ class PPPWildcards:
|
||||
full_path (str): The path to the file.
|
||||
debug (bool): Whether to print debug messages or not.
|
||||
"""
|
||||
last_modified_cached = self.__wildcard_files.get(full_path, None)
|
||||
last_modified_cached = self.__wildcard_files.get(full_path, None) # a time or a hash
|
||||
if debug and last_modified_cached is not None and self.__debug_level != DEBUG_LEVEL.none:
|
||||
self.__logger.debug(f"Removing wildcards from file: {full_path}")
|
||||
if full_path == self.LOCALINPUT_FILENAME:
|
||||
self.__logger.debug("Removing wildcards from input")
|
||||
else:
|
||||
self.__logger.debug(f"Removing wildcards from file: {full_path}")
|
||||
if full_path in self.__wildcard_files.keys():
|
||||
del self.__wildcard_files[full_path]
|
||||
for key in list(self.wildcards.keys()):
|
||||
@@ -194,7 +203,7 @@ class PPPWildcards:
|
||||
_, extension = os.path.splitext(filename)
|
||||
if extension not in (".txt", ".json", ".yaml", ".yml"):
|
||||
return
|
||||
self.__remove_wildcards_from_file(full_path, False)
|
||||
self.__remove_wildcards_from_path(full_path, False)
|
||||
if last_modified_cached is not None and self.__debug_level != DEBUG_LEVEL.none:
|
||||
self.__logger.debug(f"Updating wildcards from file: {full_path}")
|
||||
if extension == ".txt":
|
||||
@@ -203,6 +212,31 @@ class PPPWildcards:
|
||||
self.__get_wildcards_in_structured_file(full_path, base)
|
||||
self.__wildcard_files[full_path] = last_modified
|
||||
|
||||
def __get_wildcards_in_input(self, wildcards_input: str):
|
||||
"""
|
||||
Get all wildcards in the string.
|
||||
|
||||
Args:
|
||||
wildcards_input (str): The input string containing wildcards in json or yaml format.
|
||||
"""
|
||||
new_h = hash(wildcards_input)
|
||||
h = self.__wildcard_files.get(self.LOCALINPUT_FILENAME, None)
|
||||
if h == new_h:
|
||||
return
|
||||
self.__remove_wildcards_from_path(self.LOCALINPUT_FILENAME, False)
|
||||
if h is not None and self.__debug_level != DEBUG_LEVEL.none:
|
||||
self.__logger.debug("Updating wildcards from input")
|
||||
wildcards_input = wildcards_input.strip()
|
||||
if wildcards_input != "":
|
||||
try:
|
||||
content = yaml.safe_load(wildcards_input)
|
||||
except yaml.YAMLError as e:
|
||||
self.__logger.warning(f"Invalid format for input wildcards: {e}")
|
||||
return
|
||||
if content is not None:
|
||||
self.__add_wildcard(content, self.LOCALINPUT_FILENAME, [self.LOCALINPUT_FILENAME])
|
||||
self.__wildcard_files[self.LOCALINPUT_FILENAME] = new_h
|
||||
|
||||
def is_dict_choices_options(self, d: dict) -> bool:
|
||||
"""
|
||||
Check if a dictionary is a valid choices options dictionary.
|
||||
@@ -374,12 +408,8 @@ class PPPWildcards:
|
||||
"""
|
||||
external_key: str = os.path.relpath(os.path.splitext(full_path)[0], base)
|
||||
external_key_parts = external_key.split(os.sep)
|
||||
_, extension = os.path.splitext(full_path)
|
||||
with open(full_path, "r", encoding="utf-8") as file:
|
||||
if extension == ".json":
|
||||
content = json.loads(file.read())
|
||||
else:
|
||||
content = yaml.safe_load(file)
|
||||
content = yaml.safe_load(file)
|
||||
self.__add_wildcard(content, full_path, external_key_parts)
|
||||
|
||||
def __get_wildcards_in_text_file(self, full_path, base):
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "sd-webui-prompt-postprocessor"
|
||||
description = "Stable Diffusion WebUI & ComfyUI extension to post-process the prompt, including sending content from the prompt to the negative prompt and wildcards."
|
||||
version = "2.9.0"
|
||||
version = "2.10.0"
|
||||
license = { file = "LICENSE.txt" }
|
||||
dependencies = ["lark", "numpy", "pyyaml"]
|
||||
|
||||
|
||||
+63
-31
@@ -6,18 +6,17 @@ import os
|
||||
import time
|
||||
import numpy as np
|
||||
|
||||
sys.path.append(os.path.join(sys.path[0], ".."))
|
||||
sys.path.append(os.path.join(os.path.realpath(__file__), "..")) # base path for the extension
|
||||
|
||||
|
||||
from modules import scripts, shared, script_callbacks
|
||||
from modules.processing import StableDiffusionProcessing
|
||||
from modules.shared import opts
|
||||
from modules.paths import models_path
|
||||
import gradio as gr
|
||||
from ppp import PromptPostProcessor
|
||||
from ppp_logging import DEBUG_LEVEL, PromptPostProcessorLogFactory
|
||||
from ppp_cache import PPPLRUCache
|
||||
from ppp_wildcards import PPPWildcards
|
||||
from modules import scripts, shared, script_callbacks # pylint: disable=import-error
|
||||
from modules.processing import StableDiffusionProcessing # pylint: disable=import-error
|
||||
from modules.shared import opts # pylint: disable=import-error
|
||||
from modules.paths import models_path # pylint: disable=import-error
|
||||
import gradio as gr # pylint: disable=import-error
|
||||
from ppp import PromptPostProcessor # pylint: disable=import-error
|
||||
from ppp_logging import DEBUG_LEVEL, PromptPostProcessorLogFactory # pylint: disable=import-error
|
||||
from ppp_cache import PPPLRUCache # pylint: disable=import-error
|
||||
from ppp_wildcards import PPPWildcards # pylint: disable=import-error
|
||||
|
||||
|
||||
class PromptPostProcessorA1111Script(scripts.Script):
|
||||
@@ -82,7 +81,7 @@ class PromptPostProcessorA1111Script(scripts.Script):
|
||||
"""
|
||||
return PromptPostProcessor.NAME
|
||||
|
||||
def show(self, is_img2img):
|
||||
def show(self, is_img2img): # pylint: disable=unused-argument
|
||||
"""
|
||||
Determines whether the script should be shown based on the kind of processing.
|
||||
|
||||
@@ -94,7 +93,7 @@ class PromptPostProcessorA1111Script(scripts.Script):
|
||||
"""
|
||||
return scripts.AlwaysVisible
|
||||
|
||||
def ui(self, is_img2img):
|
||||
def ui(self, is_img2img): # pylint: disable=unused-argument
|
||||
with gr.Accordion(PromptPostProcessor.NAME, open=False):
|
||||
force_equal_seeds = gr.Checkbox(
|
||||
label="Force equal seeds",
|
||||
@@ -171,6 +170,7 @@ class PromptPostProcessorA1111Script(scripts.Script):
|
||||
is_i2i = bool(init_images[0])
|
||||
self.ppp_debug_level = DEBUG_LEVEL(getattr(opts, "ppp_gen_debug_level", DEBUG_LEVEL.none.value))
|
||||
do_i2i = getattr(opts, "ppp_gen_doi2i", False)
|
||||
add_prompts = getattr(opts, "ppp_gen_addpromptstometadata", True)
|
||||
if is_i2i and not do_i2i:
|
||||
if self.ppp_debug_level != DEBUG_LEVEL.none:
|
||||
self.ppp_logger.info("Not processing the prompt for i2i")
|
||||
@@ -226,6 +226,16 @@ class PromptPostProcessorA1111Script(scripts.Script):
|
||||
"is_sd3": False, # Stable Diffusion 3
|
||||
"is_flux": False, # Flux
|
||||
"is_auraflow": False, # AuraFlow
|
||||
"is_pixart": False, # PixArt
|
||||
"is_lumina2": False, # Lumina2
|
||||
"is_ltxv": False, # LTXV
|
||||
"is_cosmos": False, # Cosmos
|
||||
"is_genmomochi": False, # GenmoMochi
|
||||
"is_hunyuan": False, # Hunyuan
|
||||
"is_hunyuanvideo": False, # HunyuanVideo
|
||||
"is_hunyuan3d": False, # Hunyuan3D
|
||||
"is_wanvideo": False, # WanVideo
|
||||
"is_hidream": False, # HiDream
|
||||
}
|
||||
if app == "sdnext":
|
||||
# cannot differentiate SD1 and SD2, we set True to both
|
||||
@@ -283,7 +293,7 @@ class PromptPostProcessorA1111Script(scripts.Script):
|
||||
opts, "ppp_gen_variantsdefinitions", PromptPostProcessor.DEFAULT_VARIANTS_DEFINITIONS
|
||||
),
|
||||
"process_wildcards": getattr(opts, "ppp_wil_processwildcards", True),
|
||||
"if_wildcards": getattr(opts, "ppp_wil_ifwildcards", PromptPostProcessor.IFWILDCARDS_CHOICES.ignore.value),
|
||||
"if_wildcards": getattr(opts, "ppp_wil_ifwildcards", PromptPostProcessor.IFWILDCARDS_CHOICES.stop.value),
|
||||
"choice_separator": getattr(opts, "ppp_wil_choice_separator", PromptPostProcessor.DEFAULT_CHOICE_SEPARATOR),
|
||||
"keep_choices_order": getattr(opts, "ppp_wil_keep_choices_order", False),
|
||||
"stn_separator": getattr(opts, "ppp_stn_separator", PromptPostProcessor.DEFAULT_STN_SEPARATOR),
|
||||
@@ -349,12 +359,6 @@ class PromptPostProcessorA1111Script(scripts.Script):
|
||||
rpr: list[str] = getattr(p, "all_prompts", None)
|
||||
rnr: list[str] = getattr(p, "all_negative_prompts", None)
|
||||
if rpr is not None and rnr is not None:
|
||||
extra_params.update(
|
||||
{
|
||||
"PPP original prompts": rpr.copy(),
|
||||
"PPP original negative prompts": rnr.copy(),
|
||||
}
|
||||
)
|
||||
prompts_list += [
|
||||
("regular", seed, prompt, negative_prompt)
|
||||
for seed, prompt, negative_prompt in zip(calculated_seeds, rpr, rnr)
|
||||
@@ -364,23 +368,12 @@ class PromptPostProcessorA1111Script(scripts.Script):
|
||||
rph: list[str] = getattr(p, "all_hr_prompts", None)
|
||||
rnh: list[str] = getattr(p, "all_hr_negative_prompts", None)
|
||||
if rph is not None and rnh is not None:
|
||||
extra_params.update(
|
||||
{
|
||||
"PPP original HR prompts": rph.copy(),
|
||||
"PPP original HR negative prompts": rnh.copy(),
|
||||
}
|
||||
)
|
||||
prompts_list += [
|
||||
("hiresfix", seed, prompt, negative_prompt)
|
||||
for seed, prompt, negative_prompt in zip(calculated_seeds, rph, rnh)
|
||||
if (seed, prompt, negative_prompt) not in prompts_list
|
||||
]
|
||||
|
||||
# fill extra generation parameters only if not already present
|
||||
for k, v in extra_params.items():
|
||||
if p.extra_generation_params.get(k) is None:
|
||||
p.extra_generation_params[k] = v
|
||||
|
||||
# processes prompts
|
||||
for i, (prompttype, seed, prompt, negative_prompt) in enumerate(prompts_list):
|
||||
if self.ppp_debug_level != DEBUG_LEVEL.none:
|
||||
@@ -395,17 +388,48 @@ class PromptPostProcessorA1111Script(scripts.Script):
|
||||
|
||||
# updates the prompts
|
||||
if rpr is not None and rnr is not None:
|
||||
rpr_changes = False
|
||||
rnr_changes = False
|
||||
rpr_copy = rpr.copy()
|
||||
rnr_copy = rnr.copy()
|
||||
for i, (seed, prompt, negative_prompt) in enumerate(zip(calculated_seeds, rpr, rnr)):
|
||||
found = self.lru_cache.get((seed, hash(self.wildcards_obj), prompt, negative_prompt))
|
||||
if found is not None:
|
||||
if rpr[i].strip() != found[0].strip():
|
||||
rpr_changes = True
|
||||
if rnr[i].strip() != found[1].strip():
|
||||
rnr_changes = True
|
||||
rpr[i] = found[0]
|
||||
rnr[i] = found[1]
|
||||
if add_prompts:
|
||||
if rpr_changes:
|
||||
extra_params["PPP original prompts"] = rpr_copy
|
||||
if rnr_changes:
|
||||
extra_params["PPP original negative prompts"] = rnr_copy
|
||||
if rph is not None and rnh is not None:
|
||||
rph_changes = False
|
||||
rnh_changes = False
|
||||
rph_copy = rph.copy()
|
||||
rnh_copy = rnh.copy()
|
||||
for i, (seed, prompt, negative_prompt) in enumerate(zip(calculated_seeds, rph, rnh)):
|
||||
found = self.lru_cache.get((seed, hash(self.wildcards_obj), prompt, negative_prompt))
|
||||
if found is not None:
|
||||
if rph[i].strip() != found[0].strip():
|
||||
rph_changes = True
|
||||
if rnh[i].strip() != found[1].strip():
|
||||
rnh_changes = True
|
||||
rph[i] = found[0]
|
||||
rnh[i] = found[1]
|
||||
if add_prompts:
|
||||
if rph_changes:
|
||||
extra_params["PPP original HR prompts"] = rph_copy
|
||||
if rnh_changes:
|
||||
extra_params["PPP original HR negative prompts"] = rnh_copy
|
||||
|
||||
# fill extra generation parameters only if not already present
|
||||
for k, v in extra_params.items():
|
||||
if p.extra_generation_params.get(k) is None:
|
||||
p.extra_generation_params[k] = v
|
||||
|
||||
t2 = time.time()
|
||||
if self.ppp_debug_level != DEBUG_LEVEL.none:
|
||||
@@ -500,6 +524,14 @@ def on_ui_settings():
|
||||
section=section,
|
||||
),
|
||||
)
|
||||
shared.opts.add_option(
|
||||
key="ppp_gen_addpromptstometadata",
|
||||
info=shared.OptionInfo(
|
||||
True,
|
||||
label="Add original prompts to metadata (if they change)",
|
||||
section=section,
|
||||
),
|
||||
)
|
||||
|
||||
# wildcard settings
|
||||
shared.opts.add_option(
|
||||
|
||||
+48
-27
@@ -4,12 +4,11 @@ import unittest
|
||||
import sys
|
||||
import os
|
||||
|
||||
from ppp_wildcards import PPPWildcards
|
||||
sys.path.append(os.path.join(os.path.realpath(__file__), "..")) # base path for the extension
|
||||
|
||||
sys.path.append(os.path.join(sys.path[0], ".."))
|
||||
|
||||
from ppp import PromptPostProcessor
|
||||
from ppp_logging import DEBUG_LEVEL, PromptPostProcessorLogFactory
|
||||
from ppp_wildcards import PPPWildcards # pylint: disable=import-error
|
||||
from ppp import PromptPostProcessor # pylint: disable=import-error
|
||||
from ppp_logging import DEBUG_LEVEL, PromptPostProcessorLogFactory # pylint: disable=import-error
|
||||
|
||||
|
||||
PromptPair = namedtuple("PromptPair", ["prompt", "negative_prompt"], defaults=["", ""])
|
||||
@@ -69,6 +68,13 @@ class TestPromptPostProcessor(unittest.TestCase):
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), "wildcards")),
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), "wildcards2")),
|
||||
],
|
||||
"""
|
||||
yaml_input:
|
||||
wildcardI:
|
||||
- choice1
|
||||
- choice2
|
||||
- choice3
|
||||
"""
|
||||
)
|
||||
grammar_filename = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../grammar.lark")
|
||||
with open(grammar_filename, "r", encoding="utf-8") as file:
|
||||
@@ -355,7 +361,8 @@ class TestPromptPostProcessor(unittest.TestCase):
|
||||
def test_cmd_if_nested(self): # nested if command
|
||||
self.__process(
|
||||
PromptPair(
|
||||
"this is <ppp:if _sd eq 'sd1'>SD1<ppp:else><ppp:if _is_pony>PONY<ppp:else>SD2<ppp:/if><ppp:/if><ppp:if _is_sdxl_no_pony>NOPONY<ppp:/if><ppp:if _is_pure_sdxl>NOPONY<ppp:/if>", ""
|
||||
"this is <ppp:if _sd eq 'sd1'>SD1<ppp:else><ppp:if _is_pony>PONY<ppp:else>SD2<ppp:/if><ppp:/if><ppp:if _is_sdxl_no_pony>NOPONY<ppp:/if><ppp:if _is_pure_sdxl>NOPONY<ppp:/if>",
|
||||
"",
|
||||
),
|
||||
PromptPair("this is PONY", ""),
|
||||
ppp=PromptPostProcessor(
|
||||
@@ -394,13 +401,19 @@ class TestPromptPostProcessor(unittest.TestCase):
|
||||
|
||||
def test_cmd_set_if_complex_conditions_1(self): # complex conditions (or)
|
||||
self.__process(
|
||||
PromptPair("<ppp:set v1>true<ppp:/set><ppp:set v2>false<ppp:/set>this test is <ppp:if v1 or v2>OK<ppp:else>not OK<ppp:/if>", ""),
|
||||
PromptPair(
|
||||
"<ppp:set v1>true<ppp:/set><ppp:set v2>false<ppp:/set>this test is <ppp:if v1 or v2>OK<ppp:else>not OK<ppp:/if>",
|
||||
"",
|
||||
),
|
||||
PromptPair("this test is OK", ""),
|
||||
)
|
||||
|
||||
def test_cmd_set_if_complex_conditions_2(self): # complex conditions (and)
|
||||
self.__process(
|
||||
PromptPair("<ppp:set v1>true<ppp:/set><ppp:set v2>true<ppp:/set>this test is <ppp:if v1 and v2>OK<ppp:else>not OK<ppp:/if>", ""),
|
||||
PromptPair(
|
||||
"<ppp:set v1>true<ppp:/set><ppp:set v2>true<ppp:/set>this test is <ppp:if v1 and v2>OK<ppp:else>not OK<ppp:/if>",
|
||||
"",
|
||||
),
|
||||
PromptPair("this test is OK", ""),
|
||||
)
|
||||
|
||||
@@ -412,25 +425,37 @@ class TestPromptPostProcessor(unittest.TestCase):
|
||||
|
||||
def test_cmd_set_if_complex_conditions_4(self): # complex conditions (not, precedence)
|
||||
self.__process(
|
||||
PromptPair("<ppp:set v1>true<ppp:/set><ppp:set v2>false<ppp:/set>this test is <ppp:if not (v1 and v2)>OK<ppp:else>not OK<ppp:/if>", ""),
|
||||
PromptPair(
|
||||
"<ppp:set v1>true<ppp:/set><ppp:set v2>false<ppp:/set>this test is <ppp:if not (v1 and v2)>OK<ppp:else>not OK<ppp:/if>",
|
||||
"",
|
||||
),
|
||||
PromptPair("this test is OK", ""),
|
||||
)
|
||||
|
||||
def test_cmd_set_if_complex_conditions_5(self): # complex conditions (not, precedence, comparison)
|
||||
self.__process(
|
||||
PromptPair("<ppp:set v1>1<ppp:/set><ppp:set v2>false<ppp:/set>this test is <ppp:if not(v1 eq '1' and v2)>OK<ppp:else>not OK<ppp:/if>", ""),
|
||||
PromptPair(
|
||||
"<ppp:set v1>1<ppp:/set><ppp:set v2>false<ppp:/set>this test is <ppp:if not(v1 eq '1' and v2)>OK<ppp:else>not OK<ppp:/if>",
|
||||
"",
|
||||
),
|
||||
PromptPair("this test is OK", ""),
|
||||
)
|
||||
|
||||
def test_cmd_set_if_complex_conditions_6(self): # complex conditions
|
||||
self.__process(
|
||||
PromptPair("<ppp:set v1>1<ppp:/set><ppp:set v2>2<ppp:/set><ppp:set v3>3<ppp:/set>this test is <ppp:if v1 eq '1' and v2 eq '2' and v3 eq '3'>OK<ppp:else>not OK<ppp:/if>", ""),
|
||||
PromptPair(
|
||||
"<ppp:set v1>1<ppp:/set><ppp:set v2>2<ppp:/set><ppp:set v3>3<ppp:/set>this test is <ppp:if v1 eq '1' and v2 eq '2' and v3 eq '3'>OK<ppp:else>not OK<ppp:/if>",
|
||||
"",
|
||||
),
|
||||
PromptPair("this test is OK", ""),
|
||||
)
|
||||
|
||||
def test_cmd_set_if_complex_conditions_7(self): # complex conditions
|
||||
self.__process(
|
||||
PromptPair("<ppp:set v1>1<ppp:/set><ppp:set v2>2<ppp:/set><ppp:set v3>3<ppp:/set>this test is <ppp:if v1 eq '1' and v2 not eq '2' or v3 eq '3'>OK<ppp:else>not OK<ppp:/if>", ""),
|
||||
PromptPair(
|
||||
"<ppp:set v1>1<ppp:/set><ppp:set v2>2<ppp:/set><ppp:set v3>3<ppp:/set>this test is <ppp:if v1 eq '1' and v2 not eq '2' or v3 eq '3'>OK<ppp:else>not OK<ppp:/if>",
|
||||
"",
|
||||
),
|
||||
PromptPair("this test is OK", ""),
|
||||
)
|
||||
|
||||
@@ -916,11 +941,21 @@ class TestPromptPostProcessor(unittest.TestCase):
|
||||
ppp=self.__nocupppp,
|
||||
)
|
||||
|
||||
def test_wc_wildcard_input(self): # simple yaml wildcard input
|
||||
self.__process(
|
||||
PromptPair("the choices are: __yaml_input/wildcardI__", ""),
|
||||
PromptPair("the choices are: choice2", ""),
|
||||
ppp=self.__nocupppp,
|
||||
)
|
||||
|
||||
# Model variants tests
|
||||
|
||||
def test_variants(self):
|
||||
self.__process(
|
||||
PromptPair("<ppp:if _is_test1>test1<ppp:/if><ppp:if _is_test2>test2<ppp:/if><ppp:if _is_test3>test3<ppp:/if><ppp:if _is_test4>test4<ppp:/if>", ""),
|
||||
PromptPair(
|
||||
"<ppp:if _is_test1>test1<ppp:/if><ppp:if _is_test2>test2<ppp:/if><ppp:if _is_test3>test3<ppp:/if><ppp:if _is_test4>test4<ppp:/if>",
|
||||
"",
|
||||
),
|
||||
PromptPair("test1test2", ""),
|
||||
ppp=PromptPostProcessor(
|
||||
self.__ppp_logger,
|
||||
@@ -947,7 +982,6 @@ class TestPromptPostProcessor(unittest.TestCase):
|
||||
ppp=self.__comfyuippp,
|
||||
)
|
||||
|
||||
|
||||
# def test_mix(self):
|
||||
# self.__process(
|
||||
# PromptPair(
|
||||
@@ -961,19 +995,6 @@ class TestPromptPostProcessor(unittest.TestCase):
|
||||
# ppp=self.__nocupppp,
|
||||
# )
|
||||
|
||||
# def test_real(self):
|
||||
# self.__wildcards_obj.refresh_wildcards(
|
||||
# DEBUG_LEVEL.full,
|
||||
# ["D:\\AI\\SD\\_configuraciones\\acb-wildcards\\wildcards"],
|
||||
# )
|
||||
# self.__process(
|
||||
# PromptPair(
|
||||
# "${separator=()}, __quality/high__ __misc/sep__, photograph of a __character__",
|
||||
# "__negatives/ng_generic__",
|
||||
# ),
|
||||
# PromptPair("", ""),
|
||||
# )
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user