|
import re |
|
import random |
|
import os |
|
import folder_paths |
|
import yaml |
|
import json |
|
from .log import log_node_info |
|
|
|
easy_wildcard_dict = {} |
|
|
|
def get_wildcard_list(): |
|
return [f"__{x}__" for x in easy_wildcard_dict.keys()] |
|
|
|
def wildcard_normalize(x): |
|
return x.replace("\\", "/").lower() |
|
|
|
def read_wildcard(k, v): |
|
if isinstance(v, list): |
|
k = wildcard_normalize(k) |
|
easy_wildcard_dict[k] = v |
|
elif isinstance(v, dict): |
|
for k2, v2 in v.items(): |
|
new_key = f"{k}/{k2}" |
|
new_key = wildcard_normalize(new_key) |
|
read_wildcard(new_key, v2) |
|
|
|
def read_wildcard_dict(wildcard_path): |
|
global easy_wildcard_dict |
|
for root, directories, files in os.walk(wildcard_path, followlinks=True): |
|
for file in files: |
|
if file.endswith('.txt'): |
|
file_path = os.path.join(root, file) |
|
rel_path = os.path.relpath(file_path, wildcard_path) |
|
key = os.path.splitext(rel_path)[0].replace('\\', '/').lower() |
|
|
|
try: |
|
with open(file_path, 'r', encoding="ISO-8859-1") as f: |
|
lines = f.read().splitlines() |
|
easy_wildcard_dict[key] = lines |
|
except UnicodeDecodeError: |
|
with open(file_path, 'r', encoding="UTF-8", errors="ignore") as f: |
|
lines = f.read().splitlines() |
|
easy_wildcard_dict[key] = lines |
|
elif file.endswith('.yaml'): |
|
file_path = os.path.join(root, file) |
|
with open(file_path, 'r') as f: |
|
yaml_data = yaml.load(f, Loader=yaml.FullLoader) |
|
|
|
for k, v in yaml_data.items(): |
|
read_wildcard(k, v) |
|
elif file.endswith('.json'): |
|
file_path = os.path.join(root, file) |
|
try: |
|
with open(file_path, 'r') as f: |
|
json_data = json.load(f) |
|
for key, value in json_data.items(): |
|
key = wildcard_normalize(key) |
|
easy_wildcard_dict[key] = value |
|
except ValueError: |
|
print('json files load error') |
|
return easy_wildcard_dict |
|
|
|
|
|
def process(text, seed=None): |
|
|
|
if seed is not None: |
|
random.seed(seed) |
|
|
|
def replace_options(string): |
|
replacements_found = False |
|
|
|
def replace_option(match): |
|
nonlocal replacements_found |
|
options = match.group(1).split('|') |
|
|
|
multi_select_pattern = options[0].split('$$') |
|
select_range = None |
|
select_sep = ' ' |
|
range_pattern = r'(\d+)(-(\d+))?' |
|
range_pattern2 = r'-(\d+)' |
|
|
|
if len(multi_select_pattern) > 1: |
|
r = re.match(range_pattern, options[0]) |
|
|
|
if r is None: |
|
r = re.match(range_pattern2, options[0]) |
|
a = '1' |
|
b = r.group(1).strip() |
|
else: |
|
a = r.group(1).strip() |
|
b = r.group(3).strip() |
|
|
|
if r is not None: |
|
if b is not None and is_numeric_string(a) and is_numeric_string(b): |
|
|
|
select_range = int(a), int(b) |
|
elif is_numeric_string(a): |
|
|
|
x = int(a) |
|
select_range = (x, x) |
|
|
|
if select_range is not None and len(multi_select_pattern) == 2: |
|
|
|
options[0] = multi_select_pattern[1] |
|
elif select_range is not None and len(multi_select_pattern) == 3: |
|
|
|
select_sep = multi_select_pattern[1] |
|
options[0] = multi_select_pattern[2] |
|
|
|
adjusted_probabilities = [] |
|
|
|
total_prob = 0 |
|
|
|
for option in options: |
|
parts = option.split('::', 1) |
|
if len(parts) == 2 and is_numeric_string(parts[0].strip()): |
|
config_value = float(parts[0].strip()) |
|
else: |
|
config_value = 1 |
|
|
|
adjusted_probabilities.append(config_value) |
|
total_prob += config_value |
|
|
|
normalized_probabilities = [prob / total_prob for prob in adjusted_probabilities] |
|
|
|
if select_range is None: |
|
select_count = 1 |
|
else: |
|
select_count = random.randint(select_range[0], select_range[1]) |
|
|
|
if select_count > len(options): |
|
selected_items = options |
|
else: |
|
selected_items = random.choices(options, weights=normalized_probabilities, k=select_count) |
|
selected_items = set(selected_items) |
|
|
|
try_count = 0 |
|
while len(selected_items) < select_count and try_count < 10: |
|
remaining_count = select_count - len(selected_items) |
|
additional_items = random.choices(options, weights=normalized_probabilities, k=remaining_count) |
|
selected_items |= set(additional_items) |
|
try_count += 1 |
|
|
|
selected_items2 = [re.sub(r'^\s*[0-9.]+::', '', x, 1) for x in selected_items] |
|
replacement = select_sep.join(selected_items2) |
|
if '::' in replacement: |
|
pass |
|
|
|
replacements_found = True |
|
return replacement |
|
|
|
pattern = r'{([^{}]*?)}' |
|
replaced_string = re.sub(pattern, replace_option, string) |
|
|
|
return replaced_string, replacements_found |
|
|
|
def replace_wildcard(string): |
|
global easy_wildcard_dict |
|
pattern = r"__([\w\s.\-+/*\\]+?)__" |
|
matches = re.findall(pattern, string) |
|
replacements_found = False |
|
|
|
for match in matches: |
|
keyword = match.lower() |
|
keyword = wildcard_normalize(keyword) |
|
if keyword in easy_wildcard_dict: |
|
replacement = random.choice(easy_wildcard_dict[keyword]) |
|
replacements_found = True |
|
string = string.replace(f"__{match}__", replacement, 1) |
|
elif '*' in keyword: |
|
subpattern = keyword.replace('*', '.*').replace('+','\+') |
|
total_patterns = [] |
|
found = False |
|
for k, v in easy_wildcard_dict.items(): |
|
if re.match(subpattern, k) is not None: |
|
total_patterns += v |
|
found = True |
|
|
|
if found: |
|
replacement = random.choice(total_patterns) |
|
replacements_found = True |
|
string = string.replace(f"__{match}__", replacement, 1) |
|
elif '/' not in keyword: |
|
string_fallback = string.replace(f"__{match}__", f"__*/{match}__", 1) |
|
string, replacements_found = replace_wildcard(string_fallback) |
|
|
|
return string, replacements_found |
|
|
|
replace_depth = 100 |
|
stop_unwrap = False |
|
while not stop_unwrap and replace_depth > 1: |
|
replace_depth -= 1 |
|
|
|
|
|
pass1, is_replaced1 = replace_options(text) |
|
|
|
while is_replaced1: |
|
pass1, is_replaced1 = replace_options(pass1) |
|
|
|
|
|
text, is_replaced2 = replace_wildcard(pass1) |
|
stop_unwrap = not is_replaced1 and not is_replaced2 |
|
|
|
return text |
|
|
|
|
|
def is_numeric_string(input_str): |
|
return re.match(r'^-?\d+(\.\d+)?$', input_str) is not None |
|
|
|
|
|
def safe_float(x): |
|
if is_numeric_string(x): |
|
return float(x) |
|
else: |
|
return 1.0 |
|
|
|
|
|
def extract_lora_values(string): |
|
pattern = r'<lora:([^>]+)>' |
|
matches = re.findall(pattern, string) |
|
|
|
def touch_lbw(text): |
|
return re.sub(r'LBW=[A-Za-z][A-Za-z0-9_-]*:', r'LBW=', text) |
|
|
|
items = [touch_lbw(match.strip(':')) for match in matches] |
|
|
|
added = set() |
|
result = [] |
|
for item in items: |
|
item = item.split(':') |
|
|
|
lora = None |
|
a = None |
|
b = None |
|
lbw = None |
|
lbw_a = None |
|
lbw_b = None |
|
|
|
if len(item) > 0: |
|
lora = item[0] |
|
|
|
for sub_item in item[1:]: |
|
if is_numeric_string(sub_item): |
|
if a is None: |
|
a = float(sub_item) |
|
elif b is None: |
|
b = float(sub_item) |
|
elif sub_item.startswith("LBW="): |
|
for lbw_item in sub_item[4:].split(';'): |
|
if lbw_item.startswith("A="): |
|
lbw_a = safe_float(lbw_item[2:].strip()) |
|
elif lbw_item.startswith("B="): |
|
lbw_b = safe_float(lbw_item[2:].strip()) |
|
elif lbw_item.strip() != '': |
|
lbw = lbw_item |
|
|
|
if a is None: |
|
a = 1.0 |
|
if b is None: |
|
b = 1.0 |
|
|
|
if lora is not None and lora not in added: |
|
result.append((lora, a, b, lbw, lbw_a, lbw_b)) |
|
added.add(lora) |
|
|
|
return result |
|
|
|
|
|
def remove_lora_tags(string): |
|
pattern = r'<lora:[^>]+>' |
|
result = re.sub(pattern, '', string) |
|
|
|
return result |
|
|
|
def process_with_loras(wildcard_opt, model, clip, title="Positive", seed=None, can_load_lora=True, pipe_lora_stack=[], easyCache=None): |
|
pass1 = process(wildcard_opt, seed) |
|
loras = extract_lora_values(pass1) |
|
pass2 = remove_lora_tags(pass1) |
|
|
|
has_noodle_key = True if "__" in wildcard_opt else False |
|
has_loras = True if loras != [] else False |
|
show_wildcard_prompt = True if has_noodle_key or has_loras else False |
|
|
|
if can_load_lora and has_loras: |
|
for lora_name, model_weight, clip_weight, lbw, lbw_a, lbw_b in loras: |
|
if (lora_name.split('.')[-1]) not in folder_paths.supported_pt_extensions: |
|
lora_name = lora_name+".safetensors" |
|
lora = { |
|
"lora_name": lora_name, "model": model, "clip": clip, "model_strength": model_weight, |
|
"clip_strength": clip_weight, |
|
"lbw_a": lbw_a, |
|
"lbw_b": lbw_b, |
|
"lbw": lbw |
|
} |
|
model, clip = easyCache.load_lora(lora) |
|
lora["model"] = model |
|
lora["clip"] = clip |
|
pipe_lora_stack.append(lora) |
|
|
|
log_node_info("easy wildcards",f"{title}: {pass2}") |
|
if pass1 != pass2: |
|
log_node_info("easy wildcards",f'{title}_decode: {pass1}') |
|
|
|
return model, clip, pass2, pass1, show_wildcard_prompt, pipe_lora_stack |
|
|