* Added deduplicate_merge method, use when copy_to_l enabled * Use deduplicate_merge for SDXLPromptStylerAdvanced text_positive combined output
356 lines
15 KiB
Python
356 lines
15 KiB
Python
import json
|
|
import os
|
|
|
|
def read_json_file(file_path):
|
|
"""
|
|
Reads a JSON file's content and returns it.
|
|
Ensures content matches the expected format.
|
|
"""
|
|
if not os.access(file_path, os.R_OK):
|
|
print(f"Warning: No read permissions for file {file_path}")
|
|
return None
|
|
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as file:
|
|
content = json.load(file)
|
|
# Check if the content matches the expected format.
|
|
if not all(['name' in item and 'prompt' in item and 'negative_prompt' in item for item in content]):
|
|
print(f"Warning: Invalid content in file {file_path}")
|
|
return None
|
|
return content
|
|
except Exception as e:
|
|
print(f"An error occurred while reading {file_path}: {str(e)}")
|
|
return None
|
|
|
|
|
|
def read_sdxl_styles(json_data):
|
|
"""
|
|
Returns style names from the provided JSON data.
|
|
"""
|
|
if not isinstance(json_data, list):
|
|
print("Error: input data must be a list")
|
|
return []
|
|
|
|
return [item['name'] for item in json_data if isinstance(item, dict) and 'name' in item]
|
|
|
|
def get_all_json_files(directory):
|
|
"""
|
|
Returns all JSON files from the specified directory.
|
|
"""
|
|
return [os.path.join(directory, file) for file in os.listdir(directory) if file.endswith('.json') and os.path.isfile(os.path.join(directory, file))]
|
|
|
|
|
|
def load_styles_from_directory(directory):
|
|
"""
|
|
Loads styles from all JSON files in the directory.
|
|
Renames duplicate style names by appending a suffix.
|
|
"""
|
|
json_files = get_all_json_files(directory)
|
|
combined_data = []
|
|
seen = set()
|
|
|
|
for json_file in json_files:
|
|
json_data = read_json_file(json_file)
|
|
if json_data:
|
|
for item in json_data:
|
|
original_style = item['name']
|
|
style = original_style
|
|
suffix = 1
|
|
while style in seen:
|
|
style = f"{original_style}_{suffix}"
|
|
suffix += 1
|
|
item['name'] = style
|
|
seen.add(style)
|
|
combined_data.append(item)
|
|
|
|
unique_style_names = [item['name'] for item in combined_data if isinstance(item, dict) and 'name' in item]
|
|
|
|
return combined_data, unique_style_names
|
|
|
|
|
|
def validate_json_data(json_data):
|
|
"""
|
|
Validates the structure of the JSON data.
|
|
"""
|
|
if not isinstance(json_data, list):
|
|
return False
|
|
for template in json_data:
|
|
if 'name' not in template or 'prompt' not in template:
|
|
return False
|
|
return True
|
|
|
|
def find_template_by_name(json_data, template_name):
|
|
"""
|
|
Returns a template from the JSON data by name or None if not found.
|
|
"""
|
|
for template in json_data:
|
|
if template['name'] == template_name:
|
|
return template
|
|
return None
|
|
|
|
def split_template_advanced(template: str) -> tuple:
|
|
"""
|
|
Splits a template into two parts based on a specific pattern.
|
|
"""
|
|
if " . " in template:
|
|
template_prompt_g, template_prompt_l = template.split(" . ", 1)
|
|
template_prompt_g = template_prompt_g.strip()
|
|
template_prompt_l = template_prompt_l.strip()
|
|
else:
|
|
template_prompt_g = template
|
|
template_prompt_l = ""
|
|
|
|
return template_prompt_g, template_prompt_l
|
|
|
|
def replace_prompts_in_template(template, positive_prompt, negative_prompt):
|
|
"""
|
|
Replace the placeholders in a given template with the provided prompts.
|
|
|
|
Args:
|
|
- template (dict): The template containing prompt placeholders.
|
|
- positive_prompt (str): The positive prompt to replace '{prompt}' in the template.
|
|
- negative_prompt (str): The negative prompt to be combined with any existing negative prompt in the template.
|
|
|
|
Returns:
|
|
- tuple: A tuple containing the replaced positive and negative prompts.
|
|
"""
|
|
positive_result = template['prompt'].replace('{prompt}', positive_prompt)
|
|
|
|
json_negative_prompt = template.get('negative_prompt', "")
|
|
negative_result = f"{json_negative_prompt}, {negative_prompt}" if json_negative_prompt and negative_prompt else json_negative_prompt or negative_prompt
|
|
|
|
return positive_result, negative_result
|
|
|
|
def deduplicate_merge(prompt_1, prompt_2):
|
|
"""
|
|
Merge two prompts, deduplicating the tokens.
|
|
|
|
Args:
|
|
- prompt1 (str): The first prompt.
|
|
- prompt2 (str): The second prompt.
|
|
|
|
Returns:
|
|
- str: The merged and deduplicated prompt.
|
|
"""
|
|
if not prompt_2:
|
|
return prompt_1
|
|
elif not prompt_1:
|
|
return prompt_2
|
|
|
|
token_prompt_1 = list(map(lambda x: x.strip(), prompt_1.split(",")))
|
|
token_prompt_2 = list(map(lambda x: x.strip(), prompt_2.split(",")))
|
|
|
|
# deduplicate common prompt parts
|
|
for token in token_prompt_1:
|
|
if token in token_prompt_2:
|
|
token_prompt_2.remove(token)
|
|
|
|
token_prompt_1.extend(token_prompt_2)
|
|
|
|
prompt_out = ", ".join(token_prompt_1)
|
|
|
|
return prompt_out
|
|
|
|
def replace_prompts_in_template_advanced(template, positive_prompt_g, positive_prompt_l, negative_prompt, negative_prompt_to, copy_to_l):
|
|
"""
|
|
Replace the placeholders in a given template with the provided prompts and split them accordingly.
|
|
|
|
Args:
|
|
- template (dict): The template containing prompt placeholders.
|
|
- positive_prompt_g (str): The main positive prompt to replace '{prompt}' in the template.
|
|
- positive_prompt_l (str): The auxiliary positive prompt to be combined in a specific manner.
|
|
- negative_prompt (str): The negative prompt to be combined with any existing negative prompt in the template.
|
|
- negative_prompt_to (str): The negative prompt destination {Both, G only, L only}.
|
|
- copy_to_l (bool): Copy the G positive prompt to L.
|
|
|
|
Returns:
|
|
- tuple: A tuple containing the replaced main positive, auxiliary positive, combined positive, main negative, auxiliary negative, and negative prompts.
|
|
"""
|
|
template_prompt_g, template_prompt_l_template = split_template_advanced(template['prompt'])
|
|
|
|
text_g_positive = template_prompt_g.replace("{prompt}", positive_prompt_g)
|
|
|
|
text_l_positive = f"{template_prompt_l_template.replace('{prompt}', positive_prompt_g)}, {positive_prompt_l}" if template_prompt_l_template and positive_prompt_l else template_prompt_l_template.replace('{prompt}', positive_prompt_g) or positive_prompt_l
|
|
|
|
if copy_to_l and positive_prompt_g and "{prompt}" not in template_prompt_l_template:
|
|
text_l_positive = deduplicate_merge(text_g_positive, text_l_positive)
|
|
|
|
text_positive = deduplicate_merge(text_g_positive, text_l_positive) if text_l_positive else text_g_positive
|
|
|
|
json_negative_prompt = template.get('negative_prompt', "")
|
|
text_negative = f"{json_negative_prompt}, {negative_prompt}" if json_negative_prompt and negative_prompt else json_negative_prompt or negative_prompt
|
|
|
|
text_g_negative = ""
|
|
if negative_prompt_to in ("Both", "G only"):
|
|
text_g_negative = text_negative
|
|
|
|
text_l_negative = ""
|
|
if negative_prompt_to in ("Both", "L only"):
|
|
text_l_negative = text_negative
|
|
|
|
return text_g_positive, text_l_positive, text_positive, text_g_negative, text_l_negative, text_negative
|
|
|
|
def read_sdxl_templates_replace_and_combine(json_data, template_name, positive_prompt, negative_prompt):
|
|
"""
|
|
Find a specific template by its name, then replace and combine its placeholders with the provided prompts.
|
|
|
|
Args:
|
|
- json_data (list): The list of templates.
|
|
- template_name (str): The name of the desired template.
|
|
- positive_prompt (str): The positive prompt to replace placeholders.
|
|
- negative_prompt (str): The negative prompt to be combined.
|
|
|
|
Returns:
|
|
- tuple: A tuple containing the replaced and combined positive and negative prompts.
|
|
"""
|
|
if not validate_json_data(json_data):
|
|
return positive_prompt, negative_prompt
|
|
|
|
template = find_template_by_name(json_data, template_name)
|
|
|
|
if template:
|
|
return replace_prompts_in_template(template, positive_prompt, negative_prompt)
|
|
else:
|
|
return positive_prompt, negative_prompt
|
|
|
|
def read_sdxl_templates_replace_and_combine_advanced(json_data, template_name, positive_prompt_g, positive_prompt_l, negative_prompt, negative_prompt_to, copy_to_l):
|
|
"""
|
|
Find a specific template by its name, then replace and combine its placeholders with the provided prompts in an advanced manner.
|
|
|
|
Args:
|
|
- json_data (list): The list of templates.
|
|
- template_name (str): The name of the desired template.
|
|
- positive_prompt_g (str): The main positive prompt.
|
|
- positive_prompt_l (str): The auxiliary positive prompt.
|
|
- negative_prompt (str): The negative prompt to be combined.
|
|
- negative_prompt_to (str): The negative prompt destination {Both, G only, L only}.
|
|
- copy_to_l (bool): Copy the G positive prompt to L.
|
|
|
|
Returns:
|
|
- tuple: A tuple containing the replaced and combined main positive, auxiliary positive, combined positive, main negative, auxiliary negative, and negative prompts.
|
|
"""
|
|
if not validate_json_data(json_data):
|
|
return positive_prompt_g, positive_prompt_l, f"{positive_prompt_g} . {positive_prompt_l}", negative_prompt, negative_prompt, negative_prompt
|
|
|
|
template = find_template_by_name(json_data, template_name)
|
|
|
|
if template:
|
|
return replace_prompts_in_template_advanced(template, positive_prompt_g, positive_prompt_l, negative_prompt, negative_prompt_to, copy_to_l)
|
|
else:
|
|
return positive_prompt_g, positive_prompt_l, f"{positive_prompt_g} . {positive_prompt_l}", negative_prompt, negative_prompt, negative_prompt
|
|
|
|
|
|
class SDXLPromptStyler:
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(self):
|
|
current_directory = os.path.dirname(os.path.realpath(__file__))
|
|
self.json_data, styles = load_styles_from_directory(current_directory)
|
|
|
|
return {
|
|
"required": {
|
|
"text_positive": ("STRING", {"default": "", "multiline": True}),
|
|
"text_negative": ("STRING", {"default": "", "multiline": True}),
|
|
"style": ((styles), ),
|
|
"log_prompt": ("BOOLEAN", {"default": True, "label_on": "yes", "label_off": "no"}),
|
|
"style_positive": ("BOOLEAN", {"default": True, "label_on": "yes", "label_off": "no"}),
|
|
"style_negative": ("BOOLEAN", {"default": True, "label_on": "yes", "label_off": "no"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ('STRING','STRING',)
|
|
RETURN_NAMES = ('text_positive','text_negative',)
|
|
FUNCTION = 'prompt_styler'
|
|
CATEGORY = 'utils'
|
|
|
|
def prompt_styler(self, text_positive, text_negative, style, log_prompt, style_positive, style_negative):
|
|
# Process and combine prompts in templates
|
|
# The function replaces the positive prompt placeholder in the template,
|
|
# and combines the negative prompt with the template's negative prompt, if they exist.
|
|
text_positive_styled, text_negative_styled = read_sdxl_templates_replace_and_combine(self.json_data, style, text_positive, text_negative)
|
|
|
|
# If style_negative is disabled, set text_negative_styled to text_negative
|
|
if not style_positive:
|
|
text_positive_styled = text_positive
|
|
if log_prompt:
|
|
print(f"style_positive: disabled")
|
|
|
|
# If style_negative is disabled, set text_negative_styled to text_negative
|
|
if not style_negative:
|
|
text_negative_styled = text_negative
|
|
if log_prompt:
|
|
print(f"style_negative: disabled")
|
|
|
|
# If logging is enabled (log_prompt is set to "Yes"),
|
|
# print the style, positive and negative text, and positive and negative prompts to the console
|
|
if log_prompt:
|
|
print(f"style: {style}")
|
|
print(f"text_positive: {text_positive}")
|
|
print(f"text_negative: {text_negative}")
|
|
print(f"text_positive_styled: {text_positive_styled}")
|
|
print(f"text_negative_styled: {text_negative_styled}")
|
|
|
|
return text_positive_styled, text_negative_styled
|
|
|
|
class SDXLPromptStylerAdvanced:
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(self):
|
|
current_directory = os.path.dirname(os.path.realpath(__file__))
|
|
self.json_data, styles = load_styles_from_directory(current_directory)
|
|
|
|
return {
|
|
"required": {
|
|
"text_positive_g": ("STRING", {"default": "", "multiline": True}),
|
|
"text_positive_l": ("STRING", {"default": "", "multiline": True}),
|
|
"text_negative": ("STRING", {"default": "", "multiline": True}),
|
|
"style": ((styles), ),
|
|
"negative_prompt_to": (["Both", "G only", "L only"], {"default":"Both"}),
|
|
"copy_to_l": ("BOOLEAN", {"default": False, "label_on": "yes", "label_off": "no"}),
|
|
"log_prompt": ("BOOLEAN", {"default": False, "label_on": "yes", "label_off": "no"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ('STRING','STRING','STRING','STRING','STRING','STRING',)
|
|
RETURN_NAMES = ('text_positive_g','text_positive_l','text_positive','text_negative_g','text_negative_l','text_negative',)
|
|
FUNCTION = 'prompt_styler_advanced'
|
|
CATEGORY = 'utils'
|
|
|
|
def prompt_styler_advanced(self, text_positive_g, text_positive_l, text_negative, style, negative_prompt_to, copy_to_l, log_prompt):
|
|
# Process and combine prompts in templates
|
|
# The function replaces the positive prompt placeholder in the template,
|
|
# and combines the negative prompt with the template's negative prompt, if they exist.
|
|
text_positive_g_styled, text_positive_l_styled, text_positive_styled, text_negative_g_styled, text_negative_l_styled, text_negative_styled = read_sdxl_templates_replace_and_combine_advanced(self.json_data, style, text_positive_g, text_positive_l, text_negative, negative_prompt_to, copy_to_l)
|
|
|
|
# If logging is enabled (log_prompt is set to "Yes"),
|
|
# print the style, positive and negative text, and positive and negative prompts to the console
|
|
if log_prompt:
|
|
print(f"style: {style}")
|
|
print(f"text_positive_g: {text_positive_g}")
|
|
print(f"text_positive_l: {text_positive_l}")
|
|
print(f"text_negative: {text_negative}")
|
|
print(f"text_positive_g_styled: {text_positive_g_styled}")
|
|
print(f"text_positive_l_styled: {text_positive_l_styled}")
|
|
print(f"text_positive_styled: {text_positive_styled}")
|
|
print(f"text_negative_g_styled: {text_negative_g_styled}")
|
|
print(f"text_negative_l_styled: {text_negative_l_styled}")
|
|
print(f"text_negative_styled: {text_negative_styled}")
|
|
|
|
return text_positive_g_styled, text_positive_l_styled, text_positive_styled, text_negative_g_styled, text_negative_l_styled, text_negative_styled
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"SDXLPromptStyler": SDXLPromptStyler,
|
|
"SDXLPromptStylerAdvanced": SDXLPromptStylerAdvanced,
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"SDXLPromptStyler": "SDXL Prompt Styler",
|
|
"SDXLPromptStylerAdvanced": "SDXL Prompt Styler Advanced",
|
|
} |