Compare commits

...
3 Commits
Author SHA1 Message Date
Antonio Cordero Balcazar bfc47d0d53 * A1111: add prompts to metadata only if they change 2025-05-03 12:07:18 +02:00
Antonio Cordero Balcazar b837f38422 * if_wildcards option defaults now to stop.
* ComfyUI: new wildcards_input.
* removed json import since yaml can read them.
* more models detected in ComfyUI.
* ComfyUI: example workflow.
2025-05-03 12:07:18 +02:00
Antonio Cordero Balcazar 5e3c8cbc44 * ComfyUI: updates to the ComfyUI registry workflow.
* A1111: option to add original prompts to metadata.
* fixed some bogus import error shenanigans.
* warn when a model name detects multiple variants.
2025-04-11 17:50:26 +02:00
10 changed files with 228 additions and 153 deletions
+5 -3
View File
@@ -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 }}
+2
View File
@@ -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
+11 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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