Add chainable character slot controls

This commit is contained in:
2026-06-24 15:10:24 +02:00
parent cb35e1881f
commit a7743cfd4b
6 changed files with 671 additions and 29 deletions
+84 -13
View File
@@ -14,6 +14,8 @@ PROMPT_FIELD_LABELS = (
"Ages",
"Body types",
"Cast",
"Cast descriptors",
"Characters",
"Scene",
"Setting",
"Pose",
@@ -168,6 +170,65 @@ 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 len(labels) == 1:
return labels[0]
if len(labels) == 2:
return f"{labels[0]} and {labels[1]}"
return f"{', '.join(labels[:-1])}, and {labels[-1]}"
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]
count_phrase = "one named adult" if len(entries) == 1 else f"{len(entries)} named adults"
sentences = [f"The scene contains {count_phrase}."]
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"\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{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)
text = re.sub(r";\s*softcore visual reference:\s*", ". Softcore visual reference: ", text, flags=re.IGNORECASE)
return text
def _clean_age(age: Any) -> str:
return _clean(age)
@@ -292,10 +353,17 @@ def _normal_row_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
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"))
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)
role_graph = _clean(row.get("role_graph"))
parts = [
f"A consensual explicit adult scene with {subject}, all participants 21+ and visibly adult",
f"The cast includes {cast}" if cast else "",
f"A consensual explicit adult scene with {subject}",
cast_prose,
f"The cast includes {cast}" if cast and not cast_prose else "",
role_graph,
f"The sexual action is {item}" if item else "",
f"The setting is {scene}" if scene else "",
@@ -380,9 +448,13 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
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)
same_soft_cast = options.get("softcore_cast") == "same_as_hardcore"
soft_cast_presence = (
"Woman A and the listed partners are present together in a non-explicit teaser pose, with no sex act or genital contact"
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 softcore version focuses on Woman A alone"
)
@@ -396,12 +468,11 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
partner_pose = ""
soft_parts = [
f"Cast descriptors: {soft_cast_descriptor_text}" if same_soft_cast and soft_cast_descriptor_text else "",
soft_cast_descriptor_text if not same_soft_cast and soft_cast_descriptor_text else "",
f"Softcore {soft_level or 'creator'} Insta/OF image",
soft_cast_prose,
soft_cast_presence,
f"Partner softcore styling: {partner_outfit_text}" if partner_outfit_text else "",
f"Cast pose: {partner_pose}" if partner_pose else "",
f"shown in a {soft_level or 'softcore'} Insta/OF creator image",
partner_outfit_text,
f"The cast is {partner_pose}" if partner_pose else "",
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 "",
@@ -411,11 +482,11 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
soft_style if detail_level != "concise" else "",
]
hard_parts = [
f"{hard_level or 'hardcore'} scene with Woman A visually central",
f"Cast descriptors: {cast_descriptor_text}" if cast_descriptor_text else "",
_clean(row.get("hardcore_clothing_state")),
_clean(hard.get("role_graph")),
f"The sexual action is {hard.get('item')}" if hard.get("item") else "",
f"{hard_level or 'hardcore'} scene",
hard_cast_prose,
_natural_clothing_state(row.get("hardcore_clothing_state")),
hard_role_graph,
f"The explicit detail shows {hard_item}" if hard_item else "",
f"set in {hard_scene}" if hard_scene else "",
f"with {hard.get('expression')}" if hard.get("expression") else "",
f"framed as {hard_composition}" if hard_composition else "",