Files
ComfyUI-Ethanfel-Prompt-Bui…/krea_formatter.py
T

958 lines
40 KiB
Python

from __future__ import annotations
import json
import re
from typing import Any
TRIGGER_CANDIDATES = (
"sxcpinup_coloredpencil",
"sxcppnl7",
)
PROMPT_FIELD_LABELS = (
"Ages",
"Body types",
"Cast",
"Cast descriptors",
"Characters",
"Scene",
"Setting",
"Pose",
"Sexual pose",
"Facial expression",
"Facial expressions",
"Clothing",
"Erotic outfit",
"Prop/detail",
"Composition",
"Role graph",
"Use",
"Avoid",
)
def _clean(value: Any) -> str:
text = "" if value is None else str(value)
text = text.replace("\n", " ")
text = re.sub(r"\s+", " ", text).strip()
text = re.sub(r"\s+([,.;:])", r"\1", text)
return text
def _sentence(text: str) -> str:
text = _clean(text).strip(" ,;")
if not text:
return ""
text = text[:1].upper() + text[1:]
if text[-1] not in ".!?":
text += "."
return text
def _paragraph(parts: list[str]) -> str:
return " ".join(part for part in (_sentence(part) for part in parts) if part)
def _with_indefinite_article(text: str) -> str:
text = _clean(text)
if not text or text.lower().startswith(("a ", "an ")):
return text
article = "an" if text[:1].lower() in "aeiou" else "a"
return f"{article} {text}"
def _maybe_json(text: str) -> dict[str, Any] | None:
text = _clean(text)
if not text.startswith("{"):
return None
try:
value = json.loads(text)
except json.JSONDecodeError:
return None
return value if isinstance(value, dict) else None
def _row_from_inputs(source_text: str, metadata_json: str, input_hint: str) -> tuple[dict[str, Any] | None, str]:
candidates: list[tuple[str, str]] = []
if input_hint in ("auto", "metadata_json"):
candidates.append((metadata_json, "metadata_json"))
candidates.append((source_text, "source_json"))
for text, method in candidates:
row = _maybe_json(text)
if row is not None:
return row, method
return None, "text"
def _strip_trigger(text: str, preserve_trigger: bool) -> str:
text = _clean(text)
if preserve_trigger:
return text
for trigger in TRIGGER_CANDIDATES:
if text.lower().startswith(trigger.lower() + ","):
return text[len(trigger) + 1 :].strip(" ,")
if text.lower().startswith(trigger.lower() + "."):
return text[len(trigger) + 1 :].strip(" ,")
return text
def _split_avoid(text: str) -> tuple[str, str]:
match = re.search(r"\bAvoid:\s*(.*)$", text)
if not match:
return text, ""
return text[: match.start()].strip(" ."), match.group(1).strip(" .")
def _prompt_field(text: str, label: str) -> str:
text = _clean(text)
if not text:
return ""
labels = "|".join(re.escape(name) for name in PROMPT_FIELD_LABELS)
pattern = rf"{re.escape(label)}:\s*(.*?)(?=\. (?:{labels}):|\. Use\b|\. Avoid\b|$)"
match = re.search(pattern, text)
if not match:
return ""
return _clean(match.group(1)).rstrip(".")
def _row_value(row: dict[str, Any], key: str, labels: tuple[str, ...] = ()) -> str:
value = _clean(row.get(key, ""))
if value:
return value
prompt = _clean(row.get("prompt", ""))
for label in labels:
value = _prompt_field(prompt, label)
if value:
return value
return ""
def _body_phrase(body: Any, figure_note: Any = "") -> str:
body = _clean(body)
figure_note = _clean(figure_note)
if not body:
return figure_note
if not figure_note:
return f"{body} figure"
if "figure" in figure_note.lower():
return f"{body} build and {figure_note}"
return f"{body} figure with {figure_note}"
def _single_caption_front(row: dict[str, Any]) -> dict[str, str]:
caption = _strip_trigger(_clean(row.get("caption")), False)
if not caption:
return {}
subject = _clean(row.get("primary_subject"))
age = _clean(row.get("age_band") or row.get("age"))
body = _clean(row.get("body_phrase"))
if not body:
body_type = _clean(row.get("body_type") or row.get("body"))
figure = _clean(row.get("figure"))
body = _body_phrase(body_type, figure)
front = f"{subject}, {age}, {body}, "
if subject in ("woman", "man") and age and body and caption.startswith(front):
try:
skin, hair, eyes, _rest = caption[len(front) :].split(", ", 3)
except ValueError:
return {}
return {"body_phrase": body, "skin": skin, "hair": hair, "eyes": eyes}
return {}
def _combine_negative(*parts: str) -> str:
cleaned = [_clean(part).strip(" ,.") for part in parts if _clean(part).strip(" ,.")]
return ", ".join(cleaned)
def _prompt_cast_descriptors(text: str) -> str:
return _clean(text).replace("Woman A / primary creator:", "Woman A:")
def _cast_entries(text: str) -> list[tuple[str, str]]:
text = _prompt_cast_descriptors(text)
entries: list[tuple[str, str]] = []
for part in text.split(";"):
part = _clean(part)
match = re.match(r"^((?:Woman|Man) [A-Z]):\s*(.+)$", part)
if match:
entries.append((match.group(1), _clean(match.group(2))))
return entries
def _label_join(labels: list[str]) -> str:
labels = [_clean(label) for label in labels if _clean(label)]
if not labels:
return "the named adults"
if set(labels) == {"Woman A", "Man A"}:
return "the woman and man"
if len(labels) == 1:
if labels[0] == "Woman A":
return "the woman"
if labels[0] == "Man A":
return "the man"
return labels[0]
if len(labels) == 2:
return f"{labels[0]} and {labels[1]}"
return f"{', '.join(labels[:-1])}, and {labels[-1]}"
def _natural_label_text(text: Any, labels: list[str]) -> str:
text = _clean(text)
if not text:
return ""
if set(labels) == {"Woman A", "Man A"}:
text = re.sub(r"\bWoman A\b", "the woman", text)
text = re.sub(r"\bMan A\b", "the man", text)
elif labels == ["Woman A"]:
text = re.sub(r"\bWoman A\b", "the woman", text)
elif labels == ["Man A"]:
text = re.sub(r"\bMan A\b", "the man", text)
return text
def _cast_prose(text: str, central_label: str = "Woman A") -> tuple[str, list[str]]:
entries = _cast_entries(text)
if not entries:
return (f"{central_label} is {_clean(text)}" if _clean(text) else "", [])
labels = [label for label, _descriptor in entries]
if labels == ["Woman A"]:
return _with_indefinite_article(entries[0][1]), labels
if labels == ["Man A"]:
return _with_indefinite_article(entries[0][1]), labels
if set(labels) == {"Woman A", "Man A"} and len(labels) == 2:
by_label = {label: descriptor for label, descriptor in entries}
return f"{_with_indefinite_article(by_label['Woman A'])} alongside {_with_indefinite_article(by_label['Man A'])}", labels
sentences = []
for label, descriptor in entries:
sentences.append(f"{label} is {descriptor}.")
if central_label in labels:
sentences.append(f"{central_label} is the central subject.")
return " ".join(sentences), labels
def _sanitize_scene_text_for_cast(text: Any, labels: list[str]) -> str:
text = _clean(text)
if not text:
return ""
if len(labels) < 3:
text = re.sub(r"\s*(?:while|as)\s+another partner watches\b", "", text, flags=re.IGNORECASE)
text = re.sub(r"\banother partner watches\b", "", text, flags=re.IGNORECASE)
text = re.sub(r",?\s*\bone partner held between two bodies\b", "", text, flags=re.IGNORECASE)
text = re.sub(r",?\s*\bthree bodies locked together\b", "", text, flags=re.IGNORECASE)
text = re.sub(r",?\s*\bthree bodies\b", "", text, flags=re.IGNORECASE)
text = re.sub(r"\bwith\s*,\s*", "with ", text, flags=re.IGNORECASE)
text = re.sub(r"\bwhile blowjob\b", "during a blowjob", text, flags=re.IGNORECASE)
text = re.sub(r"\bfeaturing blowjob\b", "featuring a blowjob", text, flags=re.IGNORECASE)
text = re.sub(r"\s+,", ",", text)
text = re.sub(r",\s*,", ",", text)
text = re.sub(r"\s{2,}", " ", text).strip(" ,")
return text
def _natural_clothing_state(text: Any) -> str:
text = _clean(text)
if not text:
return ""
text = re.sub(r"^Clothing state:\s*", "", text, flags=re.IGNORECASE)
match = re.match(
r"^(.*?)\b(?:softcore|teaser) outfit is (.*?)(?: for the (?:hardcore|sex) scene)?;\s*(?:softcore visual reference|teaser outfit detail):\s*(.*?)\.?$",
text,
flags=re.IGNORECASE,
)
if match:
owner = _natural_label_text(match.group(1).strip(" 's"), ["Woman A", "Man A"]).strip() or "the woman"
state = _clean(match.group(2)).lower()
outfit = _clean(match.group(3)).rstrip(".")
if "fully nude" in state:
return f"{owner.capitalize()} is fully nude, with the removed {outfit} visible nearby"
if "nude-adjacent" in state:
return f"{owner.capitalize()} is partly nude, with the {outfit} slipping off and no abstract clothing-reference wording"
if "partially removed" in state or "pushed aside" in state:
return f"{owner.capitalize()}'s {outfit} is pushed aside and partly removed, exposing the sexual contact clearly"
if "keeps" in state:
return f"{owner.capitalize()} keeps the {outfit} on while the sexual contact stays visible"
text = re.sub(r";\s*(?:softcore visual reference|teaser outfit detail):\s*", ". Visual clothing state: ", text, flags=re.IGNORECASE)
text = text.replace("softcore outfit", "outfit")
text = text.replace("teaser outfit", "outfit")
text = text.replace("hardcore scene", "sex scene")
return text
def _hardcore_pose_anchor(role_graph: str, hard_item: str, composition: str = "") -> str:
text = " ".join(_clean(part).lower() for part in (role_graph, hard_item, composition) if _clean(part))
item_text = _clean(hard_item).lower()
if not text:
return ""
if "double penetration" in text or "vaginal and anal penetration" in text or "front-and-back" in text:
if "bed-edge" in text or "edge-of-bed" in text:
return "bed-edge front-and-back double-penetration pose"
if "standing supported" in text:
return "standing supported front-and-back double-penetration pose"
if "kneeling" in text:
return "kneeling front-and-back double-penetration pose"
return "front-and-back double-penetration pose"
if "face-sitting" in text:
return "face-sitting oral pose"
if "sixty-nine" in text:
return "sixty-nine oral pose"
if "cunnilingus" in text or "pussy licking" in text or "mouth on her pussy" in text:
if "reclining" in text:
return "reclining cunnilingus pose"
if "straddled" in text:
return "straddled cunnilingus pose"
return "open-thigh cunnilingus pose"
if "oral" in text or "blowjob" in text or "penis in her mouth" in text or "penis in mouth" in text:
if "side-lying oral position" in item_text:
return "side-lying oral pose"
if "spread-leg oral position" in item_text:
return "spread-leg oral pose"
if "edge-of-bed oral position" in item_text:
return "edge-of-bed oral pose"
if "standing oral position" in item_text:
return "standing oral pose"
if "chair oral position" in item_text:
return "chair oral pose"
if "kneeling oral position" in item_text or "kneeling" in text:
return "kneeling oral pose"
if "standing" in text:
return "standing oral pose"
if "side-lying" in text:
return "side-lying oral pose"
if "edge-of-bed" in text or "bed-edge" in text:
return "edge-of-bed oral pose"
if "spread-leg" in text:
return "spread-leg oral pose"
if "chair oral" in text:
return "chair oral pose"
return "mouth-to-genitals oral pose"
if "anal" in text or "ass" in text or "rear-entry" in text:
if "bed-edge" in text or "edge-of-bed" in text:
return "bed-edge rear-entry anal pose"
if "bent-over" in text:
return "bent-over rear-entry anal pose"
if "doggy" in text:
return "doggy-style anal pose"
return "rear-entry anal pose"
positions = (
"missionary",
"cowgirl",
"reverse cowgirl",
"doggy style",
"standing sex",
"spooning sex",
"edge-of-bed",
"kneeling straddle",
"lotus",
"bent-over",
)
for position in positions:
if position in text:
return f"{position.replace('doggy style', 'doggy-style')} pose"
if "threesome" in text or "three-body" in text:
return "three-body explicit sex pose"
if "group" in text or "orgy" in text:
return "multi-body explicit sex pose"
if "penetrat" in text or "thrust" in text:
return "hip-aligned penetrative sex pose"
return ""
def _hardcore_pose_arrangement(anchor: str, role_graph: str, hard_item: str, composition: str = "") -> str:
text = " ".join(_clean(part).lower() for part in (anchor, role_graph, hard_item, composition) if _clean(part))
if not text:
return ""
if "double-penetration" in text or "double penetration" in text:
if "toy" in text:
return "with the woman's hips spread and front-and-back alignment visible"
return "with the central body held between front-and-back contact"
if "anal" in text or "rear-entry" in text:
return "with the woman's hips raised, ass exposed, and penetration alignment visible"
if "cunnilingus" in text or "mouth on her pussy" in text or "pussy licking" in text:
return "with the woman's thighs open and the giver's mouth pressed to her pussy"
if "oral" in text or "blowjob" in text or "penis in her mouth" in text or "penis in mouth" in text:
if "sixty-nine" in text:
return "with both bodies arranged mouth-to-genitals"
if "takes the man's penis in her mouth" in text or "penis in her mouth" in text:
return "with the woman's mouth close to the man's hips"
return "with mouth and genitals aligned clearly"
if "threesome" in text or "three-body" in text:
return "with all three adult bodies clearly placed around the central subject"
if "group" in text or "orgy" in text:
return "with each adult body readable in the shared sex act"
if "penetrat" in text or "thrust" in text:
return "with hips aligned and legs open around the contact point"
return ""
def _hardcore_item_detail(hard_item: str) -> str:
text = _clean(hard_item).rstrip(".")
if not text:
return ""
text = re.sub(r"^hardcore\s+", "", text, flags=re.IGNORECASE)
text = re.sub(r"^explicit\s+", "", text, flags=re.IGNORECASE)
text = re.sub(r"^(?:mouth-to-genitals|double-contact sex|adult group pile|sex pile)\s+pose:\s*", "", text, flags=re.IGNORECASE)
text = re.sub(r"^(?:oral|threesome|orgy)\s+scene\s+with\s+", "", text, flags=re.IGNORECASE)
text = re.sub(r"^(?:threesome|orgy)\s+pose:\s*", "", text, flags=re.IGNORECASE)
act_patterns = (
r"(?:penis and toy|toy and strap-on|toy-assisted|front-and-back|hardcore|deep|kneeling|standing supported)?\s*double penetration",
r"toy-assisted vaginal and anal penetration at the same time",
r"vaginal and anal penetration at the same time",
r"one penis in pussy and one penis in ass",
r"anal penetration with visible genital contact",
r"rear-entry anal penetration",
r"anal sex with spread cheeks",
r"ass stretched around a penis",
r"penis entering ass",
r"deep anal sex",
r"bent-over anal sex",
r"hardcore anal thrusting",
r"vaginal penetration with visible genital contact",
r"penis entering pussy",
r"pussy stretched around a penis",
r"deep vaginal sex",
r"explicit penetrative sex",
r"hardcore vaginal thrusting",
r"full-body penetrative sex",
r"close-contact vaginal sex",
r"fellatio with penis in mouth",
r"deepthroat blowjob",
r"blowjob",
r"penis sucking with visible saliva",
r"cunnilingus with tongue on pussy",
r"face-sitting cunnilingus",
r"pussy licking with thighs spread",
r"oral sex with tongue and fingers",
r"mouth on genitals with explicit contact",
r"sixty-nine oral sex",
)
act_pattern = "|".join(act_patterns)
position_pattern = (
r"missionary position|cowgirl position|reverse cowgirl position|doggy style position|"
r"standing sex position|spooning sex position|edge-of-bed position|kneeling straddle position|"
r"lotus sex position|bent-over position|kneeling oral position|face-sitting position|"
r"sixty-nine position|edge-of-bed oral position|standing oral position|reclining cunnilingus position|"
r"straddled oral position|side-lying oral position|spread-leg oral position|chair oral position"
)
text = re.sub(
rf"^({position_pattern})\s+(?:while|with|featuring)\s+(?:{act_pattern})\s*,?\s*",
r"\1, ",
text,
flags=re.IGNORECASE,
)
text = re.sub(
rf"^(?:{act_pattern})\s*(?:in|from|on|with|while|featuring)?\s*",
"",
text,
flags=re.IGNORECASE,
)
text = re.sub(r"^(?:position|pose)\s+", "", text, flags=re.IGNORECASE)
text = re.sub(r"^with\s+", "", text, flags=re.IGNORECASE)
text = re.sub(r"\bwith with\b", "with", text, flags=re.IGNORECASE)
text = re.sub(r",\s*with\s+", ", ", text, flags=re.IGNORECASE)
text = re.sub(r",\s+and\s+", ", ", text)
text = re.sub(r"\s*,\s*", ", ", text).strip(" ,;")
return _clean(text)
def _dedupe_hardcore_detail(detail: str, anchor: str) -> str:
detail = _clean(detail)
anchor_lower = anchor.lower()
duplicate_phrases = {
"front-and-back": (r"front-and-back contact",),
"side-lying oral": (r"side-lying oral position",),
"kneeling oral": (r"kneeling oral position",),
"face-sitting": (r"face-sitting position",),
"sixty-nine": (
r"sixty-nine position",
r"sixty-nine oral sex",
r"kneeling oral position",
r"face-sitting position",
r"edge-of-bed oral position",
r"standing oral position",
r"reclining cunnilingus position",
r"straddled oral position",
r"side-lying oral position",
r"spread-leg oral position",
r"chair oral position",
),
"edge-of-bed oral": (r"edge-of-bed oral position",),
"standing oral": (r"standing oral position",),
"spread-leg oral": (r"spread-leg oral position",),
"chair oral": (r"chair oral position",),
"reclining cunnilingus": (r"reclining cunnilingus position",),
"straddled cunnilingus": (r"straddled oral position", r"straddled cunnilingus position"),
"open-thigh cunnilingus": (r"reclining cunnilingus position", r"straddled cunnilingus position"),
"bent-over": (r"bent-over position",),
"missionary": (r"missionary position",),
"cowgirl": (r"cowgirl position",),
"reverse cowgirl": (r"reverse cowgirl position",),
"doggy-style": (r"doggy style position",),
"edge-of-bed": (r"edge-of-bed position",),
}
for anchor_token, phrases in duplicate_phrases.items():
if anchor_token in anchor_lower:
for phrase in phrases:
detail = re.sub(rf"\b{phrase}\b,?\s*", "", detail, flags=re.IGNORECASE)
detail = re.sub(r"^\s*,\s*", "", detail)
detail = re.sub(r",\s*,", ",", detail)
return _clean(detail).strip(" ,;")
def _hardcore_action_sentence(role_graph: str, hard_item: str, composition: str = "") -> str:
role_graph = _clean(role_graph).rstrip(".")
hard_item = _clean(hard_item).rstrip(".")
role_graph = re.sub(
r"\bthe man penetrates the woman while a toy adds a second point of contact\b",
"the man's penis thrusts into the woman while a toy adds a second penetration point",
role_graph,
flags=re.IGNORECASE,
)
role_graph = re.sub(
r"\bthe man thrusts his penis into the woman while a toy adds a second penetration point\b",
"the man's penis thrusts into the woman while a toy adds the second penetration point",
role_graph,
flags=re.IGNORECASE,
)
role_graph = re.sub(
r"\bthe man thrusts his penis into the woman\b",
"the man's penis thrusts into the woman",
role_graph,
flags=re.IGNORECASE,
)
role_graph = re.sub(
r"\bthe man penetrates the woman anally\b",
"the man's penis thrusts into the woman's ass",
role_graph,
flags=re.IGNORECASE,
)
role_graph = re.sub(
r"\bthe man thrusts his penis into the woman's ass\b",
"the man's penis thrusts into the woman's ass",
role_graph,
flags=re.IGNORECASE,
)
role_graph = re.sub(
r"\bthe man penetrates the woman\b",
"the man's penis thrusts into the woman",
role_graph,
flags=re.IGNORECASE,
)
role_graph = re.sub(
r"\bthe woman and the man are in mutual oral contact with mouth-to-genital contact visible\b",
"the woman has the man's penis in her mouth while the man uses his mouth on her pussy",
role_graph,
flags=re.IGNORECASE,
)
role_graph = re.sub(
r"\bthe woman gives oral to the man\b",
"the woman takes the man's penis in her mouth",
role_graph,
flags=re.IGNORECASE,
)
detail = _hardcore_item_detail(hard_item)
anchor = _hardcore_pose_anchor(role_graph, hard_item, composition)
detail = _dedupe_hardcore_detail(detail, anchor) if anchor else detail
arrangement = _hardcore_pose_arrangement(anchor, role_graph, hard_item, composition)
anchor_phrase = _with_indefinite_article(anchor) if anchor else ""
if arrangement and anchor_phrase:
anchor_phrase = f"{anchor_phrase} {arrangement}"
if role_graph and anchor_phrase:
sentence = f"In {anchor_phrase}, {role_graph}"
elif role_graph:
sentence = role_graph
elif detail and anchor_phrase:
sentence = f"In {anchor_phrase}, {detail}"
detail = ""
else:
sentence = detail or hard_item
detail = ""
if detail:
sentence = f"{sentence}; {detail}"
return sentence
def _composition_phrase(composition: Any, action: str = "", prefix: str = "framed as") -> str:
composition = _clean(composition)
if not composition:
return ""
action_lower = _clean(action).lower()
composition_lower = composition.lower()
oral_pose_tokens = (
"kneeling oral",
"side-lying oral",
"spread-leg oral",
"standing oral",
"edge-of-bed oral",
"face-sitting",
"sixty-nine",
"reclining cunnilingus",
"straddled oral",
"chair oral",
)
if "oral" in action_lower:
composition_oral_tokens = [token for token in oral_pose_tokens if token in composition_lower]
if composition_oral_tokens and not any(token in action_lower for token in composition_oral_tokens):
match = re.search(r"\bwith\s+(.+)$", composition, flags=re.IGNORECASE)
return f"framed with {match.group(1)}" if match else ""
return f"{prefix} {composition}"
def _clean_age(age: Any) -> str:
return _clean(age)
def _age_detail_phrase(age: Any) -> str:
text = _clean(age)
text = re.sub(r"\s+adults?$", "", text).strip()
return text.replace("-year-old", " years old")
def _age_subject(row: dict[str, Any], fallback_subject: str = "adult person") -> str:
subject = _clean(row.get("subject_phrase") or row.get("primary_subject") or row.get("subject") or fallback_subject)
age = _clean_age(row.get("age_band") or row.get("age"))
if row.get("subject_type") == "configured_cast":
return _clean(row.get("subject_phrase") or subject)
if subject in ("woman", "man"):
if age:
return f"{age} {subject}" if "adult" in age.lower() else f"{age} adult {subject}"
return f"adult {subject}"
if age and "adult" not in subject.lower():
return f"{age} {subject}"
return subject or fallback_subject
def _appearance_phrase(row: dict[str, Any]) -> str:
front = _single_caption_front(row)
parts = [
_row_value(row, "body_phrase") or front.get("body_phrase"),
_row_value(row, "skin") or front.get("skin"),
_row_value(row, "hair") or front.get("hair"),
_row_value(row, "eyes") or front.get("eyes"),
]
return ", ".join(_clean(part) for part in parts if _clean(part))
def _camera_phrase(row: dict[str, Any]) -> str:
directive = _clean(row.get("camera_directive"))
if directive:
return directive
config = row.get("camera_config")
if isinstance(config, dict):
detail = _clean(config.get("camera_detail"))
if detail == "off" or _clean(config.get("camera_mode")) == "disabled":
return ""
mode = _clean(config.get("camera_mode")).replace("_", " ")
shot = _clean(config.get("shot_size")).replace("_", " ")
angle = _clean(config.get("angle")).replace("_", " ")
pieces = [piece for piece in (mode, shot, angle) if piece and piece != "auto" and piece != "standard"]
if pieces:
return "Camera: " + ", ".join(pieces)
return ""
def _camera_phrase_from_config(config: Any) -> str:
if not isinstance(config, dict):
return ""
detail = _clean(config.get("camera_detail"))
if detail == "off" or _clean(config.get("camera_mode")) == "disabled":
return ""
values = [
_clean(config.get("camera_mode")).replace("_", " "),
_clean(config.get("shot_size")).replace("_", " "),
_clean(config.get("angle")).replace("_", " "),
_clean(config.get("lens")).replace("_", " "),
_clean(config.get("distance")).replace("_", " "),
_clean(config.get("orientation")).replace("_", " "),
_clean(config.get("phone_visibility")).replace("_", " "),
]
pieces = [value for value in values if value and value not in ("auto", "standard")]
if not pieces:
return ""
return "Camera: " + ", ".join(pieces)
def _pair_camera_phrase(directive: Any, config: Any, row: dict[str, Any]) -> str:
directive_text = _clean(directive)
if directive_text:
return directive_text
if isinstance(config, dict) and (
_clean(config.get("camera_detail")) == "off" or _clean(config.get("camera_mode")) == "disabled"
):
return ""
return _camera_phrase_from_config(config) or _camera_phrase(row)
def _style_phrase(row: dict[str, Any], style_mode: str) -> str:
if style_mode == "minimal":
return ""
if style_mode == "photographic":
return "realistic creator-shot photography with natural lighting, tactile skin and fabric detail, and clean social-media composition"
style = _clean(row.get("style"))
suffix = _clean(row.get("positive_suffix")) or _prompt_field(_clean(row.get("prompt")), "Use")
if style and suffix:
return f"{style}; {suffix}"
return style or suffix
def _couple_clothing_phrase(item: str) -> str:
item = _clean(item)
lower = item.lower()
partner_text = re.sub(r"\bPartner ([AB]) wears\b", r"Partner \1 wearing", item)
partner_text = re.sub(r"\bPartner ([AB]) has\b", r"Partner \1 with", partner_text)
if lower.startswith("partner a "):
return f"The outfits show {partner_text}"
if lower.startswith(("two ", "paired ", "coordinated ")):
return f"The outfits are {partner_text}"
return f"The couple wears {item}"
def _normal_row_to_krea(row: dict[str, Any], detail_level: str, style_mode: str) -> tuple[str, str]:
subject_type = _clean(row.get("subject_type"))
primary = _clean(row.get("primary_subject"))
item = _row_value(row, "item", ("Sexual pose", "Erotic outfit", "Clothing")) or _clean(row.get("custom_item"))
item = re.sub(r",?\s*(fashion editorial|resort) styling$", "", item, flags=re.IGNORECASE)
scene = _row_value(row, "scene_text", ("Setting", "Scene")) or _clean(row.get("scene"))
pose = _row_value(row, "pose", ("Sexual pose", "Pose"))
expression = _row_value(row, "expression", ("Facial expressions", "Facial expression"))
composition = re.sub(r"^vertical\s+", "", _row_value(row, "composition", ("Composition",)), flags=re.IGNORECASE)
camera = _camera_phrase(row)
style = _style_phrase(row, style_mode)
if subject_type == "configured_cast" or _clean(row.get("cast_summary")):
subject = _clean(row.get("subject_phrase") or primary or "adult sexual scene")
cast = _clean(row.get("cast_summary"))
try:
women_count = int(row.get("women_count") or 0)
men_count = int(row.get("men_count") or 0)
except (TypeError, ValueError):
women_count = men_count = 0
cast_descriptor_text = (
_clean(row.get("cast_descriptor_text"))
or _prompt_field(_clean(row.get("prompt")), "Characters")
or _prompt_field(_clean(row.get("prompt")), "Cast descriptors")
)
cast_prose, cast_labels = _cast_prose(cast_descriptor_text)
if not cast_labels and women_count == 1 and men_count == 1:
cast_labels = ["Woman A", "Man A"]
role_graph = _sanitize_scene_text_for_cast(row.get("role_graph"), cast_labels)
item = _sanitize_scene_text_for_cast(item, cast_labels)
role_graph = _natural_label_text(role_graph, cast_labels)
item = _natural_label_text(item, cast_labels)
action = _hardcore_action_sentence(role_graph, item, composition)
parts = [
action,
cast_prose,
f"A consensual explicit adult scene with {subject}" if not action else "",
f"The cast includes {cast}" if cast and not cast_prose and not (women_count == 1 and men_count == 1) else "",
f"The setting is {scene}" if scene else "",
f"Facial expressions are {expression}" if expression else "",
_composition_phrase(composition, action, "The image is framed as"),
camera,
style if detail_level != "concise" else "",
]
return _paragraph(parts), "metadata(configured_cast)"
if primary in ("woman", "man") or subject_type in ("woman", "man", "single_any"):
subject = _age_subject(row, "adult woman")
appearance = _appearance_phrase(row)
parts = [
_with_indefinite_article(subject),
f"with {appearance}" if appearance else "",
f"wearing {item}" if item else "",
f"{pose}" if pose else "",
f"with {expression}" if expression else "",
f"in {scene}" if scene else "",
f"framed as {composition}" if composition else "",
camera,
style if detail_level != "concise" else "",
]
return _paragraph([", ".join(part for part in parts[:6] if part), *parts[6:]]), "metadata(single)"
if subject_type == "couple" or primary in ("two women", "two men", "a woman and a man"):
subject = _clean(row.get("subject_phrase") or primary or "adult couple")
if subject == "woman and man":
subject = "a woman and a man"
ages = _age_detail_phrase(_row_value(row, "age", ("Ages",)) or row.get("age_band"))
body = _row_value(row, "body", ("Body types",)) or _clean(row.get("body_type"))
parts = [
f"An adult couple: {subject}, all visibly adult",
f"Age detail: {ages}" if ages else "",
f"Body types: {body}" if body else "",
_couple_clothing_phrase(item) if item else "",
f"The pose is {pose}" if pose else "",
f"The setting is {scene}" if scene else "",
f"Facial expressions are {expression}" if expression else "",
f"The image is framed as {composition}" if composition else "",
camera,
style if detail_level != "concise" else "",
]
return _paragraph(parts), "metadata(couple)"
subject = _age_subject(row, primary or "adult scene")
parts = [
f"{subject}",
f"featuring {item}" if item else "",
f"in {scene}" if scene else "",
f"with {expression}" if expression else "",
f"framed as {composition}" if composition else "",
camera,
style if detail_level != "concise" else "",
]
return _paragraph(parts), "metadata(generic)"
def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str) -> tuple[str, str, str, str]:
descriptor = _clean(row.get("shared_descriptor"))
cast_descriptors = row.get("shared_cast_descriptors")
if isinstance(cast_descriptors, list):
cast_descriptor_text = "; ".join(_clean(item) for item in cast_descriptors if _clean(item))
else:
cast_descriptor_text = _clean(cast_descriptors)
cast_descriptor_text = _prompt_cast_descriptors(cast_descriptor_text)
soft = row.get("softcore_row") if isinstance(row.get("softcore_row"), dict) else {}
hard = row.get("hardcore_row") if isinstance(row.get("hardcore_row"), dict) else {}
soft_camera = _pair_camera_phrase(row.get("softcore_camera_directive"), row.get("softcore_camera_config"), soft)
hard_camera = _pair_camera_phrase(row.get("hardcore_camera_directive"), row.get("hardcore_camera_config"), hard)
soft_style = _style_phrase(soft, style_mode)
hard_style = _style_phrase(hard, style_mode)
options = row.get("options") if isinstance(row.get("options"), dict) else {}
soft_level = _clean(options.get("softcore_level")).replace("_", " ")
hard_level = _clean(options.get("hardcore_level")).replace("_", " ")
same_room = options.get("continuity") == "same_creator_same_room"
hard_scene = soft.get("scene_text") if same_room and soft.get("scene_text") else hard.get("scene_text")
hard_composition = hard.get("composition")
soft_cast_descriptor_text = (
cast_descriptor_text
if options.get("softcore_cast") == "same_as_hardcore"
else f"Woman A: {descriptor}"
)
soft_cast_prose, soft_labels = _cast_prose(soft_cast_descriptor_text)
hard_cast_prose, hard_labels = _cast_prose(cast_descriptor_text)
hard_item = _sanitize_scene_text_for_cast(hard.get("item"), hard_labels)
hard_role_graph = _sanitize_scene_text_for_cast(hard.get("role_graph"), hard_labels)
hard_item = _natural_label_text(hard_item, hard_labels)
hard_role_graph = _natural_label_text(hard_role_graph, hard_labels)
hard_action = _hardcore_action_sentence(hard_role_graph, hard_item, hard_composition)
same_soft_cast = options.get("softcore_cast") == "same_as_hardcore"
soft_cast_presence = (
f"{_label_join(soft_labels)} are together in a non-explicit teaser pose, with no sex act or genital contact"
if same_soft_cast
else "The image focuses on the woman alone"
)
partner_styling = row.get("softcore_partner_styling")
if isinstance(partner_styling, dict):
outfits = partner_styling.get("outfits")
partner_outfit_text = "; ".join(_clean(item) for item in outfits if _clean(item)) if isinstance(outfits, list) else ""
partner_pose = _clean(partner_styling.get("pose"))
else:
partner_outfit_text = ""
partner_pose = ""
partner_outfit_text = _natural_label_text(partner_outfit_text, soft_labels)
soft_parts = [
soft_cast_prose,
soft_cast_presence,
partner_outfit_text,
partner_pose,
f"wearing {soft.get('item')}" if soft.get("item") else "",
f"{soft.get('pose')}" if soft.get("pose") else "",
f"with {soft.get('expression')}" if soft.get("expression") else "",
f"in {soft.get('scene_text')}" if soft.get("scene_text") else "",
f"framed as {soft.get('composition')}" if soft.get("composition") else "",
soft_camera,
soft_style if detail_level != "concise" else "",
]
hard_parts = [
hard_action,
_natural_clothing_state(row.get("hardcore_clothing_state")),
hard_cast_prose,
f"set in {hard_scene}" if hard_scene else "",
f"with {hard.get('expression')}" if hard.get("expression") else "",
_composition_phrase(hard_composition, hard_action),
hard_camera,
hard_style if detail_level != "concise" else "",
]
return (
_paragraph(soft_parts),
_combine_negative(row.get("softcore_negative_prompt")),
_paragraph(hard_parts),
_combine_negative(row.get("hardcore_negative_prompt")),
)
def _fallback_text_to_krea(
source_text: str,
preserve_trigger: bool,
detail_level: str,
style_mode: str,
) -> tuple[str, str, str]:
positive, negative = _split_avoid(_strip_trigger(source_text, preserve_trigger))
positive = re.sub(r"\b(?:Scene|Setting):", "The setting is", positive)
positive = re.sub(r"\b(?:Pose|Sexual pose):", "The pose is", positive)
positive = re.sub(r"\bFacial expressions?:", "The facial expression is", positive)
positive = re.sub(r"\bComposition:", "The composition is", positive)
positive = re.sub(r"\bRole graph:", "The role choreography is", positive)
positive = re.sub(r"\bUse\b", "Use", positive)
positive = _clean(positive)
return _paragraph([positive]), negative, "text(fallback)"
def format_krea2_prompt(
source_text: str,
metadata_json: str = "",
negative_prompt: str = "",
input_hint: str = "auto",
target: str = "auto",
detail_level: str = "balanced",
style_mode: str = "preserve",
preserve_trigger: bool = False,
extra_positive: str = "",
extra_negative: str = "",
) -> dict[str, str]:
detail_level = detail_level if detail_level in ("concise", "balanced", "dense") else "balanced"
style_mode = style_mode if style_mode in ("preserve", "photographic", "minimal") else "preserve"
target = target if target in ("auto", "single", "softcore", "hardcore") else "auto"
row, method = _row_from_inputs(source_text, metadata_json, input_hint)
extracted_negative = ""
if row and row.get("mode") == "Insta/OF":
soft_prompt, soft_negative, hard_prompt, hard_negative = _insta_pair_to_krea(row, detail_level, style_mode)
selected = hard_prompt if target == "hardcore" else soft_prompt if target == "softcore" else soft_prompt
selected_negative = hard_negative if target == "hardcore" else soft_negative
if extra_positive.strip():
selected = f"{selected.rstrip()} {extra_positive.strip()}"
soft_prompt = f"{soft_prompt.rstrip()} {extra_positive.strip()}"
hard_prompt = f"{hard_prompt.rstrip()} {extra_positive.strip()}"
negative = _combine_negative(selected_negative, negative_prompt, extra_negative)
return {
"krea_prompt": selected,
"negative_prompt": negative,
"krea_softcore_prompt": soft_prompt,
"krea_hardcore_prompt": hard_prompt,
"softcore_negative_prompt": _combine_negative(soft_negative, extra_negative),
"hardcore_negative_prompt": _combine_negative(hard_negative, extra_negative),
"method": f"{method}:krea2(insta_of_pair)",
}
if row:
prompt, kind = _normal_row_to_krea(row, detail_level, style_mode)
extracted_negative = _clean(row.get("negative_prompt"))
method = f"{method}:krea2({kind})"
else:
prompt, extracted_negative, method = _fallback_text_to_krea(source_text, preserve_trigger, detail_level, style_mode)
if extra_positive.strip():
prompt = f"{prompt.rstrip()} {extra_positive.strip()}"
negative = _combine_negative(extracted_negative, negative_prompt, extra_negative)
return {
"krea_prompt": prompt,
"negative_prompt": negative,
"krea_softcore_prompt": "",
"krea_hardcore_prompt": "",
"softcore_negative_prompt": "",
"hardcore_negative_prompt": "",
"method": method,
}