1069 lines
50 KiB
Python
1069 lines
50 KiB
Python
import random
|
||
import re
|
||
import os
|
||
from datetime import datetime, timedelta
|
||
from .weather_utils import generate_weather_description
|
||
from .color_data import COLORS, color_families
|
||
# Palette generation module imports:
|
||
from .make_palette_analogous import generate_analogous_palette_4, generate_analogous_palette_5
|
||
from .make_palette_art_house import generate_art_house_palette_4, generate_art_house_palette_5
|
||
from .make_palette_chaotic import generate_chaotic_palette
|
||
from .make_palette_complementary import generate_complementary_palette, generate_complementary_palette_5
|
||
from .make_palette_double_complementary import generate_double_complementary_palette_4, generate_double_complementary_palette_5
|
||
from .make_palette_modified_compound import generate_modified_compound_palette_five, generate_modified_compound_palette_four_from_five
|
||
from .make_palette_split_complementary import generate_split_complementary_palette_4, generate_split_complementary_palette_5
|
||
from .make_palette_triadic import generate_four_color_palette, generate_triadic_palette_5
|
||
|
||
|
||
# Import weighted option lists for female character describer
|
||
from .eyes_female import WEIGHTED_OPTIONS as EYES_OPTIONS
|
||
from .nose_female import WEIGHTED_OPTIONS as NOSE_OPTIONS
|
||
from .mouth_female import WEIGHTED_OPTIONS as MOUTH_OPTIONS
|
||
from .lips_female import WEIGHTED_OPTIONS as LIPS_OPTIONS
|
||
from .face_shape_female import WEIGHTED_OPTIONS as FACE_SHAPE_OPTIONS
|
||
from .brow_female import WEIGHTED_OPTIONS as BROW_OPTIONS
|
||
from .ears_female import WEIGHTED_OPTIONS as EARS_OPTIONS
|
||
from .cheekbones_female import WEIGHTED_OPTIONS as CHEEKBONES_OPTIONS
|
||
from .cheeks_female import WEIGHTED_OPTIONS as CHEEKS_OPTIONS
|
||
from .chin_female import WEIGHTED_OPTIONS as CHIN_OPTIONS
|
||
from .skin_female import WEIGHTED_OPTIONS as SKIN_OPTIONS
|
||
from .neck_female import WEIGHTED_OPTIONS as NECK_OPTIONS
|
||
from .accessories_female import WEIGHTED_OPTIONS as ACCESSORIES_OPTIONS
|
||
from .expression_female import WEIGHTED_OPTIONS as EXPRESSION_OPTIONS
|
||
from .hair_female import STYLE_OPTIONS, COLOR_OPTIONS
|
||
from .makeup_female import WEIGHTED_OPTIONS as MAKEUP_OPTIONS
|
||
|
||
# Male trait lists aliased so they never collide with the female ones:
|
||
from .eyes_male import WEIGHTED_OPTIONS as MALE_EYES_OPTIONS
|
||
from .nose_male import WEIGHTED_OPTIONS as MALE_NOSE_OPTIONS
|
||
from .mouth_male import WEIGHTED_OPTIONS as MALE_MOUTH_OPTIONS
|
||
from .face_shape_male import WEIGHTED_OPTIONS as MALE_FACE_SHAPE_OPTIONS
|
||
from .brow_male import WEIGHTED_OPTIONS as MALE_BROW_OPTIONS
|
||
from .ears_male import WEIGHTED_OPTIONS as MALE_EARS_OPTIONS
|
||
from .cheeks_cheekbones_male import WEIGHTED_OPTIONS as MALE_CHEEKS_CHEEKBONES_OPTIONS
|
||
from .chin_male import WEIGHTED_OPTIONS as MALE_CHIN_OPTIONS
|
||
from .skin_male import WEIGHTED_OPTIONS as MALE_SKIN_OPTIONS
|
||
from .neck_male import WEIGHTED_OPTIONS as MALE_NECK_OPTIONS
|
||
from .accessories_male import WEIGHTED_OPTIONS as MALE_ACCESSORIES_OPTIONS
|
||
from .facial_hair_male import WEIGHTED_OPTIONS as MALE_FACIAL_HAIR_OPTIONS
|
||
from .expression_male import WEIGHTED_OPTIONS as MALE_EXPRESSION_OPTIONS
|
||
|
||
# Hair has two separate lists, so alias them both:
|
||
from .hair_male import STYLE_OPTIONS as MALE_HAIR_STYLE_OPTIONS, \
|
||
COLOR_OPTIONS as MALE_HAIR_COLOR_OPTIONS
|
||
|
||
def pick_weighted(options, seed):
|
||
random.seed(seed)
|
||
descs, weights = zip(*options)
|
||
return random.choices(descs, weights)[0]
|
||
|
||
class EbuPromptHelperRandomColorPalette:
|
||
"""
|
||
EBU Random Color Palette Generator Node
|
||
Inputs:
|
||
- include_analogous_palettes (BOOLEAN): Include analogous palettes (default True)
|
||
- include_art_house_palettes (BOOLEAN): Include art house palettes (default True)
|
||
- include_chaotic_palettes (BOOLEAN): Include chaotic palettes (default True)
|
||
- include_complementary_palettes (BOOLEAN): Include complementary palettes (default True)
|
||
- include_compound_palettes (BOOLEAN): Include compound palettes (default True)
|
||
- include_split_complementary_palettes (BOOLEAN): Include split complementary palettes (default True)
|
||
- include_tetradic_palettes (BOOLEAN): Include tetradic palettes (default True)
|
||
- include_triadic_palettes (BOOLEAN): Include triadic palettes (default True)
|
||
- palette_size (STRING): Drop‑down selection for palette size. Options:
|
||
"3 colors", "4 colors", or "5 colors".
|
||
(If "3 colors" is selected, a standard 4‑color palette is generated and its first 3 colors are returned.)
|
||
- prefer_color_family (STRING): Drop‑down for a preferred color family.
|
||
Options: "None", "Reds", "Pinks", "Oranges", "Yellows", "Greens", "Blues", "Purples",
|
||
"Browns", "Greys", "Neutrals", "Metallics", "Pastels", "Peaches", "Warm Colors", "Cool Colors".
|
||
(If not "None", the palette is regenerated up to 50 times until the condition is met.)
|
||
- avoid_color_family (STRING): Drop‑down for a color family to avoid.
|
||
Options: "None", "Reds", "Pinks", "Oranges", "Yellows", "Greens", "Blues", "Purples",
|
||
"Browns", "Greys", "Neutrals", "Metallics", "Pastels", "Peaches", "Warm Colors", "Cool Colors".
|
||
(If not "None", the palette is regenerated up to 50 times until the condition is met.)
|
||
- seed (INT): Seed for random generation (0 means non‑deterministic).
|
||
Returns:
|
||
- palette (STRING): A comma‑separated list of color names (always at least 3), in lower-case.
|
||
- color1 (STRING): The first color in the palette (lower-case).
|
||
- color2 (STRING): The second color in the palette (lower-case).
|
||
- color3 (STRING): The third color in the palette (lower-case).
|
||
- color4 (STRING): The fourth color if available, or "" if not.
|
||
- color5 (STRING): The fifth color if available, or "" if not.
|
||
- palette_type (STRING): The chosen palette type.
|
||
- hex_values (STRING): A comma‑separated list of hex values corresponding to each color.
|
||
"""
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {
|
||
"include_analogous_palettes": ("BOOLEAN", {"default": True}),
|
||
"include_art_house_palettes": ("BOOLEAN", {"default": True}),
|
||
"include_chaotic_palettes": ("BOOLEAN", {"default": True}),
|
||
"include_complementary_palettes": ("BOOLEAN", {"default": True}),
|
||
"include_compound_palettes": ("BOOLEAN", {"default": True}),
|
||
"include_split_complementary_palettes": ("BOOLEAN", {"default": True}),
|
||
"include_tetradic_palettes": ("BOOLEAN", {"default": True}),
|
||
"include_triadic_palettes": ("BOOLEAN", {"default": True}),
|
||
"palette_size": (["3 colors", "4 colors", "5 colors"], {"default": "4 colors"}),
|
||
"prefer_color_family": (
|
||
["None", "Reds", "Pinks", "Oranges", "Yellows", "Greens", "Blues", "Purples",
|
||
"Browns", "Greys", "Neutrals", "Metallics", "Pastels", "Peaches", "Warm Colors", "Cool Colors"],
|
||
{"default": "None"}
|
||
),
|
||
"avoid_color_family": (
|
||
["None", "Reds", "Pinks", "Oranges", "Yellows", "Greens", "Blues", "Purples",
|
||
"Browns", "Greys", "Neutrals", "Metallics", "Pastels", "Peaches", "Warm Colors", "Cool Colors"],
|
||
{"default": "None"}
|
||
),
|
||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING")
|
||
RETURN_NAMES = ("palette_string", "color1", "color2", "color3", "color4", "color5", "palette_type", "hex_values")
|
||
FUNCTION = "generate_palette"
|
||
CATEGORY = "Prompts"
|
||
|
||
def generate_palette(
|
||
self,
|
||
include_analogous_palettes,
|
||
include_art_house_palettes,
|
||
include_chaotic_palettes,
|
||
include_complementary_palettes,
|
||
include_compound_palettes,
|
||
include_split_complementary_palettes,
|
||
include_tetradic_palettes,
|
||
include_triadic_palettes,
|
||
palette_size,
|
||
prefer_color_family,
|
||
avoid_color_family,
|
||
seed):
|
||
|
||
# Set the random seed.
|
||
random.seed(seed)
|
||
|
||
# If the same color family is specified for both prefer and avoid, cancel them out.
|
||
if (prefer_color_family != "None" and
|
||
avoid_color_family != "None" and
|
||
prefer_color_family == avoid_color_family):
|
||
print("Warning: The same color family was selected for both prefer and avoid. Cancelling these conditions.")
|
||
prefer_color_family = "None"
|
||
avoid_color_family = "None"
|
||
|
||
# Build a list of enabled palette types.
|
||
enabled_types = []
|
||
if include_analogous_palettes:
|
||
enabled_types.append("analogous")
|
||
if include_art_house_palettes:
|
||
enabled_types.append("art house")
|
||
if include_chaotic_palettes:
|
||
enabled_types.append("chaotic")
|
||
if include_complementary_palettes:
|
||
enabled_types.append("complementary")
|
||
if include_compound_palettes:
|
||
enabled_types.append("compound")
|
||
if include_split_complementary_palettes:
|
||
enabled_types.append("split complementary")
|
||
if include_tetradic_palettes:
|
||
enabled_types.append("tetradic")
|
||
if include_triadic_palettes:
|
||
enabled_types.append("triadic")
|
||
|
||
# If none are enabled, log a warning and default to all palette types.
|
||
if not enabled_types:
|
||
print("Warning: No palette types were enabled. Defaulting to all palette types.")
|
||
enabled_types = [
|
||
"analogous", "art house", "chaotic", "complementary",
|
||
"compound", "split complementary", "tetradic", "triadic"
|
||
]
|
||
|
||
# Determine target palette size (numeric).
|
||
if palette_size == "3 colors":
|
||
target_size = 3
|
||
elif palette_size == "4 colors":
|
||
target_size = 4
|
||
elif palette_size == "5 colors":
|
||
target_size = 5
|
||
else:
|
||
raise Exception("Invalid palette size: " + palette_size)
|
||
|
||
max_attempts = 50
|
||
chosen_palette = None
|
||
chosen_type = None # To record which palette type was chosen.
|
||
|
||
for attempt in range(max_attempts):
|
||
chosen_type = random.choice(enabled_types)
|
||
if chosen_type == "analogous":
|
||
if target_size == 5:
|
||
palette = generate_analogous_palette_5()
|
||
else:
|
||
palette = generate_analogous_palette_4()
|
||
if target_size == 3:
|
||
palette = palette[:3]
|
||
elif chosen_type == "art house":
|
||
if target_size == 5:
|
||
palette = generate_art_house_palette_5()
|
||
else:
|
||
palette = generate_art_house_palette_4()
|
||
if target_size == 3:
|
||
palette = palette[:3]
|
||
elif chosen_type == "chaotic":
|
||
if target_size == 5:
|
||
palette = generate_chaotic_palette(num_colors=5)
|
||
elif target_size == 4:
|
||
palette = generate_chaotic_palette(num_colors=4)
|
||
elif target_size == 3:
|
||
palette = generate_chaotic_palette(num_colors=4)[:3]
|
||
elif chosen_type == "complementary":
|
||
if target_size == 5:
|
||
palette = generate_complementary_palette_5()
|
||
else:
|
||
palette = generate_complementary_palette()
|
||
if target_size == 3:
|
||
palette = palette[:3]
|
||
elif chosen_type == "compound":
|
||
if target_size == 5:
|
||
palette = generate_modified_compound_palette_five()
|
||
else:
|
||
palette = generate_modified_compound_palette_four_from_five()
|
||
if target_size == 3:
|
||
palette = palette[:3]
|
||
elif chosen_type == "split complementary":
|
||
if target_size == 5:
|
||
palette = generate_split_complementary_palette_5()
|
||
else:
|
||
palette = generate_split_complementary_palette_4()
|
||
if target_size == 3:
|
||
palette = palette[:3]
|
||
elif chosen_type == "tetradic":
|
||
if target_size == 5:
|
||
palette = generate_double_complementary_palette_5()
|
||
else:
|
||
palette = generate_double_complementary_palette_4()
|
||
if target_size == 3:
|
||
palette = palette[:3]
|
||
elif chosen_type == "triadic":
|
||
if target_size == 5:
|
||
palette = generate_triadic_palette_5()
|
||
else:
|
||
palette = generate_four_color_palette()
|
||
if target_size == 3:
|
||
palette = palette[:3]
|
||
else:
|
||
raise Exception("Unknown palette type chosen: " + chosen_type)
|
||
|
||
# --- Check the "prefer" condition ---
|
||
condition_preferred = True
|
||
if prefer_color_family != "None":
|
||
if prefer_color_family in ["Warm Colors", "Cool Colors"]:
|
||
warm_list = color_families.get("Warm Colors", [])
|
||
cool_list = color_families.get("Cool Colors", [])
|
||
warm_count = sum(1 for color in palette if color in warm_list)
|
||
cool_count = sum(1 for color in palette if color in cool_list)
|
||
if prefer_color_family == "Warm Colors":
|
||
condition_preferred = (warm_count >= cool_count + 1)
|
||
else:
|
||
condition_preferred = (cool_count >= warm_count + 1)
|
||
else:
|
||
preferred_list = color_families.get(prefer_color_family, [])
|
||
condition_preferred = any(color in preferred_list for color in palette)
|
||
|
||
# --- Check the "avoid" condition ---
|
||
condition_avoid = True
|
||
if avoid_color_family != "None":
|
||
if avoid_color_family in ["Warm Colors", "Cool Colors"]:
|
||
warm_list = color_families.get("Warm Colors", [])
|
||
cool_list = color_families.get("Cool Colors", [])
|
||
warm_count = sum(1 for color in palette if color in warm_list)
|
||
cool_count = sum(1 for color in palette if color in cool_list)
|
||
if avoid_color_family == "Warm Colors":
|
||
condition_avoid = (warm_count <= cool_count - 1)
|
||
else:
|
||
condition_avoid = (cool_count <= warm_count - 1)
|
||
else:
|
||
avoid_list = color_families.get(avoid_color_family, [])
|
||
condition_avoid = not any(color in avoid_list for color in palette)
|
||
|
||
if condition_preferred and condition_avoid:
|
||
chosen_palette = palette
|
||
break
|
||
|
||
if chosen_palette is None:
|
||
print(f"Warning: Attempted {max_attempts} times to meet color preferences but wasn't able to.")
|
||
chosen_palette = palette
|
||
|
||
display_palette = [color.lower() for color in chosen_palette]
|
||
palette_str = ", ".join(display_palette)
|
||
padded_display = display_palette.copy()
|
||
while len(padded_display) < 5:
|
||
padded_display.append("")
|
||
color1, color2, color3, color4, color5 = padded_display[:5]
|
||
hex_list = [COLORS.get(color, "") for color in chosen_palette]
|
||
hex_values_str = ", ".join(hex_list)
|
||
|
||
return (palette_str, color1, color2, color3, color4, color5, chosen_type, hex_values_str)
|
||
|
||
class EbuPromptHelperReplace:
|
||
"""
|
||
EBU Prompt Helper Replace Node
|
||
|
||
This node replaces occurrences of one or more target words within an input prompt with a specified replacement string.
|
||
The target words should be provided as a single string, where multiple words are separated by the '|' character.
|
||
For example, if the `word_to_replace` input is "person|man|woman", then any occurrence of "person", "man", or "woman"
|
||
in the prompt will be replaced with the value provided in `replace_with`.
|
||
|
||
Inputs:
|
||
- prompt_text (STRING): A multiline string representing the input prompt.
|
||
- word_to_replace (STRING): A single-line string containing one or more words separated by '|'.
|
||
- replace_with (STRING): A multiline string that will replace each occurrence of the target words.
|
||
- case_sensitive (BOOLEAN): A toggle to control if the replacement is case‑sensitive (default True).
|
||
"""
|
||
def __init__(self):
|
||
pass
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {
|
||
"required": {
|
||
"prompt_text": ("STRING", {"multiline": True, "default": ""}),
|
||
"word_to_replace": ("STRING", {"multiline": False, "default": ""}),
|
||
"replace_with": ("STRING", {"multiline": True, "default": ""}),
|
||
"case_sensitive": ("BOOLEAN", {"default": True}),
|
||
},
|
||
}
|
||
|
||
RETURN_TYPES = ("STRING",)
|
||
RETURN_NAMES = ("updated_prompt_text",)
|
||
FUNCTION = "replace_text"
|
||
CATEGORY = "Prompts"
|
||
|
||
def replace_text(self, prompt_text, word_to_replace, replace_with, case_sensitive):
|
||
if not word_to_replace.strip():
|
||
return (prompt_text,)
|
||
words = word_to_replace.split("|")
|
||
revised = prompt_text
|
||
if case_sensitive:
|
||
for word in words:
|
||
revised = revised.replace(word, replace_with)
|
||
else:
|
||
import re
|
||
for word in words:
|
||
pattern = re.compile(re.escape(word), re.IGNORECASE)
|
||
revised = pattern.sub(replace_with, revised)
|
||
return (revised,)
|
||
|
||
class EbuPromptHelperRandomize:
|
||
"""
|
||
EBU PromptHelper Randomize Node
|
||
|
||
This node takes an input prompt and replaces occurrences of a specified target substring
|
||
(word_to_replace) with a randomly selected option from a provided string of options.
|
||
Weighted options use the syntax N>>option; an option with weight N is selected
|
||
N times as often as an unweighted option.
|
||
The options string is split based on the chosen delimiter, which can be newlines, commas, or semi‑colons.
|
||
The seed input controls the random selection, allowing for deterministic output if desired.
|
||
|
||
Inputs:
|
||
- prompt_text (STRING): A multiline string representing the input prompt.
|
||
- word_to_replace (STRING): A single-line string indicating the substring to replace.
|
||
- replacement_options (STRING): A string containing replacement options.
|
||
- seed (INT): Ensures that node runs every time
|
||
- case_sensitive (BOOLEAN): A toggle to control if the replacement is case‑sensitive (default True).
|
||
- delimit_options_with (STRING): Dropdown with options ["newlines", "commas", "semi-colons"]
|
||
that determines how to split the options string.
|
||
|
||
Returns:
|
||
- updated_prompt_text (STRING): The prompt after replacement.
|
||
- word_selected (STRING): The randomly selected replacement option.
|
||
"""
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {
|
||
"prompt_text": ("STRING", {"multiline": True, "default": ""}),
|
||
"word_to_replace": ("STRING", {"multiline": False, "default": ""}),
|
||
"replacement_options": ("STRING", {"multiline": True, "default": ""}),
|
||
"seed": ("INT", {"default": 0, "step": 1, "min": 0, "max": 0xffffffffffffffff, "display": "number"}),
|
||
"case_sensitive": ("BOOLEAN", {"default": True}),
|
||
"delimit_options_with": (["newlines", "commas", "semi-colons"], {"default": "newlines"})
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("STRING", "STRING")
|
||
RETURN_NAMES = ("updated_prompt_text", "word_selected")
|
||
FUNCTION = "randomize_text"
|
||
CATEGORY = "Prompts"
|
||
|
||
def randomize_text(self, prompt_text, word_to_replace, replacement_options, seed, case_sensitive, delimit_options_with):
|
||
import re
|
||
import random
|
||
random.seed(seed)
|
||
|
||
# Split the options string based on the selected delimiter.
|
||
if delimit_options_with == "newlines":
|
||
options = [opt.strip() for opt in replacement_options.splitlines() if opt.strip()]
|
||
elif delimit_options_with == "commas":
|
||
options = [opt.strip() for opt in replacement_options.split(',') if opt.strip()]
|
||
elif delimit_options_with == "semi-colons":
|
||
options = [opt.strip() for opt in replacement_options.split(';') if opt.strip()]
|
||
else:
|
||
options = [opt.strip() for opt in replacement_options.splitlines() if opt.strip()]
|
||
|
||
if not options:
|
||
return (prompt_text, "")
|
||
|
||
# Expand weighted options. For example, 3>>red contributes three
|
||
# red tickets while an unprefixed option contributes one.
|
||
expanded_options = []
|
||
weighted_pattern = re.compile(r'^\s*(\d+)\s*>>(.*)$')
|
||
for option in options:
|
||
match = weighted_pattern.match(option)
|
||
if match:
|
||
weight = int(match.group(1))
|
||
option_text = match.group(2).strip()
|
||
if weight > 0 and option_text:
|
||
expanded_options.extend([option_text] * weight)
|
||
else:
|
||
expanded_options.append(option)
|
||
|
||
if not expanded_options:
|
||
return (prompt_text, "")
|
||
|
||
selected = random.choice(expanded_options)
|
||
|
||
# Handle multiple words using "|"
|
||
words_to_replace = word_to_replace.split("|")
|
||
|
||
if case_sensitive:
|
||
for word in words_to_replace:
|
||
prompt_text = prompt_text.replace(word, selected)
|
||
else:
|
||
import re
|
||
for word in words_to_replace:
|
||
pattern = re.compile(re.escape(word), re.IGNORECASE)
|
||
prompt_text = pattern.sub(selected, prompt_text)
|
||
|
||
return (prompt_text, selected)
|
||
|
||
class EbuPromptHelperCombineTwoStrings:
|
||
"""
|
||
EBU PromptHelper Combine Two Strings Node
|
||
|
||
This node takes two input strings and combines them into one string using a provided join string.
|
||
Only non-empty input strings are included in the final result. For example, if one of the inputs is
|
||
empty, only the non-empty string is returned.
|
||
|
||
Inputs:
|
||
- str1 (STRING): The first string to combine. (Multiline supported.)
|
||
- str2 (STRING): The second string to combine. (Multiline supported.)
|
||
- join_str (STRING): The string used to join the two input strings. Defaults to "\n\n".
|
||
|
||
Returns:
|
||
- combined (STRING): The resulting combined string.
|
||
"""
|
||
def __init__(self):
|
||
pass
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {
|
||
"required": {
|
||
"str1": ("STRING", {
|
||
"multiline": True,
|
||
"default": ""
|
||
}),
|
||
"str2": ("STRING", {
|
||
"multiline": True,
|
||
"default": ""
|
||
}),
|
||
"join_str": ("STRING", {
|
||
"multiline": True,
|
||
"default": "\n\n"
|
||
}),
|
||
},
|
||
}
|
||
|
||
RETURN_TYPES = ("STRING",)
|
||
RETURN_NAMES = ("combined",)
|
||
|
||
FUNCTION = "combine_two_strings"
|
||
|
||
CATEGORY = "Prompts"
|
||
|
||
def combine_two_strings(self, str1, str2, join_str):
|
||
strs = []
|
||
if str1:
|
||
strs.append(str1)
|
||
if str2:
|
||
strs.append(str2)
|
||
combined = join_str.join(strs)
|
||
return (combined,)
|
||
|
||
class EbuPromptHelperListSampler:
|
||
"""
|
||
EBU PromptHelper List Sampler Node
|
||
|
||
This node takes a multi-line string (representing a list) as input, processes each line
|
||
by removing any leading numbers (e.g., "1. ", "23. ") and trimming whitespace, and then
|
||
randomly shuffles the remaining non-empty lines. It returns a sample containing a specified
|
||
number of lines (or all lines if there are fewer than requested). If the toggle is enabled,
|
||
the sampled list will be numbered starting at 1.
|
||
|
||
Inputs:
|
||
- list (STRING): A multi-line string containing the list items. Each line may optionally
|
||
start with a number and a dot.
|
||
- seed (INT): A seed for the random number generator. Use 0 for non-deterministic output.
|
||
- number_of_elements (INT, optional): The maximum number of list elements to sample. Defaults to 10.
|
||
- number_sampled_list (BOOLEAN, optional): If True, the sampled list will be numbered (default False).
|
||
|
||
Returns:
|
||
- (STRING): A newline-separated string of the sampled list items.
|
||
"""
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {
|
||
"list": ("STRING", {"multiline": True}),
|
||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||
},
|
||
"optional": {
|
||
"number_of_elements": ("INT", {"default": 10, "min": 1, "max": 1000}),
|
||
"number_sampled_list": ("BOOLEAN", {"default": False}),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("STRING",)
|
||
FUNCTION = "sample_list"
|
||
CATEGORY = "Prompts"
|
||
|
||
def sample_list(self, list, seed, number_of_elements=10, number_sampled_list=False):
|
||
# Split the input string into lines.
|
||
lines = list.split('\n')
|
||
|
||
# Process lines: remove leading numbers and ignore empty or whitespace-only lines.
|
||
processed_lines = []
|
||
for line in lines:
|
||
# Remove leading numbers followed by a dot (e.g., "1. ", "23. ").
|
||
processed_line = re.sub(r'^\d+\.\s*', '', line)
|
||
processed_line = processed_line.strip()
|
||
if processed_line: # Only add non-empty lines.
|
||
processed_lines.append(processed_line)
|
||
|
||
# Set the random seed.
|
||
random.seed(seed)
|
||
|
||
# Shuffle the processed lines.
|
||
random.shuffle(processed_lines)
|
||
|
||
# Select the specified number of elements, or all available if there are fewer.
|
||
selected_lines = processed_lines[:min(number_of_elements, len(processed_lines))]
|
||
|
||
# If numbering is enabled, number each line starting at 1.
|
||
if number_sampled_list:
|
||
numbered_lines = []
|
||
for idx, line in enumerate(selected_lines, start=1):
|
||
numbered_lines.append(f"{idx}. {line}")
|
||
selected_lines = numbered_lines
|
||
|
||
# Join the selected lines into a single string separated by newlines.
|
||
output = '\n'.join(selected_lines)
|
||
|
||
return (output,)
|
||
|
||
class EbuPromptHelperLoadFileAsString:
|
||
"""
|
||
EBU PromptHelper Load File as String Node
|
||
|
||
This node loads the contents of a file as a string. Provide the directory and file name, and the node
|
||
will attempt to read the file using UTF-8 encoding. If an error occurs (e.g., if the file is not found),
|
||
the node will print an error message and return an empty string.
|
||
|
||
Inputs:
|
||
- directory (STRING): The directory path where the file is located.
|
||
- file_name (STRING): The name of the file to load.
|
||
|
||
Returns:
|
||
- (STRING): The content of the file as a string, or an empty string if an error occurred.
|
||
"""
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {
|
||
"required": {
|
||
"directory": ("STRING", {"default": ""}),
|
||
"file_name": ("STRING", {"default": ""}),
|
||
},
|
||
}
|
||
|
||
RETURN_TYPES = ("STRING",)
|
||
FUNCTION = "load_file"
|
||
CATEGORY = "File Operations"
|
||
|
||
def load_file(self, directory, file_name):
|
||
full_path = os.path.join(directory, file_name)
|
||
try:
|
||
with open(full_path, 'r', encoding='utf-8') as file:
|
||
content = file.read()
|
||
return (content,)
|
||
except Exception as e:
|
||
print(f"Error reading file: {e}")
|
||
return ("",)
|
||
|
||
class EbuPromptHelperCurrentDateTime:
|
||
"""
|
||
EBU PromptHelper Current DateTime Node
|
||
|
||
This node returns the current datetime (at the moment of execution) in several formats:
|
||
|
||
- date: "February 10, 2025"
|
||
- time: "1:15pm"
|
||
- datetime: "2025-02-10 1:15:33pm"
|
||
- datetime_for_filename: "2025-02-10_13-15-33"
|
||
|
||
No input parameters are required.
|
||
"""
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
# No inputs required.
|
||
return {"required": {}}
|
||
|
||
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING")
|
||
RETURN_NAMES = ("date", "time", "datetime", "datetime_for_filename")
|
||
FUNCTION = "generate_datetime"
|
||
CATEGORY = "Utility"
|
||
|
||
def generate_datetime(self):
|
||
# Get the current datetime.
|
||
dt = datetime.now()
|
||
|
||
# Format the date as "February 10, 2025" (with the day inserted without a leading zero).
|
||
date_str = dt.strftime("%B {day}, %Y").format(day=dt.day)
|
||
|
||
# Format the time as "1:15pm" (12-hour format with any leading zero removed and lowercase am/pm).
|
||
time_str = dt.strftime("%I:%M%p").lstrip("0").lower()
|
||
|
||
# Format the full datetime as "2025-02-10 1:15:33pm".
|
||
datetime_str = dt.strftime("%Y-%m-%d ") + dt.strftime("%I:%M:%S%p").lstrip("0").lower()
|
||
|
||
# Format the datetime for filenames as "2025-02-10_13-15-33".
|
||
datetime_filename_str = dt.strftime("%Y-%m-%d_%H-%M-%S")
|
||
|
||
return (date_str, time_str, datetime_str, datetime_filename_str)
|
||
|
||
class EbuPromptHelperConsumeListItem:
|
||
"""
|
||
EBU PromptHelper Consume List Item Node
|
||
|
||
This node takes an optional prompt, a target substring to replace, and a list of options provided
|
||
as a newline-separated string. It randomly selects one option from the list (using the provided seed
|
||
for reproducibility), removes that option from the list, and replaces all occurrences of the target
|
||
substring in the prompt with the selected option. The revised list (with the selected option removed)
|
||
is also returned.
|
||
|
||
Inputs:
|
||
- word_to_replace (STRING): The substring in the prompt to be replaced.
|
||
- list (STRING): A newline-separated list of options.
|
||
- seed (INT): A seed for the random number generator (0 means non-deterministic).
|
||
- prompt_text (STRING, optional): A multiline string representing the input prompt. Defaults to an empty string.
|
||
|
||
Returns:
|
||
- updated_prompt_text (STRING): The prompt with the target substring replaced by the selected option.
|
||
- word_selected (STRING): The randomly selected option from the list.
|
||
- revised_list (STRING): The remaining options (after the selected one is removed) joined by a single newline
|
||
between each option, with no extra newline at the beginning.
|
||
"""
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {
|
||
"word_to_replace": ("STRING", {}),
|
||
"list": ("STRING", {"multiline": True, "default": ""}),
|
||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||
},
|
||
"optional": {
|
||
"prompt_text": ("STRING", {"multiline": True, "default": ""})
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("STRING", "STRING", "STRING")
|
||
RETURN_NAMES = ("updated_prompt_text", "word_selected", "revised_list")
|
||
FUNCTION = "consume_list_item"
|
||
CATEGORY = "Prompts"
|
||
|
||
def consume_list_item(self, word_to_replace, list, seed, prompt_text=""):
|
||
# Split the input string into a list of options using newlines.
|
||
options = [opt.strip() for opt in list.splitlines() if opt.strip()]
|
||
|
||
# Set the random seed.
|
||
random.seed(seed)
|
||
|
||
if options:
|
||
# Randomly select an option.
|
||
word_selected = random.choice(options)
|
||
|
||
# Remove the selected option from the list.
|
||
options.remove(word_selected)
|
||
|
||
# Join the remaining options with a single newline between them.
|
||
revised_list = '\n'.join(options)
|
||
# Ensure there is no extra newline at the beginning.
|
||
revised_list = revised_list.lstrip('\n')
|
||
|
||
# Replace all occurrences of the target substring in the prompt with the selected option.
|
||
updated_prompt_text = prompt_text.replace(word_to_replace, word_selected)
|
||
return (updated_prompt_text, word_selected, revised_list)
|
||
else:
|
||
# If no options are provided, return the prompt (or empty string) and empty values for the other outputs.
|
||
return (prompt_text, "", "")
|
||
|
||
import random
|
||
from datetime import datetime, timedelta
|
||
|
||
class EbuPromptHelperSeasonWeatherTimeOfDay:
|
||
"""
|
||
EBU PromptHelper Season Weather Time-Of-Day Node
|
||
|
||
This node generates a random abstract description of a datetime within a specified year and time range,
|
||
and then produces a weather description based on that abstract datetime.
|
||
"""
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {
|
||
"required": {
|
||
"year_from": ("INT", {"default": 1980, "step": 1, "display": "number"}),
|
||
"year_to": ("INT", {"default": 2025, "step": 1, "display": "number"}),
|
||
"time_from": ("STRING", {"multiline": False, "default": "5:00am"}),
|
||
"time_to": ("STRING", {"multiline": False, "default": "9:00pm"}),
|
||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||
# Added latest and earliest of three to year_skew only
|
||
"year_skew": (["no skew", "earlier of two", "later of two", "middle of three", "earliest of three", "latest of three"], {"default": "no skew"}),
|
||
# time_of_day_skew remains unchanged
|
||
"time_of_day_skew": (["no skew", "earlier of two", "later of two", "middle of three"], {"default": "no skew"}),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("STRING", "STRING", "STRING")
|
||
RETURN_NAMES = ("when", "when_no_year", "weather")
|
||
FUNCTION = "generate_info"
|
||
CATEGORY = "Prompts"
|
||
|
||
def generate_info(self, year_from, year_to, time_from, time_to, seed, year_skew, time_of_day_skew):
|
||
random.seed(seed)
|
||
when = self.generate_random_datetime(year_from, year_to, time_from, time_to, year_skew, time_of_day_skew)
|
||
# Assuming generate_weather_description is defined globally or imported
|
||
weather = generate_weather_description(when)
|
||
when_no_year = when.rsplit(" of ", 1)[0]
|
||
return (when, when_no_year, weather)
|
||
|
||
def generate_random_datetime(self, year_from, year_to, time_from, time_to, year_skew, time_of_day_skew):
|
||
def time_str_to_dt(time_str):
|
||
return datetime.strptime(time_str.strip().lower(), '%I:%M%p')
|
||
|
||
def generate_skewed_random(min_val, max_val, skew):
|
||
if skew == "no skew":
|
||
return random.randint(min_val, max_val)
|
||
|
||
count = 3 if "three" in skew else 2
|
||
vals = [random.randint(min_val, max_val) for _ in range(count)]
|
||
|
||
if "earlier" in skew or "earliest" in skew:
|
||
return min(vals)
|
||
elif "later" in skew or "latest" in skew:
|
||
return max(vals)
|
||
elif "middle" in skew:
|
||
return sorted(vals)[1]
|
||
return random.randint(min_val, max_val)
|
||
|
||
def get_time_of_day(hour):
|
||
if 5 <= hour < 8: return "early morning"
|
||
if 8 <= hour < 10: return "morning"
|
||
if 10 <= hour < 12: return "late morning"
|
||
if 12 <= hour < 14: return "early afternoon"
|
||
if 14 <= hour < 16: return "afternoon"
|
||
if 16 <= hour < 18: return "late afternoon"
|
||
if 18 <= hour < 20: return "early evening"
|
||
if 20 <= hour < 22: return "evening"
|
||
if 22 <= hour or hour < 3: return "night"
|
||
if 3 <= hour < 4: return "witching hour"
|
||
if 4 <= hour < 5: return "pre-dawn"
|
||
return "unknown time"
|
||
|
||
def get_season_part(month):
|
||
# Dictionary lookup for cleaner seasonal mapping
|
||
seasons = {
|
||
12: "early winter", 1: "winter", 2: "late winter",
|
||
3: "early spring", 4: "middle of the spring", 5: "late spring",
|
||
6: "early summer", 7: "middle of the summer", 8: "late summer",
|
||
9: "early fall", 10: "middle of the fall", 11: "late fall"
|
||
}
|
||
return seasons.get(month, "unknown season")
|
||
|
||
# Handle overnight logic: if time_to is before time_from, it spans across midnight
|
||
base_date = datetime(2000, 1, 1)
|
||
time_from_dt = datetime.combine(base_date.date(), time_str_to_dt(time_from).time())
|
||
time_to_dt = datetime.combine(base_date.date(), time_str_to_dt(time_to).time())
|
||
|
||
if time_to_dt <= time_from_dt:
|
||
time_to_dt += timedelta(days=1)
|
||
|
||
# Calculate random time within range
|
||
total_seconds = int((time_to_dt - time_from_dt).total_seconds())
|
||
sec_offset = generate_skewed_random(0, total_seconds, time_of_day_skew)
|
||
random_time_dt = time_from_dt + timedelta(seconds=sec_offset)
|
||
|
||
# Calculate random date
|
||
start_date = datetime(year_from, 1, 1)
|
||
end_date = datetime(year_to + 1, 1, 1)
|
||
delta_days = (end_date - start_date).days
|
||
day_offset = generate_skewed_random(0, delta_days - 1, year_skew)
|
||
random_date_obj = start_date + timedelta(days=day_offset)
|
||
|
||
tod_str = get_time_of_day(random_time_dt.hour)
|
||
season_str = get_season_part(random_date_obj.month)
|
||
|
||
return f"{tod_str} during {season_str} of {random_date_obj.year}"
|
||
|
||
class EbuPromptHelperTruncate:
|
||
"""
|
||
EBU PromptHelper Truncate Node
|
||
|
||
This node takes a prompt string and a target substring. It removes everything either before or after the first occurrence of the substring.
|
||
The deletion can be inclusive (removing the substring as well) or exclusive (keeping the substring).
|
||
|
||
Inputs:
|
||
- prompt (STRING): The input prompt.
|
||
- substring (STRING): The target substring to search for.
|
||
- delete_option (STRING): Dropdown with options "delete before" or "delete after".
|
||
- inclusive (BOOLEAN): If True, the substring is removed along with the preceding or following text.
|
||
|
||
Returns:
|
||
- modified_prompt (STRING): The prompt after truncation.
|
||
"""
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {
|
||
"required": {
|
||
"prompt": ("STRING", {"multiline": True, "default": ""}),
|
||
"substring": ("STRING", {"multiline": False, "default": ""}),
|
||
"delete_option": (["delete before", "delete after"], {"default": "delete before"}),
|
||
"inclusive": ("BOOLEAN", {"default": True}),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("STRING",)
|
||
RETURN_NAMES = ("modified_prompt",)
|
||
FUNCTION = "truncate_prompt"
|
||
CATEGORY = "Prompts"
|
||
|
||
def truncate_prompt(self, prompt, substring, delete_option, inclusive):
|
||
index = prompt.find(substring)
|
||
if index == -1:
|
||
# If the substring is not found, return the original prompt.
|
||
return (prompt,)
|
||
if delete_option == "delete before":
|
||
if inclusive:
|
||
# Delete everything before and including the substring.
|
||
return (prompt[index + len(substring):],)
|
||
else:
|
||
# Delete everything before but keep the substring.
|
||
return (prompt[index:],)
|
||
elif delete_option == "delete after":
|
||
if inclusive:
|
||
# Delete everything after and including the substring.
|
||
return (prompt[:index],)
|
||
else:
|
||
# Delete everything after but keep the substring.
|
||
return (prompt[:index + len(substring)],)
|
||
else:
|
||
return (prompt,)
|
||
|
||
class EbuPromptHelperCharacterDescriberFemale:
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {
|
||
"required": {
|
||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||
"eyes_enabled": ("BOOLEAN", {"default": True}),
|
||
"nose_enabled": ("BOOLEAN", {"default": True}),
|
||
"mouth_enabled": ("BOOLEAN", {"default": True}),
|
||
"lips_enabled": ("BOOLEAN", {"default": True}),
|
||
"face_shape_enabled": ("BOOLEAN", {"default": True}),
|
||
"brow_enabled": ("BOOLEAN", {"default": True}),
|
||
"ears_enabled": ("BOOLEAN", {"default": True}),
|
||
"cheekbones_enabled": ("BOOLEAN", {"default": True}),
|
||
"cheeks_enabled": ("BOOLEAN", {"default": True}),
|
||
"chin_enabled": ("BOOLEAN", {"default": True}),
|
||
"skin_enabled": ("BOOLEAN", {"default": True}),
|
||
"makeup_enabled": ("BOOLEAN", {"default": False}),
|
||
"neck_enabled": ("BOOLEAN", {"default": False}),
|
||
"accessories_enabled": ("BOOLEAN", {"default": False}),
|
||
}
|
||
}
|
||
|
||
# Now returning 4 strings instead of 3
|
||
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING")
|
||
RETURN_NAMES = ("face_description", "hair_style", "hair_color", "facial_expression")
|
||
FUNCTION = "generate"
|
||
CATEGORY = "Prompts"
|
||
|
||
@staticmethod
|
||
def generate(seed,
|
||
eyes_enabled, nose_enabled, mouth_enabled, lips_enabled,
|
||
face_shape_enabled, brow_enabled, ears_enabled,
|
||
cheekbones_enabled, cheeks_enabled, chin_enabled,
|
||
skin_enabled, makeup_enabled, neck_enabled, accessories_enabled):
|
||
|
||
lines = []
|
||
if eyes_enabled:
|
||
lines.append(f"Eyes: {pick_weighted(EYES_OPTIONS, seed + 1)}")
|
||
if nose_enabled:
|
||
lines.append(f"Nose: {pick_weighted(NOSE_OPTIONS, seed + 2)}")
|
||
if mouth_enabled:
|
||
lines.append(f"Mouth: {pick_weighted(MOUTH_OPTIONS, seed + 3)}")
|
||
if lips_enabled:
|
||
lines.append(f"Lips: {pick_weighted(LIPS_OPTIONS, seed + 4)}")
|
||
if face_shape_enabled:
|
||
lines.append(f"Face Shape: {pick_weighted(FACE_SHAPE_OPTIONS, seed + 5)}")
|
||
if brow_enabled:
|
||
lines.append(f"Eyebrows & Forehead: {pick_weighted(BROW_OPTIONS, seed + 6)}")
|
||
if ears_enabled:
|
||
lines.append(f"Ears: {pick_weighted(EARS_OPTIONS, seed + 7)}")
|
||
if cheekbones_enabled:
|
||
lines.append(f"Cheekbones: {pick_weighted(CHEEKBONES_OPTIONS, seed + 8)}")
|
||
if cheeks_enabled:
|
||
lines.append(f"Cheeks: {pick_weighted(CHEEKS_OPTIONS, seed + 9)}")
|
||
if chin_enabled:
|
||
lines.append(f"Chin/Jaw: {pick_weighted(CHIN_OPTIONS, seed + 10)}")
|
||
if skin_enabled:
|
||
lines.append(f"Skin: {pick_weighted(SKIN_OPTIONS, seed + 11)}")
|
||
if makeup_enabled:
|
||
lines.append(f"Makeup: {pick_weighted(MAKEUP_OPTIONS, seed + 12)}")
|
||
if neck_enabled:
|
||
lines.append(f"Neck: {pick_weighted(NECK_OPTIONS, seed + 13)}")
|
||
if accessories_enabled:
|
||
lines.append(f"Accessories: {pick_weighted(ACCESSORIES_OPTIONS, seed + 14)}")
|
||
|
||
face_description = "\n".join(lines)
|
||
hair_style = pick_weighted(STYLE_OPTIONS, seed + 15)
|
||
hair_color = pick_weighted(COLOR_OPTIONS, seed + 16)
|
||
facial_expression = pick_weighted(EXPRESSION_OPTIONS, seed + 17)
|
||
|
||
return face_description, hair_style, hair_color, facial_expression
|
||
|
||
# Alias all male option lists to avoid collisions
|
||
from .eyes_male import WEIGHTED_OPTIONS as MALE_EYES_OPTIONS
|
||
from .nose_male import WEIGHTED_OPTIONS as MALE_NOSE_OPTIONS
|
||
from .mouth_male import WEIGHTED_OPTIONS as MALE_MOUTH_OPTIONS
|
||
from .face_shape_male import WEIGHTED_OPTIONS as MALE_FACE_SHAPE_OPTIONS
|
||
from .brow_male import WEIGHTED_OPTIONS as MALE_BROW_OPTIONS
|
||
from .ears_male import WEIGHTED_OPTIONS as MALE_EARS_OPTIONS
|
||
from .cheeks_cheekbones_male import WEIGHTED_OPTIONS as MALE_CHEEKS_CHEEKBONES_OPTIONS
|
||
from .chin_male import WEIGHTED_OPTIONS as MALE_CHIN_OPTIONS
|
||
from .skin_male import WEIGHTED_OPTIONS as MALE_SKIN_OPTIONS
|
||
from .neck_male import WEIGHTED_OPTIONS as MALE_NECK_OPTIONS
|
||
from .accessories_male import WEIGHTED_OPTIONS as MALE_ACCESSORIES_OPTIONS
|
||
from .facial_hair_male import WEIGHTED_OPTIONS as MALE_FACIAL_HAIR_OPTIONS
|
||
from .expression_male import WEIGHTED_OPTIONS as MALE_EXPRESSION_OPTIONS
|
||
from .hair_male import STYLE_OPTIONS as MALE_HAIR_STYLE_OPTIONS, \
|
||
COLOR_OPTIONS as MALE_HAIR_COLOR_OPTIONS
|
||
|
||
class EbuPromptHelperCharacterDescriberMale:
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {
|
||
"required": {
|
||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||
"eyes_enabled": ("BOOLEAN", {"default": True}),
|
||
"nose_enabled": ("BOOLEAN", {"default": True}),
|
||
"mouth_enabled": ("BOOLEAN", {"default": True}),
|
||
"face_shape_enabled": ("BOOLEAN", {"default": True}),
|
||
"brow_enabled": ("BOOLEAN", {"default": True}),
|
||
"ears_enabled": ("BOOLEAN", {"default": True}),
|
||
"cheeks_and_cheekbones_enabled":("BOOLEAN", {"default": False}),
|
||
"cheekbones_enabled": ("BOOLEAN", {"default": True}),
|
||
"chin_enabled": ("BOOLEAN", {"default": True}),
|
||
"skin_enabled": ("BOOLEAN", {"default": True}),
|
||
"neck_enabled": ("BOOLEAN", {"default": False}),
|
||
"accessories_enabled": ("BOOLEAN", {"default": False}),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING")
|
||
RETURN_NAMES = ("face_description", "hair_style", "hair_color", "facial_hair", "facial_expression")
|
||
FUNCTION = "generate"
|
||
CATEGORY = "Prompts"
|
||
|
||
@staticmethod
|
||
def generate(seed,
|
||
eyes_enabled, nose_enabled, mouth_enabled,
|
||
face_shape_enabled, brow_enabled, ears_enabled,
|
||
cheeks_and_cheekbones_enabled, cheekbones_enabled,
|
||
chin_enabled, skin_enabled,
|
||
neck_enabled, accessories_enabled):
|
||
|
||
lines = []
|
||
if eyes_enabled:
|
||
lines.append(f"Eyes: {pick_weighted(MALE_EYES_OPTIONS, seed+1)}")
|
||
if nose_enabled:
|
||
lines.append(f"Nose: {pick_weighted(MALE_NOSE_OPTIONS, seed+2)}")
|
||
if mouth_enabled:
|
||
lines.append(f"Mouth: {pick_weighted(MALE_MOUTH_OPTIONS, seed+3)}")
|
||
if face_shape_enabled:
|
||
lines.append(f"Face Shape: {pick_weighted(MALE_FACE_SHAPE_OPTIONS, seed+4)}")
|
||
if brow_enabled:
|
||
lines.append(f"Eyebrows & Forehead: {pick_weighted(MALE_BROW_OPTIONS, seed+5)}")
|
||
if ears_enabled:
|
||
lines.append(f"Ears: {pick_weighted(MALE_EARS_OPTIONS, seed+6)}")
|
||
|
||
# Combined cheeks & cheekbones
|
||
if cheeks_and_cheekbones_enabled:
|
||
lines.append(f"Cheeks & Cheekbones: {pick_weighted(MALE_CHEEKS_CHEEKBONES_OPTIONS, seed+7)}")
|
||
elif cheekbones_enabled:
|
||
lines.append(f"Cheekbones: {pick_weighted(MALE_CHEEKS_CHEEKBONES_OPTIONS, seed+7)}")
|
||
|
||
if chin_enabled:
|
||
lines.append(f"Chin/Jaw: {pick_weighted(MALE_CHIN_OPTIONS, seed+8)}")
|
||
if skin_enabled:
|
||
lines.append(f"Skin: {pick_weighted(MALE_SKIN_OPTIONS, seed+9)}")
|
||
if neck_enabled:
|
||
lines.append(f"Neck: {pick_weighted(MALE_NECK_OPTIONS, seed+10)}")
|
||
if accessories_enabled:
|
||
lines.append(f"Accessories: {pick_weighted(MALE_ACCESSORIES_OPTIONS, seed+11)}")
|
||
|
||
face_description = "\n".join(lines)
|
||
hair_style = pick_weighted(MALE_HAIR_STYLE_OPTIONS, seed+12)
|
||
hair_color = pick_weighted(MALE_HAIR_COLOR_OPTIONS, seed+13)
|
||
facial_hair = pick_weighted(MALE_FACIAL_HAIR_OPTIONS, seed+14)
|
||
facial_expression = pick_weighted(MALE_EXPRESSION_OPTIONS, seed+15)
|
||
|
||
return face_description, hair_style, hair_color, facial_hair, facial_expression
|
||
|
||
# Node registration mappings.
|
||
NODE_CLASS_MAPPINGS = {
|
||
"EbuPromptHelperCombineTwoStrings": EbuPromptHelperCombineTwoStrings,
|
||
"EbuPromptHelperConsumeListItem": EbuPromptHelperConsumeListItem,
|
||
"EbuPromptHelperCurrentDateTime": EbuPromptHelperCurrentDateTime,
|
||
"EbuPromptHelperListSampler": EbuPromptHelperListSampler,
|
||
"EbuPromptHelperLoadFileAsString": EbuPromptHelperLoadFileAsString,
|
||
"EbuPromptHelperRandomColorPalette": EbuPromptHelperRandomColorPalette,
|
||
"EbuPromptHelperRandomize": EbuPromptHelperRandomize,
|
||
"EbuPromptHelperReplace": EbuPromptHelperReplace,
|
||
"EbuPromptHelperSeasonWeatherTimeOfDay": EbuPromptHelperSeasonWeatherTimeOfDay,
|
||
"EbuPromptHelperTruncate": EbuPromptHelperTruncate,
|
||
"EbuPromptHelperCharacterDescriberFemale": EbuPromptHelperCharacterDescriberFemale,
|
||
"EbuPromptHelperCharacterDescriberMale": EbuPromptHelperCharacterDescriberMale,
|
||
}
|
||
|
||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||
"EbuPromptHelperCombineTwoStrings": "EBU PromptHelper Combine Two Strings",
|
||
"EbuPromptHelperConsumeListItem": "EBU PromptHelper Consume List Item",
|
||
"EbuPromptHelperCurrentDateTime": "EBU PromptHelper Current DateTime",
|
||
"EbuPromptHelperListSampler": "EBU PromptHelper List Sampler",
|
||
"EbuPromptHelperLoadFileAsString": "EBU PromptHelper Load File as String",
|
||
"EbuPromptHelperRandomColorPalette": "EBU PromptHelper Color Palette",
|
||
"EbuPromptHelperRandomize": "EBU PromptHelper Randomize",
|
||
"EbuPromptHelperReplace": "EBU PromptHelper Replace",
|
||
"EbuPromptHelperSeasonWeatherTimeOfDay": "EBU PromptHelper Season Weather Time-Of-Day",
|
||
"EbuPromptHelperTruncate": "EBU PromptHelper Truncate",
|
||
"EbuPromptHelperCharacterDescriberFemale": "EBU PromptHelper Character Describer Female",
|
||
"EbuPromptHelperCharacterDescriberMale": "EBU PromptHelper Character Describer Male",
|
||
}
|