Files

2724 lines
119 KiB
Python

from __future__ import annotations
import json
import re
from typing import Any
try:
from .prompt_hygiene import sanitize_negative_text, sanitize_prose_text
except ImportError: # Allows local smoke tests with `python -c`.
from prompt_hygiene import sanitize_negative_text, sanitize_prose_text
TRIGGER_CANDIDATES = (
"sxcpinup_coloredpencil",
"sxcppnl7",
)
HARDCORE_DETAIL_DENSITY_CHOICES = {"compact", "balanced", "dense"}
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
HARDCORE_ENVIRONMENT_ANCHOR_REPLACEMENTS = (
(r"\bstacked bodies on the bed\b", "close body alignment"),
(r"\bstacked bodies with close body alignment\b", "close body alignment"),
(r"\boverhead tangled-body anal frame\b", "overhead rear-entry anal frame"),
(r"\btangled-body\b", "close-body"),
(r"\bthree bodies tangled on the bed\b", "three bodies tangled in close contact"),
(r"\ba triangle of bodies on the mattress\b", "a triangle of bodies in close contact"),
(r"\bbodies tangled on the sheets\b", "bodies tangled in close contact"),
(r"\bwet bodies tangled on sheets\b", "wet bodies tangled in close contact"),
(r"\bbody arched on rumpled sheets\b", "body arched with clear skin contact"),
(r"\bface-down ass-up on the mattress\b", "face-down ass-up position"),
(r"\bsitting on the edge of the bed\b", "sitting on a raised edge"),
(r"\blying at the bed edge with thighs open\b", "lying near a raised edge with thighs open"),
(r"\bedge[- ]of[- ]bed\b", "edge-supported"),
(r"\bbed[- ]edge\b", "raised edge"),
(r"\bedge of (?:the )?bed\b", "raised edge"),
(r"\bbed edge\b", "raised edge"),
(r"\bhands? braced on the bed\b", "hands braced beside the body"),
(r"\bone hand pressing into the mattress\b", "one hand braced beside the body"),
(r"\bone foot planted on the bed\b", "one foot planted for leverage"),
(r"\bfingers gripping sheets and skin\b", "fingers gripping skin"),
(r"\bfingers gripping sheets\b", "fingers gripping skin"),
(r"\bhands gripping sheets\b", "hands gripping skin"),
(r"\bone hand gripping the sheets\b", "one hand gripping skin"),
(r"\brumpled bed sheets\b", "wrinkled body-contact fabric"),
(r"\bwet sheets beneath the bodies\b", "visible wetness beneath the bodies"),
(r"\bsexual fluids on sheets\b", "sexual fluids visible on skin"),
(r"\bcum dripping onto sheets\b", "cum visible on skin"),
(r"\bfluid dripping onto sheets\b", "fluid visible on skin"),
(r"\bsquirting fluid on the sheets\b", "squirting fluid visible on skin"),
(r"\bsoft sheets\b", "soft fabric"),
(r"\bsilk sheets\b", "silk fabric"),
(r"\bsheets\b", "fabric"),
(r"\bmattress\b", "low support surface"),
(r"\ba low support surface\b", "a low body support"),
(r"\ba low mattress\b", "a low body support"),
(r"\ba wide couch\b", "a wide body support"),
(r"\bwide couch\b", "wide body support"),
(r"\bcouch\b", "body support"),
(r"\bsofa\b", "body support"),
(r"\bon the bed\b", "on a body support"),
(r"\bon a bed\b", "on a body support"),
(r"\bbedroom-floor\b", "floor-level"),
(r"\bbedroom floor\b", "floor-level"),
)
def _sanitize_hardcore_environment_anchors(value: Any) -> str:
text = _clean(value)
if not text:
return ""
for pattern, replacement in HARDCORE_ENVIRONMENT_ANCHOR_REPLACEMENTS:
text = re.sub(pattern, replacement, text, flags=re.IGNORECASE)
text = re.sub(r"\s+,", ",", text)
text = re.sub(r",\s*,", ",", text)
text = re.sub(r"\s{2,}", " ", text)
return text.strip()
def _sanitize_hardcore_axis_values(values: Any) -> dict[str, str]:
if not isinstance(values, dict):
return {}
return {str(key): _sanitize_hardcore_environment_anchors(value) for key, value in values.items()}
def _is_false(value: Any) -> bool:
if isinstance(value, bool):
return value is False
if isinstance(value, str):
return value.strip().lower() in ("false", "0", "no", "off")
return False
def _expression_disabled(row: dict[str, Any]) -> bool:
return bool(row.get("expression_disabled")) or _is_false(row.get("expression_enabled", True))
def _normalize_hardcore_detail_density(value: Any) -> str:
text = _clean(value).lower()
return text if text in HARDCORE_DETAIL_DENSITY_CHOICES else "balanced"
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)
text = re.sub(
r"(^|[.!?]\s+)(the woman|the man)\b",
lambda match: match.group(1) + match.group(2).capitalize(),
text,
flags=re.IGNORECASE,
)
return text
def _lowercase_for_inline_join(text: str) -> str:
return re.sub(
r"^(The woman|The man|The viewer|The named adults)\b",
lambda match: match.group(1).lower(),
_clean(text),
flags=re.IGNORECASE,
)
def _cast_prose(
text: str,
central_label: str = "Woman A",
omit_labels: list[str] | set[str] | tuple[str, ...] = (),
) -> tuple[str, list[str]]:
raw_entries = _cast_entries(text)
omitted = set(omit_labels or [])
entries = [(label, descriptor) for label, descriptor in raw_entries if label not in omitted]
if raw_entries and not entries:
return "", []
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 _pov_labels_from_value(value: Any) -> list[str]:
labels: list[str] = []
if isinstance(value, list):
candidates = value
else:
candidates = re.split(r"[,;]\s*", _clean(value)) if _clean(value) else []
for candidate in candidates:
label = _clean(candidate)
if re.match(r"^Man [A-Z]$", label) and label not in labels:
labels.append(label)
return labels
def _merge_labels(*groups: list[str]) -> list[str]:
merged: list[str] = []
for group in groups:
for label in group:
if label and label not in merged:
merged.append(label)
return merged
def _filter_pov_labeled_clauses(text: Any, pov_labels: list[str]) -> str:
rendered = _clean(text)
if not rendered or not pov_labels:
return rendered
clauses = [clause.strip() for clause in rendered.split(";") if clause.strip()]
filtered = [
clause
for clause in clauses
if not any(re.match(rf"^{re.escape(label)}\b", clause) for label in pov_labels)
]
return "; ".join(filtered)
def _pov_ejaculation_target(context: str) -> str:
if any(token in context for token in ("face", "mouth", "lips", "tongue", "chin")):
return "onto her face and chest"
if any(token in context for token in ("lower back", "ass", "rear-entry", "face-down", "bent-over", "doggy")):
return "across her ass, thighs, and lower back"
if any(token in context for token in ("pussy", "open thighs", "thighs", "legs open")):
return "across her pussy and thighs"
return "onto her body"
def _pov_contact_clause(
action: Any,
role_graph: Any,
hard_item: Any,
axis_values: Any,
context: str,
) -> str:
is_climax = _is_climax_text(action, role_graph, hard_item, _axis_values_text(axis_values))
if is_climax:
return f"as he ejaculates semen {_pov_ejaculation_target(context)}"
is_anal = any(
token in context
for token in (
"anal",
"into her ass",
"penis entering ass",
"ass stretched",
"thrusts into her ass",
)
)
contact = "as his penis penetrates her ass" if is_anal else "as his penis penetrates her pussy"
if _is_toy_assisted_double_text(action, role_graph, hard_item, _axis_values_text(axis_values)):
contact = f"{contact} while a toy is positioned at the second penetration point"
return contact
def _pov_clean_detail(detail: Any, context: str, detail_density: str) -> str:
detail = _clean(detail).strip(" .;")
if not detail:
return ""
detail = re.sub(r"\bthe POV viewer\b", "the viewer", detail, flags=re.IGNORECASE)
detail = re.sub(r"\bthe man's\b", "the viewer's", detail, flags=re.IGNORECASE)
detail = re.sub(r"\bthe man\b", "the viewer", detail, flags=re.IGNORECASE)
detail = re.sub(
r"^(?:missionary|cowgirl|reverse cowgirl|doggy style|standing sex|spooning sex|edge-supported|edge-of-bed|raised edge|kneeling straddle|lotus sex|bent-over|face-down ass-up|side-lying|kneeling rear-entry)\s+(?:position|pose)\s+(?:featuring|with|while|,)?\s*",
"",
detail,
flags=re.IGNORECASE,
)
detail = re.sub(r"^(?:featuring|with)\s+", "", detail, flags=re.IGNORECASE)
detail = re.sub(
r"^(?:full-body|explicit|close-contact|deep|hardcore|vaginal|anal)?\s*(?:penetrative sex|vaginal sex|anal sex|penetration with visible genital contact|hardcore vaginal thrusting|hardcore anal thrusting),?\s*",
"",
detail,
flags=re.IGNORECASE,
)
detail = re.sub(
r"\b(?:front-facing|close-up|wide full-body|wide|overhead|mirror-reflected|low-angle|side-profile|bed-level)\s+view of\b",
"visible",
detail,
flags=re.IGNORECASE,
)
detail = re.sub(
r",?\s*\bthe viewer is behind her at hip level with (?:his|the viewer's) hands on her hips in the foreground as (?:his|the viewer's) penis (?:thrusts into her|penetrates her pussy)\b",
"",
detail,
flags=re.IGNORECASE,
)
detail = re.sub(
r",?\s*\bthe woman is on all fours directly in front of the viewer with hips raised and back arched\b",
"",
detail,
flags=re.IGNORECASE,
)
if any(token in context for token in ("ass raised", "on all fours", "doggy", "rear-entry", "bent-over", "face-down")):
detail = re.sub(
r",?\s*\b(?:one body pinned under another|bodies stacked close together|bodies tangled on the sheets)\b",
"",
detail,
flags=re.IGNORECASE,
)
if "toy is positioned at the second penetration point" in context:
detail = re.sub(
r",?\s*\b(?:toy aligned for a second penetration point|toy-assisted second contact aligned behind the body)\b",
"",
detail,
flags=re.IGNORECASE,
)
detail = re.sub(r"\bwith with\b", "with", detail, flags=re.IGNORECASE)
detail = re.sub(r"\s*,\s*", ", ", detail)
detail = re.sub(r",\s*,", ",", detail).strip(" ,;")
return _limit_detail_for_density(detail, _normalize_hardcore_detail_density(detail_density), _is_climax_text(context, detail))
def _pov_hardcore_pose_sentence(
action: Any,
role_graph: Any,
hard_item: Any,
composition: Any = "",
axis_values: Any = None,
detail_density: str = "balanced",
) -> str:
context = _position_context_text(role_graph, hard_item, composition, axis_values)
action_text = _clean(action)
action_lower = action_text.lower()
if not context:
context = action_lower
def sentence(base: str) -> str:
details = ""
if ";" in action_text:
details = _pov_clean_detail(action_text.split(";", 1)[1], f"{context} {base}", detail_density)
return f"{base}; {details}" if details else base
def outercourse_sentence(base: str) -> str:
return _clean(base).rstrip(".")
if (
"face-sitting" in context
or "face sitting" in context
or ("straddles" in context and "face" in context and "pussy" in context)
):
return outercourse_sentence(
"The woman is above the camera in a close first-person underview, straddling the viewer's face with her thighs on both sides of his head; "
"her pussy is directly over the viewer's mouth in the lower foreground, tongue contact visible from below"
)
if _is_outercourse_text(context, action_lower):
if any(term in context for term in ("boobjob", "titjob", "breast sex", "breast-sex")):
return outercourse_sentence(
"The woman kneels between the viewer's open thighs with her torso bent forward over his pelvis and shoulders low; "
"both hands lift and press her breasts tightly around the viewer's penis shaft in the lower foreground, with the glans just below her lips"
)
if any(term in context for term in ("testicle", "balls licking", "balls-licking", "balls and mouth")):
return outercourse_sentence(
"The woman kneels very low between the viewer's open thighs with her torso bent forward and shoulders between his knees; "
"her head is tucked under the penis shaft at the base of the penis, mouth and tongue licking the viewer's balls while his penis points upward above her face in the lower foreground"
)
if any(term in context for term in ("penis licking", "penis-licking", "tongue along", "tongue licking")):
return outercourse_sentence(
"The woman bends forward between the viewer's open thighs, head low under the viewer's penis with her face directly under the penis; "
"her tongue runs along the underside from the penis shaft to the glans while one hand steadies the base of the penis in the lower foreground"
)
if any(term in context for term in ("handjob", "hand job", "hand wrapped", "hand stroking", "manual stimulation")):
return outercourse_sentence(
"The woman kneels between the viewer's open thighs with her torso leaning forward and face visible behind the penis shaft; "
"one hand wraps around the penis shaft in the lower foreground while the other hand steadies the base of the penis as she strokes toward the glans"
)
if any(term in context for term in ("footjob", "soles", "toes curled", "feet stroking")):
return outercourse_sentence(
"The woman faces the viewer with her hips back, torso visible behind her raised legs, and both knees bent open toward the camera; "
"her soles wrap around the penis shaft in the lower foreground, toes curled around the penis shaft with her face visible beyond her feet"
)
return outercourse_sentence(
"The woman stays close to the viewer's pelvis, keeping the non-penetrative contact centered in the lower foreground with her face visible behind the contact"
)
penetrative_tokens = (
"penetrat",
"thrust",
"anal",
"cowgirl",
"missionary",
"doggy",
"rear-entry",
"spooning",
"side-lying",
"bent-over",
"face-down",
"ejaculat",
"semen",
"cumshot",
"climax",
)
if not any(token in context or token in action_lower for token in penetrative_tokens):
return ""
oral_only = any(token in context for token in ("oral", "blowjob", "cunnilingus", "mouth on", "penis in her mouth"))
if oral_only and not any(token in context for token in ("penetrat", "thrust", "anal", "ejaculat", "semen", "cumshot", "climax")):
return ""
contact = _pov_contact_clause(action, role_graph, hard_item, axis_values, context)
if "reverse cowgirl" in context:
return sentence(
"POV reverse cowgirl position: the viewer lies on his back while the woman straddles his hips facing away; "
f"her back, ass, thighs, and the viewer's foreground legs are visible {contact}"
)
if "cowgirl" in context or "straddling a partner" in context or "squatting on top" in context:
return sentence(
"POV cowgirl position: the viewer lies on his back while the woman straddles his hips facing him; "
f"her torso, hips, and open thighs fill the frame from below {contact}"
)
if "lotus" in context or "seated in a partner's lap" in context:
return sentence(
"POV lotus position: the viewer sits upright while the woman sits in his lap facing him with her legs around his hips; "
f"her torso and hips stay close to the viewer {contact}"
)
if "kneeling straddle" in context:
return sentence(
"POV kneeling straddle position: the viewer kneels upright while the woman straddles his hips facing him; "
f"both torsos are upright and her hips press directly against him {contact}"
)
if "face-down" in context or "face down" in context:
return sentence(
"The woman is seen from behind with her ass raised toward the POV viewer, lying face-down with hips lifted; "
f"the viewer looks down at her raised ass with foreground hands on her hips {contact}"
)
if "bent-over" in context or "bent over" in context or "bent forward" in context:
return sentence(
"The woman is seen from behind with her ass raised toward the POV viewer, bent forward at the waist with hips lifted and head turned back; "
f"the viewer looks down at her raised ass from behind with foreground hands near her hips {contact}"
)
if "doggy" in context or "all fours" in context or "rear-entry" in context:
return sentence(
"The woman is seen from behind with her ass raised toward the POV viewer, on all fours directly in front of him with hips high and back arched; "
f"the viewer looks down at her raised ass with his hands on her hips in the foreground {contact}"
)
if "standing" in context:
return sentence(
"POV standing rear-entry position: the woman stands braced in front of the viewer with hips angled back and legs steady; "
f"the viewer stands behind her at hip level {contact}"
)
if "spooning" in context or "side-lying" in context or "lies on her side" in context:
return sentence(
"POV side-lying sex position: the woman lies on her side in front of the viewer with thighs parted; "
f"the viewer is behind her along the same body line {contact}"
)
if (
"edge-supported" in context
or "raised edge" in context
or "edge of bed" in context
or "bed edge" in context
or "kneels between her legs" in context
):
return sentence(
"POV raised-edge penetration position: the woman reclines at the raised edge with thighs open toward the viewer; "
f"the viewer kneels between her legs with his hands near her hips {contact}"
)
if "missionary" in context or ("lies on her back" in context and ("legs open" in context or "thighs open" in context)):
return sentence(
"POV missionary position: the woman lies on her back with legs open around the viewer's hips; "
f"the viewer is above her with foreground arms braced beside her body {contact}"
)
return sentence(
"POV penetrative sex position: the woman is directly in front of the viewer with legs open around his hips; "
f"the viewer's foreground hands and body position define the first-person angle {contact}"
)
def _pov_action_phrase(
action: Any,
pov_labels: list[str],
role_graph: Any = "",
hard_item: Any = "",
composition: Any = "",
axis_values: Any = None,
detail_density: str = "balanced",
) -> str:
rendered = _clean(action)
if not rendered or not pov_labels:
return rendered
if "Man A" in pov_labels:
pov_sentence = _pov_hardcore_pose_sentence(
rendered,
role_graph,
hard_item,
composition,
axis_values,
detail_density,
)
if pov_sentence:
return pov_sentence
for label in sorted(pov_labels, key=len, reverse=True):
escaped = re.escape(label)
rendered = re.sub(rf"\b{escaped}'s\b", "the viewer's", rendered)
rendered = re.sub(rf"\b{escaped}\b", "the viewer", rendered)
if "Man A" in pov_labels:
rendered = re.sub(r"\bthe man's\b", "the viewer's", rendered, flags=re.IGNORECASE)
rendered = re.sub(r"\bthe man\b", "the viewer", rendered, flags=re.IGNORECASE)
rendered = re.sub(r"\bhe\b", "the viewer", rendered, flags=re.IGNORECASE)
rendered = re.sub(r"\bhim\b", "the viewer", rendered, flags=re.IGNORECASE)
rendered = re.sub(r"\bhis\b", "the viewer's", rendered, flags=re.IGNORECASE)
rendered = re.sub(
r"\bthe viewer lies on the viewer's back under her\b",
"the viewer reclines underneath her",
rendered,
flags=re.IGNORECASE,
)
rendered = re.sub(
r"\bthe viewer lies on the viewer's back\b",
"the viewer reclines",
rendered,
flags=re.IGNORECASE,
)
rendered = re.sub(r"\bthe viewer is positioned\b", "the POV camera is positioned", rendered, flags=re.IGNORECASE)
return rendered
def _pov_camera_phrase(pov_labels: list[str], softcore: bool = False) -> str:
if not pov_labels:
return ""
if softcore:
return (
"Camera is the male participant's first-person creator view in one continuous frame, with him implied by perspective or foreground cues"
)
return (
"Camera is the male participant's first-person view in one continuous frame; only his foreground hands or body cues appear"
)
def _pov_composition_text(composition: Any, pov_labels: list[str]) -> str:
text = _clean(composition)
if not text or not pov_labels:
return text
text = re.sub(r"\ball participants visible\b", "visible partners readable", text, flags=re.IGNORECASE)
text = re.sub(r"\ball adult bodies visible\b", "visible partners readable", text, flags=re.IGNORECASE)
text = re.sub(r"\ball bodies visible\b", "visible partners readable", text, flags=re.IGNORECASE)
text = re.sub(r"\ball three bodies readable\b", "visible partner bodies readable", text, flags=re.IGNORECASE)
text = re.sub(r"\bwide group-sex composition\b", "first-person group-sex POV composition", text, flags=re.IGNORECASE)
text = re.sub(
r",?\s*adapted for first-person POV with the POV participant kept off-camera\b",
"",
text,
flags=re.IGNORECASE,
)
text = re.sub(r",?\s*with the POV participant kept off-camera\b", "", text, flags=re.IGNORECASE)
return _clean(text)
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 _clothing_access_phrase(action_text: Any) -> str:
text = _clean(action_text).lower()
if any(term in text for term in ("cumshot", "ejaculat", "semen", "cum on", "cum across", "post-orgasm", "aftermath")):
return "leaving the body exposed for visible semen and aftermath"
if any(term in text for term in ("boobjob", "titjob", "breast sex", "handjob", "hand job", "footjob", "testicle", "balls", "penis licking", "non-penetrative")):
return "leaving the contact point unobstructed"
if any(term in text for term in ("oral", "blowjob", "fellatio", "mouth", "tongue")):
return "leaving the oral contact unobstructed"
if any(term in text for term in ("penetrat", "thrust", "penis entering", "vaginal", "anal")):
return "leaving the penetration point unobstructed"
return "leaving skin and body contact readable"
def _natural_clothing_state(text: Any, action_text: Any = "") -> str:
text = _clean(text)
if not text:
return ""
text = re.sub(r"^Clothing state:\s*", "", text, flags=re.IGNORECASE)
if re.search(r";\s*(?=(?:Woman|Man) [A-Z]\b)", text):
parts = [
_natural_clothing_state(part, action_text).rstrip(".")
for part in re.split(r";\s*(?=(?:Woman|Man) [A-Z]\b)", text)
if _clean(part)
]
return ". ".join(part for part in parts if part)
body_exposure = re.match(r"^Body exposure:\s*(.*?)\.?$", text, flags=re.IGNORECASE)
if body_exposure:
return _clean(body_exposure.group(1)).rstrip(".")
if re.search(r"\bfully nude\b|\bbody is fully exposed\b|\bno clothing covering\b", text, flags=re.IGNORECASE):
owner = "the woman"
owner_match = re.match(r"^\s*((?:Woman|Man) [A-Z])\b", text)
if owner_match:
owner = _natural_label_text(owner_match.group(1), ["Woman A", "Man A"]) or owner
return f"{owner.capitalize()}'s body is fully exposed, bare skin unobstructed"
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 or "fully exposed" in state or "no clothing covering" in state:
return f"{owner.capitalize()}'s body is fully exposed, bare skin unobstructed"
if "nude-adjacent" in state:
return f"{owner.capitalize()}'s body is partly exposed"
if "partially removed" in state or "pushed aside" in state:
return f"{owner.capitalize()}'s {outfit} is pushed aside or partly removed where needed, {_clothing_access_phrase(action_text)}"
if "keeps" in state:
return f"{owner.capitalize()} keeps the {outfit} on while {_clothing_access_phrase(action_text)}"
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 _axis_values_text(axis_values: Any) -> str:
if not isinstance(axis_values, dict):
return ""
priority = (
"position",
"body_position",
"body_arrangement",
"arrangement",
"angle",
"surface",
"body_contact",
"leg_detail",
"oral_act",
"oral_detail",
"penetration_act",
"penetration_detail",
"anal_act",
"double_act",
"threesome_act",
"group_act",
)
parts = [_clean(axis_values.get(key)) for key in priority if _clean(axis_values.get(key))]
return " ".join(parts)
def _position_context_text(role_graph: str, hard_item: str, composition: str = "", axis_values: Any = None) -> str:
return " ".join(
_clean(part).lower()
for part in (role_graph, hard_item, composition, _axis_values_text(axis_values))
if _clean(part)
)
def _is_outercourse_text(*parts: Any) -> bool:
text = " ".join(_clean(part).lower() for part in parts if _clean(part))
return any(
term in text
for term in (
"outercourse",
"non-penetrative",
"boobjob",
"titjob",
"breast sex",
"breast-sex",
"testicle",
"balls",
"balls licking",
"balls-licking",
"breasts tightly around",
"breasts around",
"penis licking",
"penis-licking",
"tongue along",
"tongue runs along",
"tongue running along",
"handjob",
"hand job",
"hand wrapped",
"hand stroking",
"hand wraps around",
"manual stimulation",
"fingering",
"fingers inside",
"fingers in pussy",
"hand on pussy",
"fingers on pussy",
"fingers sliding against the pussy",
"open-thigh manual",
"clit rubbing",
"clit",
"clitoris",
"mutual masturbation",
"footjob",
"soles wrap around",
"soles",
"toes curled",
"feet stroking",
)
)
def _is_oral_text(*parts: Any) -> bool:
text = " ".join(_clean(part).lower() for part in parts if _clean(part))
return any(
term in text
for term in (
"oral",
"fellatio",
"blowjob",
"deepthroat",
"penis sucking",
"penis in her mouth",
"penis in mouth",
"takes the man's penis",
"takes his penis",
"mouth at penis level",
"mouth on his penis",
"lips wrapped",
"cunnilingus",
"pussy licking",
"mouth on her pussy",
"mouth pressed to her pussy",
"face-sitting",
"sixty-nine",
)
)
def _is_foreplay_text(*parts: Any) -> bool:
text = " ".join(_clean(part).lower() for part in parts if _clean(part))
if not text:
return False
return any(
term in text
for term in (
"foreplay",
"pre-sex",
"before sex",
"before penetration",
"kissing",
"deep kiss",
"mouth-to-mouth",
"lips pressed",
"caressing",
"hands roaming",
"stroking skin",
"touching breasts",
"cupping a breast",
"hand on the cheek",
"cheek and jaw",
"fingers under the chin",
"undressing",
"removing clothing",
"removing clothes",
"pulling clothing",
"sliding straps",
"unbuttoning",
"body worship",
"nipple",
"mouth on skin",
"kissing down",
"ass grabbing",
"gripping the ass",
"thigh kissing",
"inner thighs",
"hair held",
"holding hair",
"hair pulled back",
"wrist",
"wrists",
"pinning",
"guided",
"guiding",
"turning the body",
"position transition",
"pulling onto the bed",
"lifting and spreading",
"spreading thighs",
"dirty talk",
"whispering",
"camera performance",
"presented directly to the camera",
"present her body",
"showing to camera",
"spread open for the camera",
"watching partner",
"waiting turn",
"group coordination",
"aftercare",
"cleanup",
"wiping",
"towel",
"post-sex",
"fingering",
"fingers inside",
"hand on pussy",
"fingers on pussy",
"clit rubbing",
"clit",
"clitoris",
"manual stimulation",
"mutual masturbation",
)
)
def _is_close_foreplay_text(*parts: Any) -> bool:
text = " ".join(_clean(part).lower() for part in parts if _clean(part))
if not text or not _is_foreplay_text(text):
return False
return any(
term in text
for term in (
"stand close",
"stand face-to-face",
"press their bodies",
"bodies pressed close",
"hips pressed close",
"mouth-to-mouth",
"deep kissing",
"heated kiss",
"hands pull clothing",
"pull clothing aside",
"clothing being removed",
)
)
def _sanitize_foreplay_detail(detail: str, role_graph: str = "", composition: str = "") -> str:
detail = _clean(detail)
if not detail:
return ""
if not _is_close_foreplay_text(role_graph, detail, composition):
return detail
detail = re.sub(
r"\b(?:raised edge|edge-supported|edge-of-bed|bed-edge)\s+undressing position\s+(?:featuring|while|with)\s+",
"",
detail,
flags=re.IGNORECASE,
)
detail = re.sub(
r"\b(?:standing kissing|wall-pressed kissing|mirror undressing)\s+position\s+(?:featuring|while|with)\s+",
"",
detail,
flags=re.IGNORECASE,
)
detail = re.sub(
r"\b(?:raised edge|edge-supported|edge-of-bed|bed-edge)\s+undressing position\b",
"close standing undressing",
detail,
flags=re.IGNORECASE,
)
detail = re.sub(r"\braised-edge open-thigh position\b", "close-body first-person position", detail, flags=re.IGNORECASE)
detail = re.sub(r"\s*,\s*", ", ", detail).strip(" ,;")
return _clean(detail)
def _is_vaginal_penetration_text(*parts: Any) -> bool:
text = " ".join(_clean(part).lower() for part in parts if _clean(part))
if not text or _is_outercourse_text(text) or _is_oral_text(text) or _is_foreplay_text(text):
return False
if any(term in text for term in ("anal", "double penetration", "double-penetration", "toy-assisted", "strap-on")):
return False
return any(
term in text
for term in (
"vaginal penetration",
"deep vaginal sex",
"explicit penetrative sex",
"penetrative sex",
"penis entering pussy",
"penis thrusts into her pussy",
"penis thrusts into the woman",
"pussy stretched around a penis",
"hardcore vaginal thrusting",
"full-body penetrative sex",
"close-contact vaginal sex",
"missionary position",
"cowgirl position",
"reverse cowgirl position",
"doggy style position",
"standing sex position",
"spooning sex position",
"edge-of-bed position",
"kneeling straddle position",
"lotus sex position",
"bent-over position",
)
)
def _is_toy_assisted_double_text(*parts: Any) -> bool:
text = " ".join(_clean(part).lower() for part in parts if _clean(part))
if "toy" not in text:
return False
return any(
token in text
for token in (
"double penetration",
"double-penetration",
"vaginal and anal penetration",
"second penetration point",
"second point of contact",
"second contact",
)
)
def _mentions_ass(text: str) -> bool:
return bool(
re.search(
r"\bass\b|ass[- ](?:up|raised|exposed|lifted)|spread cheeks|lower back and ass|cum (?:on|dripping from) ass|pussy, ass|ass and",
text,
)
)
def _mentions_rear_entry(text: str) -> bool:
return bool(
re.search(
r"ass[- ](?:up|raised|exposed|lifted|stretched)|penis entering ass|cum (?:on|dripping from) ass|spread cheeks|lower back and ass|pussy, ass|rear[- ]entry",
text,
)
)
def _hardcore_pose_anchor(role_graph: str, hard_item: str, composition: str = "", axis_values: Any = None) -> str:
text = _position_context_text(role_graph, hard_item, composition, axis_values)
item_text = " ".join(part for part in (_clean(hard_item).lower(), _axis_values_text(axis_values).lower()) if part)
position_text = ""
if isinstance(axis_values, dict):
position_text = _clean(axis_values.get("position", "")).lower()
if not text:
return ""
if _is_foreplay_text(role_graph, hard_item, composition, _axis_values_text(axis_values)):
return ""
if _is_outercourse_text(role_graph, hard_item, composition, _axis_values_text(axis_values)):
if any(term in text for term in ("boobjob", "titjob", "breast sex", "breast-sex")):
return "breast-sex outercourse pose"
if any(term in text for term in ("testicle", "balls licking", "balls-licking", "balls and mouth")):
return "testicle-sucking outercourse pose"
if any(term in text for term in ("penis licking", "penis-licking", "tongue along", "tongue licking")):
return "penis-licking outercourse pose"
if any(term in text for term in ("handjob", "hand job", "hand wrapped", "hand stroking", "manual stimulation")):
return "handjob outercourse pose"
if any(term in text for term in ("footjob", "soles", "toes curled", "feet stroking")):
return "footjob outercourse pose"
return "non-penetrative outercourse pose"
if _is_toy_assisted_double_text(role_graph, hard_item, composition, _axis_values_text(axis_values)):
if "face-down ass-up" in text or "face-down" in text:
return "toy-assisted face-down rear-entry double-penetration pose"
if "doggy style" in text or "doggy-style" in text or "all fours" in text or "rear-entry" in text:
return "toy-assisted rear-entry double-penetration pose"
if "bent-over" in text or "bent forward" in text:
return "toy-assisted bent-over double-penetration pose"
if "spooning anal" in text or "side-lying anal" in text or "side-lying" in text:
return "toy-assisted side-lying double-penetration pose"
if "edge-supported" in text or "bed-edge" in text or "edge-of-bed" in text:
return "toy-assisted edge-supported double-penetration pose"
if "standing anal" in text or "standing supported" in text or "standing" in text:
return "toy-assisted standing double-penetration pose"
if "kneeling anal" in text or "kneeling" in text:
return "toy-assisted kneeling rear-entry double-penetration pose"
return "toy-assisted rear-entry double-penetration pose"
if "double penetration" in text or "vaginal and anal penetration" in text or "front-and-back" in text:
if "face-down ass-up" in text:
return "face-down rear-entry double-penetration pose"
if "doggy style" in text or "doggy-style" in text:
return "doggy-style double-penetration pose"
if "bent-over" in text:
return "bent-over double-penetration pose"
if "spooning anal" in text or "side-lying anal" in text:
return "side-lying double-penetration pose"
if "bed-edge" in text or "edge-of-bed" in text:
return "bed-edge front-and-back double-penetration pose"
if "standing anal" in text or "standing supported" in text:
return "standing supported front-and-back double-penetration pose"
if "kneeling anal" in text:
return "kneeling rear-entry 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 "sixty-nine" in position_text or ("sixty-nine" in text and not position_text):
return "sixty-nine oral pose"
if "face-sitting" in position_text or ("face-sitting" in text and not position_text):
return "face-sitting oral pose"
if "side-lying oral" in position_text or (("side-lying oral position" in item_text or "side-lying oral" in text) and not position_text):
return "side-lying oral pose"
if (
"edge-of-bed oral" in position_text
or "edge-supported oral" in position_text
or (("edge-of-bed oral position" in item_text or "edge-of-bed oral" in text or "edge-supported oral" in text) and not position_text)
):
return "edge-supported oral pose"
if "standing oral" in position_text or (("standing oral position" in item_text or "standing oral" in text) and not position_text):
return "standing oral pose"
if "chair oral" in position_text or (("chair oral position" in item_text or "chair oral" in text) and not position_text):
return "chair oral pose"
if "kneeling oral" in position_text or (("kneeling oral position" in item_text or "kneeling oral" in text) and not position_text):
return "kneeling oral pose"
if "straddled oral" in position_text or (("straddled oral position" in item_text or "straddled oral" in text) and not position_text):
return "straddled cunnilingus pose"
if "reclining cunnilingus" in position_text or (("reclining cunnilingus position" in item_text or "reclining cunnilingus" in text) and not position_text):
return "reclining cunnilingus pose"
if "spread-leg oral" in position_text or (("spread-leg oral position" in item_text or "spread-leg oral" in text) and not position_text):
return "spread-leg 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-supported 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-supported 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 _mentions_rear_entry(text) or "rear-entry" in text:
if "face-down ass-up" in text:
return "face-down ass-up rear-entry anal pose"
if "doggy style" in text or "doggy-style" in text:
return "doggy-style anal pose"
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 "spooning anal" in text or "side-lying anal" in text:
return "side-lying rear-entry anal pose"
if "kneeling anal" in text:
return "kneeling rear-entry anal pose"
if "standing anal" in text:
return "standing rear-entry anal pose"
if "doggy" in text:
return "doggy-style anal pose"
return "rear-entry anal pose"
if "edge-supported" in text or "raised edge" in text or "edge-of-bed" in text or "bed-edge" in text:
return "edge-supported penetrative sex pose"
positions = (
"missionary",
"reverse cowgirl",
"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 re.search(r"(?<!non-)penetrat|thrust", text):
return "hip-aligned penetrative sex pose"
return ""
def _hardcore_pose_arrangement(anchor: str, role_graph: str, hard_item: str, composition: str = "", axis_values: Any = None) -> str:
text = _position_context_text(anchor, f"{role_graph} {hard_item}", composition, axis_values)
position_text = ""
if isinstance(axis_values, dict):
position_text = _clean(axis_values.get("position", "")).lower()
if not text:
return ""
mixed_woman_man = "the woman" in text and "the man" in text
is_double = "double-penetration" in text or "double penetration" in text
def cast_phrase(mixed: str, generic: str) -> str:
return mixed if mixed_woman_man else generic
def double_tail() -> str:
return "" if "toy" in text else ", with the second penetration point aligned"
if "sixty-nine" in position_text or ("sixty-nine" in text and not position_text):
return cast_phrase(
"with the woman and man inverted head-to-hips so both mouths align with genitals",
"with both bodies inverted head-to-hips so both mouths align with genitals",
)
if "face-sitting" in position_text or ("face-sitting" in text and not position_text):
return cast_phrase(
"with the man lying back while the woman straddles his face",
"with one partner lying back while the other straddles the face",
)
if (
"reclining cunnilingus" in position_text
or "spread-leg oral" in position_text
or (("reclining cunnilingus" in text or "spread-leg oral" in text) and not position_text)
):
if "takes the man's penis" in text or "penis in her mouth" in text:
return cast_phrase(
"with the man seated with legs apart and the woman positioned at his hips",
"with the receiver seated with legs apart and the giver positioned at the hips",
)
return cast_phrase(
"with the woman lying back, thighs spread, and the man positioned between her legs",
"with the receiving partner lying back, thighs spread, and the giver positioned between the legs",
)
if (
"straddled oral" in position_text
or (("straddled cunnilingus" in text or "straddled oral" in text) and not position_text)
):
return cast_phrase(
"with the woman straddling above the man's mouth and her thighs framing his face",
"with the receiver straddling above the giver's mouth",
)
if (
"edge-of-bed oral" in position_text
or "edge-supported oral" in position_text
or ("edge-of-bed oral" in text and not position_text)
or ("edge-supported oral" in text and not position_text)
):
if "takes the man's penis" in text or "penis in her mouth" in text:
return cast_phrase(
"with the man at a raised edge and the woman kneeling at his hips",
"with the receiver at a raised edge and the giver positioned at hip height",
)
return cast_phrase(
"with the woman lying at a raised edge and the man positioned between her open thighs",
"with the receiver lying at a raised edge and the giver positioned between open thighs",
)
if "standing oral" in position_text or ("standing oral" in text and not position_text):
if "takes the man's penis" in text or "penis in her mouth" in text:
return cast_phrase(
"with the man standing and the woman kneeling in front of his hips",
"with the receiver standing and the giver kneeling at hip height",
)
return cast_phrase(
"with the woman standing braced and the man kneeling between her thighs",
"with the receiver standing braced and the giver kneeling between the thighs",
)
if "chair oral" in position_text or ("chair oral" in text and not position_text):
if "takes the man's penis" in text or "penis in her mouth" in text:
return cast_phrase(
"with the man seated in the chair and the woman kneeling between his legs at hip level",
"with the receiver seated in the chair and the giver kneeling between the legs at hip level",
)
return cast_phrase(
"with one partner seated in a chair and the other kneeling between the open thighs",
"with the receiver seated in a chair and the giver kneeling between the open thighs",
)
if "side-lying oral" in position_text or ("side-lying oral" in text and not position_text):
return "with both bodies lying on their sides and mouth aligned to genitals"
if "kneeling oral" in position_text or ("kneeling oral" in text and not position_text):
if "takes the man's penis" in text or "penis in her mouth" in text:
return cast_phrase(
"with the woman kneeling in front of the man's hips, her mouth at penis level",
"with the giver kneeling in front of the receiver's hips",
)
if "mouth on her pussy" in text or "uses his mouth on" in text:
return cast_phrase(
"with the man kneeling between the woman's open thighs, his mouth at her pussy",
"with the giver kneeling between the receiver's open thighs",
)
return "with the giver kneeling at the receiver's hips"
if "reverse cowgirl" in text:
return cast_phrase(
"with the man lying on his back under the woman while she straddles his hips facing away",
"with the lower partner lying on their back while the upper partner straddles them facing away",
)
if "cowgirl" in text:
return cast_phrase(
"with the man lying on his back under the woman while she straddles his hips on top",
"with the lower partner lying on their back while the upper partner straddles their hips on top",
)
if "missionary" in text:
return cast_phrase(
"with the woman lying on her back under the man, legs open around his hips",
"with the receiving partner lying on their back under the penetrating partner, legs open around the hips",
)
if "lotus" in text:
return cast_phrase(
"with the man seated upright and the woman seated in his lap facing him, legs wrapped around his hips",
"with one partner seated upright and the other seated in their lap facing them, legs wrapped around the hips",
)
if "kneeling straddle" in text:
return cast_phrase(
"with the woman straddling the man's kneeling lap, both torsos upright and hips pressed together",
"with one partner straddling the other's kneeling lap, torsos upright and hips pressed together",
)
if "doggy-style" in text:
return cast_phrase(
f"with the woman on all fours and the man positioned behind her at hip level{double_tail() if is_double else ''}",
f"with the receiving partner on all fours and the penetrating partner positioned behind at hip level{double_tail() if is_double else ''}",
)
if "face-down" in text:
return cast_phrase(
f"with the woman face-down, hips raised, and the man positioned behind her{double_tail() if is_double else ''}",
f"with the receiving partner face-down, hips raised, and the penetrating partner positioned behind{double_tail() if is_double else ''}",
)
if "bent-over" in text:
return cast_phrase(
f"with the woman bent forward at the waist and the man positioned behind her{double_tail() if is_double else ''}",
f"with the receiving partner bent forward at the waist and the penetrating partner positioned behind{double_tail() if is_double else ''}",
)
if "spooning" in text or ("side-lying" in text and "oral" not in text):
return cast_phrase(
f"with both lying on their sides and the man positioned behind the woman{double_tail() if is_double else ''}",
f"with both bodies lying on their sides and the penetrating partner positioned behind{double_tail() if is_double else ''}",
)
if "edge-of-bed" in text or "bed-edge" in text:
return cast_phrase(
f"with the woman lying at the bed edge, hips at the edge, and the man kneeling between her legs{double_tail() if is_double else ''}",
f"with the receiver lying at the bed edge, hips at the edge, and the penetrating partner kneeling between the legs{double_tail() if is_double else ''}",
)
if "standing" in text:
return cast_phrase(
f"with the woman braced standing and the man aligned at her hips{double_tail() if is_double else ''}",
f"with both partners standing and the penetrating partner aligned at the receiver's hips{double_tail() if is_double else ''}",
)
if "kneeling" in text and ("anal" in text or "rear-entry" in text):
return cast_phrase(
f"with the woman kneeling forward and the man positioned behind her{double_tail() if is_double else ''}",
f"with the receiving partner kneeling forward and the penetrating partner positioned behind{double_tail() if is_double else ''}",
)
if "double-penetration" in text or "double penetration" in text:
if "toy" in text:
return cast_phrase(
"with the woman on all fours and the man positioned behind her at hip level",
"with the receiving body on all fours and the penetrating partner positioned behind at hip level",
)
if "from the front" in text:
return cast_phrase(
"with the woman held between the man behind her and a second partner in front",
"with the receiving body held between one partner behind and a second partner in front",
)
return cast_phrase(
"with the woman held in a front-and-back position so both contact points are visible",
"with the central body held in a front-and-back position so both contact points are visible",
)
if "anal" in text or _mentions_rear_entry(text) or "rear-entry" in text:
return cast_phrase(
"with the woman's hips raised, ass exposed, and the man positioned behind her",
"with the receiving partner's hips raised and the penetrating partner positioned behind",
)
if "cunnilingus" in text or "mouth on her pussy" in text or "pussy licking" in text:
return cast_phrase(
"with the woman's thighs open and the man's mouth pressed to her pussy",
"with the receiver's thighs open and the giver's mouth pressed to genitals",
)
if "oral" in text or "blowjob" in text or "penis in her mouth" in text or "penis in mouth" in text:
if "takes the man's penis in her mouth" in text or "penis in her mouth" in text:
return cast_phrase(
"with the woman's mouth at the man's hips",
"with the giver's mouth positioned at the receiver'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 re.search(r"(?<!non-)penetrat|thrust", text):
return "with hips aligned and legs open around the contact point"
return ""
def _arrangement_duplicates_role(arrangement: str, role_graph: str) -> bool:
arrangement_lower = _clean(arrangement).lower()
role_lower = _clean(role_graph).lower()
if not arrangement_lower or not role_lower:
return False
markers = (
"bed edge",
"on all fours",
"face-down",
"hips raised",
"bent forward",
"straddl",
"on her back",
"on their sides",
"on her side",
"seated in",
"sits in",
"lap",
"kneeling between",
"kneels between",
"kneeling in front",
"kneels in front",
"positioned behind",
"standing",
)
return any(marker in arrangement_lower and marker in role_lower for marker in markers)
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"^(?:orgasm|climax)\s+scene:\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"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"oral contact with mouth on the visible genitals",
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|edge-supported 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-supported oral": (r"edge-of-bed oral position", r"edge-supported oral position"),
"edge-of-bed oral": (r"edge-of-bed oral position", r"edge-supported 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",),
"face-down": (r"face-down ass-up position",),
"missionary": (r"missionary position",),
"reverse cowgirl": (r"reverse cowgirl position",),
"cowgirl": (r"cowgirl position",),
"doggy-style": (r"doggy style position",),
"edge-supported": (r"edge-of-bed position", r"edge-supported position", r"raised edge position"),
"edge-of-bed": (r"edge-of-bed position", r"edge-supported position"),
"lotus": (r"lotus sex position",),
"standing sex": (r"standing sex position",),
"spooning": (r"spooning sex position", r"spooning anal 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 _dedupe_toy_double_detail(detail: str) -> str:
detail = _clean(detail)
if not detail:
return ""
angle_view = (
r"(?:rear-view|side-profile|low-angle|mirror-reflected|overhead|close-up|wide full-body|front-facing with hips turned)"
)
toy_act = (
r"(?:penis and toy double penetration|toy-assisted vaginal and anal penetration at the same time|toy and strap-on double penetration)"
)
detail = re.sub(
rf"\b({angle_view}\s+view of\s+){toy_act}\b",
r"\1the rear-entry contact",
detail,
flags=re.IGNORECASE,
)
detail = re.sub(rf",?\s*\b{toy_act}\b", "", detail, flags=re.IGNORECASE)
duplicate_phrases = (
"toy-assisted second contact aligned behind the body",
"toy aligned for a second penetration point",
"rear-entry body alignment",
"close body alignment",
"stacked bodies in close contact",
"one body between two partners",
"one partner behind and one partner in front",
"two partners penetrating at once",
"one partner held between two bodies",
"front-and-back contact",
"three bodies locked together",
"kneeling center partner",
)
for phrase in duplicate_phrases:
detail = re.sub(rf",?\s*\b{re.escape(phrase)}\b", "", detail, flags=re.IGNORECASE)
detail = re.sub(r"^\s*,\s*", "", detail)
detail = re.sub(r",\s*,", ",", detail)
return _clean(detail).strip(" ,;")
def _dedupe_outercourse_detail(detail: str, role_graph: str, hard_item: str = "", axis_values: Any = None) -> str:
detail = _clean(detail)
if not detail:
return ""
context = _position_context_text(role_graph, hard_item, "", axis_values)
context_lower = context.lower()
breast_sex = any(term in context_lower for term in ("boobjob", "titjob", "breast sex", "breast-sex"))
clauses: list[str] = []
for clause in _detail_clauses(detail):
lower = clause.lower()
if breast_sex:
if lower in ("penis", "breasts", "mouth clearly visible"):
continue
if any(
term in lower
for term in (
"boobjob",
"titjob",
"breast-sex",
"breast sex",
"seated titjob position",
"kneeling boobjob position",
"tight close-up breast-sex position",
"penis shaft compressed between breasts",
"penis squeezed between both breasts",
"hands pressing the breasts tightly",
"hands pressing breasts firmly together",
"fingers spreading the breasts around the penis shaft",
"soft flesh squeezed around the penis shaft",
"hand wrapped around the penis shaft",
"glans near the mouth",
"glans visible",
"penis, breasts, and mouth clearly visible",
)
):
continue
clauses.append(clause)
return _join_detail_clauses(clauses)
def _dedupe_oral_detail(detail: str, role_graph: str, hard_item: str = "", axis_values: Any = None) -> str:
detail = _clean(detail)
if not detail:
return ""
context = _position_context_text(role_graph, hard_item, "", axis_values)
woman_gives = any(
term in context
for term in (
"takes the man's penis",
"takes his penis",
"penis in her mouth",
"mouth at penis level",
"mouth on his penis",
"fellatio",
"blowjob",
"deepthroat",
"penis sucking",
)
)
clauses: list[str] = []
for clause in _detail_clauses(detail):
lower = clause.lower()
if any(
term in lower
for term in (
"kneeling oral position",
"standing oral position",
"edge-of-bed oral position",
"side-lying oral position",
"chair oral position",
"reclining cunnilingus position",
"face-sitting position",
"sixty-nine position",
"fellatio with penis in mouth",
"deepthroat blowjob",
"penis sucking with visible saliva",
"cunnilingus with tongue on pussy",
"oral sex with tongue and fingers",
"oral contact with mouth on the visible genitals",
"bodies stacked close together",
"body angle keeps the penis and face readable",
)
):
continue
if woman_gives and lower == "wet shine on genitals":
clause = "saliva dripping on the penis"
clauses.append(clause)
return _join_detail_clauses(clauses)
def _dedupe_penetration_detail(detail: str, role_graph: str, hard_item: str = "", axis_values: Any = None) -> str:
detail = _clean(detail)
if not detail:
return ""
role_lower = _clean(role_graph).lower()
detail = re.sub(
r"\b(?:front-facing|side-profile|rear-view|overhead|mirror-reflected|low-angle|close-up|wide full-body)\s+view of\s+"
r"(?:vaginal penetration with visible genital contact|deep vaginal sex|explicit penetrative sex|penetrative sex|"
r"penis entering pussy|pussy stretched around a penis|hardcore vaginal thrusting|full-body penetrative sex|"
r"close-contact vaginal sex)\b,?\s*",
"",
detail,
flags=re.IGNORECASE,
)
act_terms = (
"vaginal penetration with visible genital contact",
"deep vaginal sex",
"explicit penetrative sex",
"penetrative sex",
"penis entering pussy",
"pussy stretched around a penis",
"hardcore vaginal thrusting",
"full-body penetrative sex",
"close-contact vaginal sex",
"missionary position",
"cowgirl position",
"reverse cowgirl position",
"doggy style position",
"standing sex position",
"spooning sex position",
"edge-of-bed position",
"kneeling straddle position",
"lotus sex position",
"bent-over position",
)
clauses: list[str] = []
for clause in _detail_clauses(detail):
lower = clause.lower()
if any(term in lower for term in act_terms):
continue
if lower in (
"tongues visible while kissing",
"deep kissing",
"mouth close to the ear",
"neck kissing",
"explicit genital contact visible",
"genitals clearly visible",
"anatomically clear penetration",
"pussy and penis visible",
"wetness visible between the thighs",
):
continue
if lower in ("legs spread wide", "thighs open toward the viewer") and any(
term in role_lower for term in ("legs spread wide", "thighs open", "open thighs")
):
continue
if lower == "one body pinned under another" and "lies under" in role_lower:
continue
if lower in ("hips locked tightly together", "hips aligned") and "hips" in role_lower:
continue
if lower in ("hands gripping hips", "hands spreading the thighs") and any(
term in role_lower for term in ("hips", "thighs", "legs")
):
continue
clauses.append(clause)
return _join_detail_clauses(clauses)
def _detail_clauses(detail: str) -> list[str]:
return [part.strip(" ,;") for part in re.split(r",\s*(?:and\s+)?", _clean(detail)) if part.strip(" ,;")]
def _join_detail_clauses(clauses: list[str]) -> str:
cleaned: list[str] = []
seen: set[str] = set()
for clause in clauses:
clause = _clean(clause).strip(" ,;")
key = clause.lower()
if clause and key not in seen:
cleaned.append(clause)
seen.add(key)
return ", ".join(cleaned)
def _action_position_phrase(action: str) -> str:
action = _clean(action).lower()
if _is_close_foreplay_text(action):
return "single-frame close-body first-person position"
if "pov reverse cowgirl" in action:
return "reverse-cowgirl first-person position"
if "pov cowgirl" in action:
return "cowgirl first-person position"
if "pov missionary" in action:
return "missionary first-person position"
if "pov raised-edge" in action or "raised edge" in action:
return "raised-edge open-thigh position"
if "pov doggy" in action or "on all fours" in action:
return "all-fours rear-entry position"
if "pov bent-over" in action or "bent forward" in action:
return "bent-over rear-entry position"
if "pov face-down" in action:
return "face-down rear-entry position"
if "pov standing" in action:
return "standing rear-entry position"
if "pov side-lying" in action:
return "side-lying position"
if "pov lotus" in action:
return "lap-straddling position"
if "face-down" in action and "ass raised" in action:
return "face-down raised-hip position"
if "on all fours" in action:
return "all-fours raised-hip position"
if "bends forward" in action or "bent forward" in action:
return "bent-over raised-hip position"
if "lies on her back" in action and ("thighs open" in action or "legs open" in action):
return "open-thigh reclined position"
if "lies at the bed edge" in action or "bed edge" in action:
return "bed-edge position"
if "lies on her side" in action:
return "side-lying position"
if "kneels in front" in action:
return "kneeling-at-hip-height position"
if "straddles" in action or "squats over" in action:
return "straddling position"
if "sits in the man's lap" in action:
return "lap-straddling position"
if "stands braced" in action:
return "standing braced position"
if "held between" in action or "front-and-back" in action:
return "front-and-back position"
if "lies between" in action:
return "between-partners position"
return ""
def _normalize_climax_view_clause(clause: str, role_graph: str) -> str:
lower = clause.lower()
if "view" not in lower and "frame" not in lower:
return clause
angle_match = re.search(
r"\b(front-facing|close-up|wide full-body|wide|overhead|mirror-reflected|low-angle|side-profile|bed-level)\b",
lower,
)
if not angle_match:
return clause
angle = angle_match.group(1)
if angle == "wide":
angle = "wide full-body"
position = _action_position_phrase(role_graph)
if position:
return f"{angle} aftermath view with the {position} readable"
return f"{angle} aftermath view"
def _climax_clause_duplicates_role(clause: str, role_graph: str) -> bool:
clause_lower = clause.lower()
role_lower = role_graph.lower()
role_has_ejaculation = any(token in role_lower for token in ("ejaculates semen", "visible semen", "semen lands"))
if role_has_ejaculation and re.search(
r"\b(?:cum clearly visible|explicit semen aftermath visible|hardcore ejaculation detail visible|"
r"post-ejaculation fluids anatomically clear|sexual fluids and body contact visible|"
r"visible external ejaculation|hardcore ejaculation scene|visible orgasm aftermath)\b",
clause_lower,
):
return True
duplicate_pairs = (
(("lower back", "ass"), ("lower back", "ass")),
(("ass",), ("ass",)),
(("pussy", "thigh"), ("pussy", "thigh")),
(("face", "lips"), ("face", "lips")),
(("tongue", "chin"), ("face", "lips", "mouth", "tongue")),
(("breast",), ("breast", "chest")),
(("belly",), ("belly", "torso")),
(("body",), ("body",)),
)
if any(token in clause_lower for token in ("cum", "semen", "fluid")):
for clause_tokens, role_tokens in duplicate_pairs:
if any(token in clause_lower for token in clause_tokens) and any(token in role_lower for token in role_tokens):
return True
return False
def _limit_detail_for_density(detail: str, density: str, is_climax: bool) -> str:
density = _normalize_hardcore_detail_density(density)
if density == "compact":
return ""
clauses = _detail_clauses(detail)
if not clauses:
return ""
if density == "balanced":
limit = 1 if is_climax else 2
else:
limit = 3 if is_climax else 4
return _join_detail_clauses(clauses[:limit])
def _is_climax_text(*parts: str) -> bool:
text = " ".join(_clean(part).lower() for part in parts if _clean(part))
return any(
token in text
for token in (
"cumshot",
"ejaculation",
"post-orgasm",
"post-climax",
"orgasm aftermath",
"orgasm scene",
"orgasm during",
"shared climax",
"hardcore climax",
"external cumshot",
"visible external ejaculation",
"climaxes on",
"climax lands",
)
)
def _climax_role_graph(role_graph: str, hard_item: str, axis_values: Any = None) -> str:
role_graph = _clean(role_graph).rstrip(".")
text = " ".join(part.lower() for part in (role_graph, _clean(hard_item), _axis_values_text(axis_values)) if part)
if "the woman" not in text or "the man" not in text:
return role_graph
if "lying between two partners" in text or "lies between" in text:
return "the woman lies between two partners, the man under her hips and another partner over her torso as visible semen lands on her body"
if "held between front-and-back partners" in text:
return "the woman is held between the man behind her and another partner in front of her as visible semen lands across her body"
if "kneeling between standing partners" in text:
return "the woman kneels between standing partners gathered around her face and torso for visible ejaculation"
if "side-lying with thighs parted" in text:
return "the woman lies on her side with thighs parted while the man kneels beside her hips and ejaculates semen across her thighs and pussy"
if "sitting on the edge of the bed" in text:
return "the woman sits on the edge of the bed with knees spread while the man stands close between her legs and ejaculates semen across her body"
if "lying at the bed edge with thighs open" in text:
return "the woman lies at the bed edge with thighs open while the man kneels between her legs and ejaculates semen across her pussy and thighs"
if "reclining with thighs open" in text or "lying on the back with legs spread" in text:
return "the woman lies on her back with thighs open while the man kneels between her legs and ejaculates semen across her pussy and thighs"
if "on all fours with hips raised" in text:
return "the woman is on all fours with hips raised while the man is positioned behind her and ejaculates semen across her ass, thighs, and lower back"
if "face-down ass-up" in text or "lies face-down" in text or "face down" in text:
return "the woman lies face-down with ass raised while the man is positioned behind her and ejaculates semen across her lower back and ass"
if "bent over with ass raised" in text or "bent over" in text:
return "the woman bends forward with hips raised while the man stands behind her with visible semen across her lower back, ass, and thighs"
if "kneeling with mouth open" in text:
return "the woman kneels in front of the man at hip height as he ejaculates semen onto her face, lips, and chest"
if "kneeling in front of a standing partner" in text:
return "the woman kneels in front of the man at hip height while he stands over her for visible ejaculation"
if "standing with cum on the body" in text:
return "the woman stands braced in front of the man while he stands close at hip level and ejaculates semen across her body"
if "squatting on top of a partner" in text:
return "the woman squats over the man's hips while the man lies on his back under her and ejaculates semen onto her body"
if "reverse cowgirl over a partner's hips" in text:
return "the woman straddles the man's hips facing away while the man lies on his back under her and ejaculates semen onto her body"
if "straddles" in text or "straddling a partner" in text or "straddling a partner's hips" in text or "shared climax after penetration" in text:
return "the woman straddles the man's hips while the man lies on his back under her and ejaculates semen onto her body"
if "seated in a partner's lap facing them" in text:
return "the woman sits in the man's lap facing him, legs wrapped around his hips as he ejaculates semen across her body"
if "lower back" in text or "cum dripping from ass" in text or "cum on lower back" in text or _mentions_rear_entry(text):
return "the woman bends forward with hips raised while the man stands behind her with visible semen across her lower back, ass, and thighs"
if "cum on face" in text or "cum on tongue" in text or "cum on lips" in text or "cum on tongue and chin" in text:
return "the woman kneels in front of the man at hip height as he ejaculates semen onto her face, lips, and chest"
if (
"cum dripping from pussy" in text
or "arousal dripping from pussy" in text
or "open thighs" in text
):
return "the woman lies on her back with thighs open while the man kneels between her legs and ejaculates semen across her pussy and thighs"
if role_graph:
return role_graph
return "the woman lies on her back with thighs open while the man kneels between her legs and ejaculates semen across her body"
def _dedupe_climax_detail(detail: str, role_graph: str, density: str = "balanced") -> str:
detail = _clean(detail)
lower = role_graph.lower()
patterns: list[str] = []
if "lies on her back" in lower:
patterns.extend((r"lying on the back with legs spread and hips lifted", r"reclining with thighs open", r"lying on the back with legs spread"))
detail = re.sub(r"\bcum on lower back and ass\b", "cum across thighs and pussy", detail, flags=re.IGNORECASE)
detail = re.sub(r"\bcum (?:on|dripping from) ass\b", "cum across thighs and pussy", detail, flags=re.IGNORECASE)
if "straddles" in lower:
patterns.extend(
(
r"straddling a partner's hips in cowgirl position",
r"reverse cowgirl over a partner's hips",
r"straddling a partner",
r"squatting on top of a partner",
)
)
if "squats over" in lower:
patterns.append(r"squatting on top of a partner")
if "sits in the man's lap" in lower:
patterns.append(r"seated in a partner's lap facing them")
if "bends forward" in lower:
patterns.append(r"bent over with ass raised")
if "on all fours" in lower:
patterns.append(r"on all fours with hips raised")
if "face-down" in lower:
patterns.append(r"face-down ass-up on the mattress")
if "lies on her side" in lower:
patterns.append(r"side-lying with thighs parted")
if "sits on the edge" in lower:
patterns.append(r"sitting on the edge of the bed")
if "bed edge" in lower:
patterns.append(r"lying at the bed edge with thighs open")
if "kneels in front" in lower:
patterns.extend((r"kneeling with mouth open", r"kneeling in front of a standing partner"))
if "stands braced" in lower:
patterns.append(r"standing with cum on the body")
for pattern in patterns:
detail = re.sub(rf"\b{pattern}\b,?\s*", "", detail, flags=re.IGNORECASE)
if not any(token in lower for token in ("face", "mouth", "lips", "tongue")):
detail = re.sub(r"\bsaliva and cum mixed on the mouth\b", "visible semen on skin", detail, flags=re.IGNORECASE)
detail = re.sub(r"\bcum on tongue and chin\b", "visible semen on skin", detail, flags=re.IGNORECASE)
detail = re.sub(r"\bcum on face and lips\b", "visible semen on skin", detail, flags=re.IGNORECASE)
detail = re.sub(r",\s*,", ",", detail)
detail = re.sub(r"\bwith\s*,\s*", "with ", detail, flags=re.IGNORECASE)
detail = re.sub(r"^with\s+", "", detail, flags=re.IGNORECASE)
detail = re.sub(r"^and\s+", "", detail, flags=re.IGNORECASE)
clauses: list[str] = []
for clause in _detail_clauses(detail):
normalized = _normalize_climax_view_clause(clause, role_graph)
if _climax_clause_duplicates_role(normalized, role_graph):
continue
if density != "dense" and normalized.lower() in ("orgasm during penetration", "post-orgasm visible release"):
continue
clauses.append(normalized)
return _limit_detail_for_density(_join_detail_clauses(clauses), density, True)
def _hardcore_action_sentence(
role_graph: str,
hard_item: str,
composition: str = "",
axis_values: Any = None,
detail_density: str = "balanced",
) -> str:
detail_density = _normalize_hardcore_detail_density(detail_density)
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 is positioned at the 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 is positioned at 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,
)
is_climax = _is_climax_text(role_graph, hard_item, composition, _axis_values_text(axis_values))
if is_climax:
role_graph = _climax_role_graph(role_graph, hard_item, axis_values)
detail = _hardcore_item_detail(hard_item)
anchor = _hardcore_pose_anchor(role_graph, hard_item, composition, axis_values)
is_outercourse = _is_outercourse_text(role_graph, hard_item, composition, _axis_values_text(axis_values))
is_oral = _is_oral_text(role_graph, hard_item, composition, _axis_values_text(axis_values))
is_penetrative = _is_vaginal_penetration_text(role_graph, hard_item, composition, _axis_values_text(axis_values))
if _is_toy_assisted_double_text(role_graph, hard_item, composition, _axis_values_text(axis_values)):
role_graph = re.sub(
r"\s+while a toy adds (?:the|a) second penetration point\b",
" while a toy is positioned at the second penetration point",
role_graph,
flags=re.IGNORECASE,
)
if is_climax:
anchor = ""
detail = _dedupe_climax_detail(detail, role_graph, detail_density)
elif _is_foreplay_text(role_graph, hard_item, composition, _axis_values_text(axis_values)):
anchor = ""
detail = _sanitize_foreplay_detail(detail, role_graph, composition)
detail = _limit_detail_for_density(detail, detail_density, False)
elif is_outercourse:
anchor = ""
detail = _dedupe_outercourse_detail(detail, role_graph, hard_item, axis_values)
detail = _limit_detail_for_density(detail, detail_density, False)
elif is_oral and role_graph:
anchor = ""
detail = _dedupe_oral_detail(detail, role_graph, hard_item, axis_values)
detail = _limit_detail_for_density(detail, detail_density, False)
elif is_penetrative and role_graph:
anchor = ""
detail = _dedupe_penetration_detail(detail, role_graph, hard_item, axis_values)
detail = _limit_detail_for_density(detail, detail_density, False)
else:
detail = _dedupe_hardcore_detail(detail, anchor) if anchor else detail
if _is_toy_assisted_double_text(role_graph, hard_item, composition, _axis_values_text(axis_values)):
detail = _dedupe_toy_double_detail(detail)
detail = _limit_detail_for_density(detail, detail_density, False)
arrangement = _hardcore_pose_arrangement(anchor, role_graph, hard_item, composition, axis_values)
anchor_phrase = _with_indefinite_article(anchor) if anchor else ""
if arrangement and anchor_phrase and not _arrangement_duplicates_role(arrangement, role_graph):
anchor_phrase = f"{anchor_phrase} {arrangement}"
if role_graph and anchor_phrase:
sentence = f"In {anchor_phrase}, {_lowercase_for_inline_join(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",
detail_density: str = "balanced",
) -> str:
composition = _clean(composition)
if not composition:
return ""
action_lower = _clean(action).lower()
composition_lower = composition.lower()
detail_density = _normalize_hardcore_detail_density(detail_density)
if "first-person underview" in action_lower or "straddling the viewer's face" in action_lower:
if any(token in composition_lower for token in ("mirror-reflected", "oral scene", "face and body visible")):
return (
f"{prefix} close first-person underview with the woman's thighs framing the camera and the oral contact centered"
)
if "pov viewer" in action_lower and any(
token in action_lower
for token in ("ass raised", "seen from behind", "on all fours", "bent forward", "face-down")
):
return (
f"{prefix} first-person rear-view frame looking down at the woman's raised ass, with foreground hands and rear-entry contact readable"
)
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 ""
if _is_outercourse_text(action_lower):
return f"{prefix} {composition}"
position = _action_position_phrase(action)
close_or_aftermath = any(
token in composition_lower
for token in ("close-up", "close crop", "tight", "direct-flash", "subscriber-view", "post-ejaculation", "aftermath")
)
if _is_close_foreplay_text(action_lower) and close_or_aftermath:
return f"{prefix} {composition}, in one continuous first-person close-body frame"
if position and close_or_aftermath:
if detail_density == "compact":
return f"{prefix} {composition}, with the {position} still readable"
return f"{prefix} {composition}, keeping the {position} and action geography readable"
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 _expression_phrase(expression: Any) -> str:
expression = _clean(expression)
if not expression:
return ""
if ";" in expression or re.search(
r"\b(?:Woman|Man) [A-Z] has\b|\bthe (?:woman|man) has\b",
expression,
flags=re.IGNORECASE,
):
return f"Expressions: {expression}"
return f"with {expression}"
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 ""
custom = _clean(config.get("custom_camera_prompt"))
if custom:
base = _clean(config.get("camera_mode")).replace("_", " ")
pieces = [piece for piece in (base, custom) if piece and piece != "standard"]
return "Camera: " + ", ".join(pieces)
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_scene_phrase(row: dict[str, Any]) -> str:
return _clean(row.get("camera_scene_directive"))
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 ""
custom = _clean(config.get("custom_camera_prompt"))
if custom:
base = _clean(config.get("camera_mode")).replace("_", " ")
pieces = [piece for piece in (base, custom) if piece and piece != "standard"]
return "Camera: " + ", ".join(pieces)
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 = ""
if not _expression_disabled(row):
expression = _row_value(row, "character_expression_text") or _row_value(row, "expression", ("Facial expressions", "Facial expression"))
composition = re.sub(r"^vertical\s+", "", _row_value(row, "composition", ("Composition",)), flags=re.IGNORECASE)
source_composition = re.sub(
r"^vertical\s+",
"",
_clean(row.get("source_composition")) or composition,
flags=re.IGNORECASE,
)
camera = _camera_phrase(row)
camera_scene = _camera_scene_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")
)
pov_labels = _pov_labels_from_value(row.get("pov_character_labels"))
if pov_labels:
camera = ""
cast_prose, cast_labels = _cast_prose(cast_descriptor_text, omit_labels=pov_labels)
if not cast_labels and women_count == 1 and men_count == 1:
cast_labels = ["Woman A", "Man A"]
cast_labels = _merge_labels(cast_labels, pov_labels)
expression = _filter_pov_labeled_clauses(expression, pov_labels)
expression = _natural_label_text(expression, cast_labels)
composition = _sanitize_hardcore_environment_anchors(composition)
source_composition = _sanitize_hardcore_environment_anchors(source_composition)
role_graph = _sanitize_scene_text_for_cast(
_sanitize_hardcore_environment_anchors(row.get("source_role_graph") or row.get("role_graph")),
cast_labels,
)
item = _sanitize_scene_text_for_cast(_sanitize_hardcore_environment_anchors(item), cast_labels)
role_graph = _natural_label_text(role_graph, cast_labels)
item = _natural_label_text(item, cast_labels)
axis_values = _sanitize_hardcore_axis_values(row.get("item_axis_values"))
detail_density = _normalize_hardcore_detail_density(row.get("hardcore_detail_density"))
action = _hardcore_action_sentence(role_graph, item, source_composition, axis_values, detail_density)
action = _pov_action_phrase(action, pov_labels, role_graph, item, source_composition, axis_values, detail_density)
output_composition = _pov_composition_text(composition, pov_labels)
parts = [
action,
_pov_camera_phrase(pov_labels),
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 "",
camera_scene,
_expression_phrase(expression),
_composition_phrase(output_composition, action, "The image is framed as", detail_density),
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 "",
camera_scene,
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 "",
camera_scene,
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 "",
camera_scene,
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_camera_scene = _camera_scene_phrase(soft) or _clean(row.get("softcore_camera_scene_directive"))
hard_camera_scene = _camera_scene_phrase(hard) or _clean(row.get("hardcore_camera_scene_directive"))
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 = _sanitize_hardcore_environment_anchors(hard.get("composition"))
hard_source_composition = _sanitize_hardcore_environment_anchors(hard.get("source_composition") or hard_composition)
pov_labels = _merge_labels(
_pov_labels_from_value(row.get("pov_character_labels")),
_pov_labels_from_value(soft.get("pov_character_labels")),
_pov_labels_from_value(hard.get("pov_character_labels")),
)
if pov_labels:
hard_camera = ""
if options.get("softcore_cast") == "same_as_hardcore":
soft_camera = ""
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,
omit_labels=pov_labels if options.get("softcore_cast") == "same_as_hardcore" else [],
)
hard_cast_prose, hard_labels = _cast_prose(cast_descriptor_text, omit_labels=pov_labels)
soft_labels = _merge_labels(soft_labels, pov_labels if options.get("softcore_cast") == "same_as_hardcore" else [])
hard_labels = _merge_labels(hard_labels, pov_labels)
hard_item = _sanitize_scene_text_for_cast(_sanitize_hardcore_environment_anchors(hard.get("item")), hard_labels)
hard_role_graph = _sanitize_scene_text_for_cast(
_sanitize_hardcore_environment_anchors(hard.get("source_role_graph") or 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_axis_values = _sanitize_hardcore_axis_values(hard.get("item_axis_values"))
hard_detail_density = _normalize_hardcore_detail_density(
hard.get("hardcore_detail_density") or row.get("hardcore_detail_density") or options.get("hardcore_detail_density")
)
hard_action = _hardcore_action_sentence(
hard_role_graph,
hard_item,
hard_source_composition,
hard_axis_values,
hard_detail_density,
)
hard_action = _pov_action_phrase(
hard_action,
pov_labels,
hard_role_graph,
hard_item,
hard_source_composition,
hard_axis_values,
hard_detail_density,
)
hard_output_composition = _pov_composition_text(hard_composition, pov_labels)
same_soft_cast = options.get("softcore_cast") == "same_as_hardcore"
soft_output_composition = _pov_composition_text(soft.get("composition"), pov_labels if same_soft_cast else [])
if same_soft_cast and pov_labels:
soft_cast_presence = (
"the woman is framed from the POV participant's first-person camera in a soft creator-teaser pose, "
"with the POV participant kept off-camera as the viewpoint and implied by camera position or foreground cues"
)
else:
soft_cast_presence = (
f"{_label_join(soft_labels)} share the frame in a soft creator-teaser pose"
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 = _filter_pov_labeled_clauses(partner_outfit_text, pov_labels)
if pov_labels:
partner_pose = ""
partner_outfit_text = _natural_label_text(partner_outfit_text, soft_labels)
soft_expression = ""
if not _expression_disabled(soft):
soft_expression_source = _filter_pov_labeled_clauses(
_clean(soft.get("character_expression_text")) or _clean(soft.get("expression")),
pov_labels,
)
soft_expression = _natural_label_text(
soft_expression_source,
soft_labels,
)
hard_expression = ""
if not _expression_disabled(hard):
hard_expression_source = _filter_pov_labeled_clauses(
_clean(hard.get("character_expression_text")) or _clean(hard.get("expression")),
pov_labels,
)
hard_expression = _natural_label_text(
hard_expression_source,
hard_labels,
)
soft_item = _clean(soft.get("item"))
soft_item_label = _clean(soft.get("softcore_item_prompt_label"))
soft_item_phrase = ""
if soft_item:
soft_item_phrase = f"body exposure: {soft_item}" if soft_item_label == "Body exposure" else f"wearing {soft_item}"
soft_parts = [
soft_cast_prose,
soft_cast_presence,
partner_outfit_text,
partner_pose,
_pov_camera_phrase(pov_labels, softcore=True) if same_soft_cast else "",
soft_item_phrase,
f"{soft.get('pose')}" if soft.get("pose") else "",
_expression_phrase(soft_expression),
f"in {soft.get('scene_text')}" if soft.get("scene_text") else "",
soft_camera_scene,
f"framed as {soft_output_composition}" if soft_output_composition else "",
soft_camera,
soft_style if detail_level != "concise" else "",
]
hard_parts = [
hard_action,
_pov_camera_phrase(pov_labels),
_natural_label_text(
_filter_pov_labeled_clauses(_natural_clothing_state(row.get("hardcore_clothing_state"), hard_action), pov_labels),
hard_labels,
),
hard_cast_prose,
f"set in {hard_scene}" if hard_scene else "",
hard_camera_scene,
_expression_phrase(hard_expression),
_composition_phrase(hard_output_composition, hard_action, detail_density=hard_detail_density),
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)
if extra_positive.strip():
soft_prompt = f"{soft_prompt.rstrip()} {extra_positive.strip()}"
hard_prompt = f"{hard_prompt.rstrip()} {extra_positive.strip()}"
soft_prompt = sanitize_prose_text(soft_prompt, triggers=TRIGGER_CANDIDATES)
hard_prompt = sanitize_prose_text(hard_prompt, triggers=TRIGGER_CANDIDATES)
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
negative = sanitize_negative_text(_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": sanitize_negative_text(_combine_negative(soft_negative, extra_negative)),
"hardcore_negative_prompt": sanitize_negative_text(_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()}"
prompt = sanitize_prose_text(prompt, triggers=TRIGGER_CANDIDATES)
negative = sanitize_negative_text(_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,
}