Files
2026-05-18 18:34:38 +02:00

121 lines
4.6 KiB
Python

# /ComfyUI-Prompt-Formatter/prompt_normalizer.py
import re
class PromptNormalizer:
"""
A ComfyUI node to normalize prompts:
- Normalizes brackets to their minimum matching pair
- Removes excessive whitespace
- Properly spaces commas, bracketing, and |
- Converts nested brackets ((a)) to a single bracket weight (a:1.21)
"""
NODE_NAME = "Prompt Normalizer"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"multiline": True, "default": ""}),
},
"optional": {
"weight_step": ("FLOAT", {"default": 1.1, "min": 0.1, "max": 2.0, "step": 0.05}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("normalized_prompt",)
FUNCTION = "normalize_prompt"
CATEGORY = "text/utilities"
def normalize_prompt(self, prompt, weight_step=1.1):
if not prompt:
return ("",)
# 1. Clean up whitespace and spacing (brackets, pipes, multiple spacing)
text = re.sub(r'\s+', ' ', prompt)
text = re.sub(r'\s*\|\s*', '|', text)
text = re.sub(r'\s*,\s*', ', ', text)
text = re.sub(r'\(\s+', '(', text)
text = re.sub(r'\s+\)', ')', text)
text = re.sub(r'\[\s+', '[', text)
text = re.sub(r'\s+\]', ']', text)
# 2. Split by commas while tracking outer brackets to bypass internal splits
tokens = []
depth = 0
current =[]
for char in text:
if char in '([{':
depth += 1
current.append(char)
elif char in ')]}':
depth -= 1
current.append(char)
elif char == ',' and depth <= 0:
tokens.append("".join(current))
current =[]
depth = 0 # reset negative depth
else:
current.append(char)
if current:
tokens.append("".join(current))
# 3. Process individual tags securely ensuring mathematical extraction
processed_tokens =[]
for token in tokens:
tag = token.strip()
if not tag:
continue
m = re.match(r'^([\[\(]+)(.*?)([\]\)]+)$', tag)
if m:
opens = m.group(1)
content = m.group(2)
closes = m.group(3)
open_round = opens.count('(')
close_round = closes.count(')')
min_round = min(open_round, close_round)
open_square = opens.count('[')
close_square = closes.count(']')
min_square = min(open_square, close_square)
base_weight = 1.0
text_content = content
weight_m = re.search(r':([0-9.]+)\s*$', content)
if weight_m:
try:
base_weight = float(weight_m.group(1))
text_content = content[:weight_m.start()].strip()
# Base webUI explicit weight consumes exactly one outer round bracket format syntax requirement
if min_round > 0:
min_round -= 1
except ValueError:
pass
elif not content and (min_round > 0 or min_square > 0):
continue
if min_round == 0 and min_square == 0 and abs(base_weight - 1.0) < 1e-5:
processed_tokens.append(content.strip())
continue
weight = base_weight * (weight_step ** min_round) * ((1.0 / weight_step) ** min_square)
if abs(weight - 1.0) < 1e-5:
processed_tokens.append(text_content)
else:
weight_str = f"{weight:.2f}".rstrip('0').rstrip('.')
if weight_str.endswith('.'):
weight_str = weight_str[:-1]
processed_tokens.append(f"({text_content}:{weight_str})")
else:
# Target un-bracketed or highly unbalanced remnants
tag = re.sub(r'^([\[\(]+)', '', tag)
tag = re.sub(r'([\]\)]+)$', '', tag)
processed_tokens.append(tag)
final_prompt = ", ".join(processed_tokens)
return (final_prompt,)