Tighten hardcore Krea action wording

This commit is contained in:
2026-06-24 16:00:01 +02:00
parent 81d9b20db7
commit 8589035a07
4 changed files with 215 additions and 70 deletions
+53 -5
View File
@@ -83,6 +83,52 @@ def _human_join(parts: list[str]) -> str:
return f"{', '.join(parts[:-1])}, and {parts[-1]}" return f"{', '.join(parts[:-1])}, and {parts[-1]}"
def _prompt_cast_descriptors(text: str) -> str:
return _clean_text(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_text(part)
match = re.match(r"^((?:Woman|Man) [A-Z]):\s*(.+)$", part)
if match:
entries.append((match.group(1), _clean_text(match.group(2))))
return entries
def _natural_cast_descriptor_text(text: str) -> str:
entries = _cast_entries(text)
if not entries:
return _clean_text(text)
labels = [label for label, _descriptor in entries]
if labels == ["Woman A"] or labels == ["Man A"]:
return f"A {entries[0][1]}"
if set(labels) == {"Woman A", "Man A"} and len(labels) == 2:
by_label = {label: descriptor for label, descriptor in entries}
return f"A {by_label['Woman A']} alongside a {by_label['Man A']}"
return " ".join(f"{label} is {descriptor}." for label, descriptor in entries)
def _cast_labels(text: str) -> list[str]:
return [label for label, _descriptor in _cast_entries(text)]
def _natural_label_text(text: Any, labels: list[str]) -> str:
text = _clean_text(text)
if not text:
return ""
if set(labels) == {"Woman A", "Man A"}:
text = re.sub(r"\bWoman A\b", "the woman", text)
text = re.sub(r"\bMan A\b", "the man", text)
elif labels == ["Woman A"]:
text = re.sub(r"\bWoman A\b", "the woman", text)
elif labels == ["Man A"]:
text = re.sub(r"\bMan A\b", "the man", text)
return text
def _strip_style_tail(text: str) -> str: def _strip_style_tail(text: str) -> str:
text = _clean_text(text) text = _clean_text(text)
for tail in STYLE_TAILS: for tail in STYLE_TAILS:
@@ -428,7 +474,7 @@ def _configured_cast_from_row(row: dict[str, Any], detail_level: str, keep_style
parts = [f"{_cap_first(subject)} {verb} shown as a consensual {scene_kind}"] parts = [f"{_cap_first(subject)} {verb} shown as a consensual {scene_kind}"]
if cast_descriptor_text: if cast_descriptor_text:
parts.append(f"The named characters are {cast_descriptor_text}") parts.append(_natural_cast_descriptor_text(cast_descriptor_text))
if cast and not cast_descriptor_text: if cast and not cast_descriptor_text:
parts.append(f"The cast is {cast}") parts.append(f"The cast is {cast}")
if role_graph: if role_graph:
@@ -505,19 +551,20 @@ def _insta_of_pair_from_row(row: dict[str, Any], detail_level: str, keep_style:
options = row.get("options") if isinstance(row.get("options"), dict) else {} options = row.get("options") if isinstance(row.get("options"), dict) else {}
cast_descriptors = row.get("shared_cast_descriptors") cast_descriptors = row.get("shared_cast_descriptors")
if isinstance(cast_descriptors, list): if isinstance(cast_descriptors, list):
cast_descriptor_text = _human_join([_clean_text(item) for item in cast_descriptors if _clean_text(item)]) cast_descriptor_text = "; ".join(_clean_text(item) for item in cast_descriptors if _clean_text(item))
else: else:
cast_descriptor_text = _clean_text(cast_descriptors) cast_descriptor_text = _clean_text(cast_descriptors)
labels = _cast_labels(cast_descriptor_text)
same_soft_cast = options.get("softcore_cast") == "same_as_hardcore" same_soft_cast = options.get("softcore_cast") == "same_as_hardcore"
parts = [] parts = []
if cast_descriptor_text and same_soft_cast: if cast_descriptor_text and same_soft_cast:
parts.append(f"The shared cast descriptors are {cast_descriptor_text}") parts.append(_natural_cast_descriptor_text(cast_descriptor_text))
elif descriptor: elif descriptor:
parts.append(f"The softcore primary creator descriptor is {descriptor}") parts.append(f"A {descriptor}")
if cast_descriptor_text and not same_soft_cast: if cast_descriptor_text and not same_soft_cast:
parts.append(f"The hardcore cast descriptors are {cast_descriptor_text}") parts.append(_natural_cast_descriptor_text(cast_descriptor_text))
if same_soft_cast: if same_soft_cast:
parts.append("The softcore version keeps the same adult cast present together in a non-explicit teaser setup") parts.append("The softcore version keeps the same adult cast present together in a non-explicit teaser setup")
partner_styling = row.get("softcore_partner_styling") partner_styling = row.get("softcore_partner_styling")
@@ -525,6 +572,7 @@ def _insta_of_pair_from_row(row: dict[str, Any], detail_level: str, keep_style:
outfits = partner_styling.get("outfits") outfits = partner_styling.get("outfits")
if isinstance(outfits, list): if isinstance(outfits, list):
outfit_text = _human_join([_clean_text(item) for item in outfits if _clean_text(item)]) outfit_text = _human_join([_clean_text(item) for item in outfits if _clean_text(item)])
outfit_text = _natural_label_text(outfit_text, labels)
if outfit_text: if outfit_text:
parts.append(f"Softcore partner styling: {outfit_text}") parts.append(f"Softcore partner styling: {outfit_text}")
pose = _clean_text(partner_styling.get("pose")) pose = _clean_text(partner_styling.get("pose"))
+25 -25
View File
@@ -189,7 +189,7 @@
"cast": "women_only" "cast": "women_only"
}, },
{ {
"text": "cock entering ass between men", "text": "penis entering ass between men",
"cast": "men_only" "cast": "men_only"
}, },
{ {
@@ -203,8 +203,8 @@
"vaginal penetration with visible genital contact", "vaginal penetration with visible genital contact",
"deep vaginal sex", "deep vaginal sex",
"explicit penetrative sex", "explicit penetrative sex",
"cock entering pussy", "penis entering pussy",
"pussy stretched around a cock", "pussy stretched around a penis",
"hardcore vaginal thrusting", "hardcore vaginal thrusting",
"full-body penetrative sex", "full-body penetrative sex",
"close-contact vaginal sex" "close-contact vaginal sex"
@@ -244,7 +244,7 @@
"visibility": [ "visibility": [
"genitals clearly visible", "genitals clearly visible",
"penetration clearly visible", "penetration clearly visible",
"pussy and cock visible", "pussy and penis visible",
"explicit genital contact visible", "explicit genital contact visible",
"wetness visible between the thighs", "wetness visible between the thighs",
"hips framed in the foreground", "hips framed in the foreground",
@@ -317,7 +317,7 @@
"hand_detail": [ "hand_detail": [
"hands spreading thighs", "hands spreading thighs",
"hands holding hips", "hands holding hips",
"one hand stroking a cock", "one hand stroking a penis",
"fingers gripping sheets", "fingers gripping sheets",
"hands holding the head gently in place", "hands holding the head gently in place",
"hands cupping breasts", "hands cupping breasts",
@@ -332,17 +332,17 @@
"deep mouth contact", "deep mouth contact",
"tongue pressed to genitals", "tongue pressed to genitals",
"saliva shining on lips", "saliva shining on lips",
"mouth stretched around a cock" "mouth stretched around a penis"
], ],
"oral_act": [ "oral_act": [
"fellatio with cock in mouth", "fellatio with penis in mouth",
"deepthroat blowjob", "deepthroat blowjob",
"cunnilingus with tongue on pussy", "cunnilingus with tongue on pussy",
"face-sitting cunnilingus", "face-sitting cunnilingus",
"sixty-nine oral sex", "sixty-nine oral sex",
{"text": "blowjob while another partner watches", "min_people": 3}, {"text": "blowjob while another partner watches", "min_people": 3},
"pussy licking with thighs spread", "pussy licking with thighs spread",
"cock sucking with visible saliva", "penis sucking with visible saliva",
"oral sex with tongue and fingers", "oral sex with tongue and fingers",
"mouth on genitals with explicit contact" "mouth on genitals with explicit contact"
], ],
@@ -381,7 +381,7 @@
"visibility": [ "visibility": [
"mouth and genitals clearly visible", "mouth and genitals clearly visible",
"tongue contact clearly visible", "tongue contact clearly visible",
"cock and lips visible", "penis and lips visible",
"pussy and tongue visible", "pussy and tongue visible",
"saliva and arousal visible", "saliva and arousal visible",
"face pressed to genitals", "face pressed to genitals",
@@ -426,8 +426,8 @@
}, },
"anal penetration with visible genital contact", "anal penetration with visible genital contact",
"deep anal sex", "deep anal sex",
"cock entering ass", "penis entering ass",
"ass stretched around a cock", "ass stretched around a penis",
"hardcore anal thrusting", "hardcore anal thrusting",
"bent-over anal sex", "bent-over anal sex",
"rear-entry anal penetration", "rear-entry anal penetration",
@@ -494,7 +494,7 @@
"min_people": 2 "min_people": 2
}, },
{ {
"text": "cock and toy double penetration", "text": "penis and toy double penetration",
"cast": "mixed", "cast": "mixed",
"min_people": 2 "min_people": 2
}, },
@@ -511,7 +511,7 @@
"double penetration with pussy and ass filled", "double penetration with pussy and ass filled",
"vaginal and anal penetration at the same time", "vaginal and anal penetration at the same time",
"front-and-back double penetration", "front-and-back double penetration",
"one cock in pussy and one cock in ass", "one penis in pussy and one penis in ass",
"hardcore double penetration", "hardcore double penetration",
"kneeling double penetration", "kneeling double penetration",
"standing supported double penetration", "standing supported double penetration",
@@ -588,10 +588,10 @@
"wet skin sliding together" "wet skin sliding together"
], ],
"visibility": [ "visibility": [
"ass and cock clearly visible", "ass and penis clearly visible",
"anal penetration clearly visible", "anal penetration clearly visible",
"double penetration clearly visible", "double penetration clearly visible",
"pussy, ass, and cocks visible", "pussy, ass, and penises visible",
"genital contact anatomically clear", "genital contact anatomically clear",
"spread cheeks exposing penetration", "spread cheeks exposing penetration",
"open thighs exposing both entries", "open thighs exposing both entries",
@@ -668,7 +668,7 @@
"hands holding breasts", "hands holding breasts",
"fingers spreading thighs", "fingers spreading thighs",
"hands tangled in hair", "hands tangled in hair",
"one hand stroking a cock", "one hand stroking a penis",
"hands braced on the mattress", "hands braced on the mattress",
"fingers digging into ass", "fingers digging into ass",
"hands pulling bodies closer" "hands pulling bodies closer"
@@ -710,10 +710,10 @@
"one partner licking pussy", "one partner licking pussy",
"sixty-nine oral contact", "sixty-nine oral contact",
"mouth on genitals while penetration happens", "mouth on genitals while penetration happens",
"one partner sucking cock from the front", "one partner sucking penis from the front",
"one partner licking from behind", "one partner licking from behind",
"oral sex and penetration at the same time", "oral sex and penetration at the same time",
"a mouth full of cock with saliva visible" "a mouth full of penis with saliva visible"
], ],
"penetration_detail": [ "penetration_detail": [
{ {
@@ -731,8 +731,8 @@
"visible vaginal penetration", "visible vaginal penetration",
"visible anal penetration", "visible anal penetration",
"front-and-back penetration", "front-and-back penetration",
"one cock in pussy", "one penis in pussy",
"one cock in ass", "one penis in ass",
"penetration while another partner uses their mouth", "penetration while another partner uses their mouth",
"deep thrusting into the center partner", "deep thrusting into the center partner",
"explicit genital contact" "explicit genital contact"
@@ -772,7 +772,7 @@
"visibility": [ "visibility": [
"all genitals clearly visible", "all genitals clearly visible",
"penetration and oral contact visible", "penetration and oral contact visible",
"pussy, cock, and mouth contact visible", "pussy, penis, and mouth contact visible",
"explicit genital contact from two angles", "explicit genital contact from two angles",
"open thighs and spread bodies visible", "open thighs and spread bodies visible",
"anatomically clear hardcore contact", "anatomically clear hardcore contact",
@@ -898,10 +898,10 @@
"cast": "women_only" "cast": "women_only"
}, },
{ {
"text": "one mouth on a cock", "text": "one mouth on a penis",
"cast": "men_only" "cast": "men_only"
}, },
"one mouth on a cock", "one mouth on a penis",
"one mouth on a pussy", "one mouth on a pussy",
"multiple mouths giving oral sex", "multiple mouths giving oral sex",
"sixty-nine contact inside the group", "sixty-nine contact inside the group",
@@ -926,7 +926,7 @@
"visible vaginal penetration", "visible vaginal penetration",
"visible anal penetration", "visible anal penetration",
"double penetration visible", "double penetration visible",
"multiple cocks penetrating", "multiple penises penetrating",
"front-and-back penetration", "front-and-back penetration",
"genitals pressed together", "genitals pressed together",
"open thighs exposing penetration", "open thighs exposing penetration",
@@ -1097,7 +1097,7 @@
], ],
"hand_detail": [ "hand_detail": [
"hands spreading thighs", "hands spreading thighs",
"one hand holding a cock", "one hand holding a penis",
"hands gripping sheets", "hands gripping sheets",
"hands rubbing cum into skin", "hands rubbing cum into skin",
"fingers holding the mouth open", "fingers holding the mouth open",
+113 -16
View File
@@ -185,20 +185,46 @@ def _label_join(labels: list[str]) -> str:
labels = [_clean(label) for label in labels if _clean(label)] labels = [_clean(label) for label in labels if _clean(label)]
if not labels: if not labels:
return "the named adults" return "the named adults"
if set(labels) == {"Woman A", "Man A"}:
return "the woman and man"
if len(labels) == 1: if len(labels) == 1:
if labels[0] == "Woman A":
return "the woman"
if labels[0] == "Man A":
return "the man"
return labels[0] return labels[0]
if len(labels) == 2: if len(labels) == 2:
return f"{labels[0]} and {labels[1]}" return f"{labels[0]} and {labels[1]}"
return f"{', '.join(labels[:-1])}, and {labels[-1]}" return f"{', '.join(labels[:-1])}, and {labels[-1]}"
def _natural_label_text(text: Any, labels: list[str]) -> str:
text = _clean(text)
if not text:
return ""
if set(labels) == {"Woman A", "Man A"}:
text = re.sub(r"\bWoman A\b", "the woman", text)
text = re.sub(r"\bMan A\b", "the man", text)
elif labels == ["Woman A"]:
text = re.sub(r"\bWoman A\b", "the woman", text)
elif labels == ["Man A"]:
text = re.sub(r"\bMan A\b", "the man", text)
return text
def _cast_prose(text: str, central_label: str = "Woman A") -> tuple[str, list[str]]: def _cast_prose(text: str, central_label: str = "Woman A") -> tuple[str, list[str]]:
entries = _cast_entries(text) entries = _cast_entries(text)
if not entries: if not entries:
return (f"{central_label} is {_clean(text)}" if _clean(text) else "", []) return (f"{central_label} is {_clean(text)}" if _clean(text) else "", [])
labels = [label for label, _descriptor in entries] labels = [label for label, _descriptor in entries]
count_phrase = "one named adult" if len(entries) == 1 else f"{len(entries)} named adults" if labels == ["Woman A"]:
sentences = [f"The scene contains {count_phrase}."] return f"A {entries[0][1]}", labels
if labels == ["Man A"]:
return f"A {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"A {by_label['Woman A']} alongside a {by_label['Man A']}", labels
sentences = []
for label, descriptor in entries: for label, descriptor in entries:
sentences.append(f"{label} is {descriptor}.") sentences.append(f"{label} is {descriptor}.")
if central_label in labels: if central_label in labels:
@@ -225,10 +251,72 @@ def _natural_clothing_state(text: Any) -> str:
if not text: if not text:
return "" return ""
text = re.sub(r"^Clothing state:\s*", "", text, flags=re.IGNORECASE) text = re.sub(r"^Clothing state:\s*", "", text, flags=re.IGNORECASE)
text = re.sub(r";\s*softcore visual reference:\s*", ". Softcore visual reference: ", text, flags=re.IGNORECASE) match = re.match(
r"^(.*?)\b(?:softcore|teaser) outfit is (.*?)(?: for the (?:hardcore|sex) scene)?;\s*(?:softcore visual reference|teaser outfit detail):\s*(.*?)\.?$",
text,
flags=re.IGNORECASE,
)
if match:
owner = _natural_label_text(match.group(1).strip(" 's"), ["Woman A", "Man A"]).strip() or "the woman"
state = _clean(match.group(2)).lower()
outfit = _clean(match.group(3)).rstrip(".")
if "fully nude" in state:
return f"{owner.capitalize()} is fully nude, with the removed {outfit} visible nearby"
if "nude-adjacent" in state:
return f"{owner.capitalize()} is partly nude, with the {outfit} slipping off and no abstract clothing-reference wording"
if "partially removed" in state or "pushed aside" in state:
return f"{owner.capitalize()}'s {outfit} is pushed aside and partly removed, exposing the sexual contact clearly"
if "keeps" in state:
return f"{owner.capitalize()} keeps the {outfit} on while the sexual contact stays visible"
text = re.sub(r";\s*(?:softcore visual reference|teaser outfit detail):\s*", ". Visual clothing state: ", text, flags=re.IGNORECASE)
text = text.replace("softcore outfit", "outfit")
text = text.replace("teaser outfit", "outfit")
text = text.replace("hardcore scene", "sex scene")
return text return text
def _hardcore_action_sentence(role_graph: str, hard_item: str) -> str:
role_graph = _clean(role_graph).rstrip(".")
hard_item = _clean(hard_item).rstrip(".")
role_graph = re.sub(
r"\bthe man penetrates the woman while a toy adds a second point of contact\b",
"the man's penis thrusts into the woman while a toy adds a second penetration point",
role_graph,
flags=re.IGNORECASE,
)
role_graph = re.sub(
r"\bthe man 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 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,
)
if hard_item and role_graph:
return f"Explicit hardcore action: {role_graph}; {hard_item}"
if role_graph:
return f"Explicit hardcore action: {role_graph}"
if hard_item:
return f"Explicit hardcore action: {hard_item}"
return ""
def _clean_age(age: Any) -> str: def _clean_age(age: Any) -> str:
return _clean(age) return _clean(age)
@@ -353,19 +441,27 @@ def _normal_row_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
if subject_type == "configured_cast" or _clean(row.get("cast_summary")): if subject_type == "configured_cast" or _clean(row.get("cast_summary")):
subject = _clean(row.get("subject_phrase") or primary or "adult sexual scene") subject = _clean(row.get("subject_phrase") or primary or "adult sexual scene")
cast = _clean(row.get("cast_summary")) 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 = ( cast_descriptor_text = (
_clean(row.get("cast_descriptor_text")) _clean(row.get("cast_descriptor_text"))
or _prompt_field(_clean(row.get("prompt")), "Characters") or _prompt_field(_clean(row.get("prompt")), "Characters")
or _prompt_field(_clean(row.get("prompt")), "Cast descriptors") or _prompt_field(_clean(row.get("prompt")), "Cast descriptors")
) )
cast_prose, _cast_labels = _cast_prose(cast_descriptor_text) cast_prose, cast_labels = _cast_prose(cast_descriptor_text)
role_graph = _clean(row.get("role_graph")) if not cast_labels and women_count == 1 and men_count == 1:
cast_labels = ["Woman A", "Man A"]
role_graph = _natural_label_text(_clean(row.get("role_graph")), cast_labels)
item = _natural_label_text(item, cast_labels)
action = _hardcore_action_sentence(role_graph, item)
parts = [ parts = [
f"A consensual explicit adult scene with {subject}", action,
cast_prose, cast_prose,
f"The cast includes {cast}" if cast and not cast_prose else "", f"A consensual explicit adult scene with {subject}" if not action else "",
role_graph, f"The cast includes {cast}" if cast and not cast_prose and not (women_count == 1 and men_count == 1) else "",
f"The sexual action is {item}" if item else "",
f"The setting is {scene}" if scene else "", f"The setting is {scene}" if scene else "",
f"Facial expressions are {expression}" if expression else "", f"Facial expressions are {expression}" if expression else "",
f"The image is framed as {composition}" if composition else "", f"The image is framed as {composition}" if composition else "",
@@ -452,11 +548,14 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
hard_cast_prose, hard_labels = _cast_prose(cast_descriptor_text) hard_cast_prose, hard_labels = _cast_prose(cast_descriptor_text)
hard_item = _sanitize_scene_text_for_cast(hard.get("item"), hard_labels) hard_item = _sanitize_scene_text_for_cast(hard.get("item"), hard_labels)
hard_role_graph = _sanitize_scene_text_for_cast(hard.get("role_graph"), hard_labels) hard_role_graph = _sanitize_scene_text_for_cast(hard.get("role_graph"), hard_labels)
hard_item = _natural_label_text(hard_item, hard_labels)
hard_role_graph = _natural_label_text(hard_role_graph, hard_labels)
hard_action = _hardcore_action_sentence(hard_role_graph, hard_item)
same_soft_cast = options.get("softcore_cast") == "same_as_hardcore" same_soft_cast = options.get("softcore_cast") == "same_as_hardcore"
soft_cast_presence = ( soft_cast_presence = (
f"{_label_join(soft_labels)} are together in a non-explicit teaser pose, with no sex act or genital contact" f"{_label_join(soft_labels)} are together in a non-explicit teaser pose, with no sex act or genital contact"
if same_soft_cast if same_soft_cast
else "The softcore version focuses on Woman A alone" else "The image focuses on the woman alone"
) )
partner_styling = row.get("softcore_partner_styling") partner_styling = row.get("softcore_partner_styling")
if isinstance(partner_styling, dict): if isinstance(partner_styling, dict):
@@ -466,13 +565,13 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
else: else:
partner_outfit_text = "" partner_outfit_text = ""
partner_pose = "" partner_pose = ""
partner_outfit_text = _natural_label_text(partner_outfit_text, soft_labels)
soft_parts = [ soft_parts = [
f"Softcore {soft_level or 'creator'} Insta/OF image",
soft_cast_prose, soft_cast_prose,
soft_cast_presence, soft_cast_presence,
partner_outfit_text, partner_outfit_text,
f"The cast is {partner_pose}" if partner_pose else "", partner_pose,
f"wearing {soft.get('item')}" if soft.get("item") else "", f"wearing {soft.get('item')}" if soft.get("item") else "",
f"{soft.get('pose')}" if soft.get("pose") else "", f"{soft.get('pose')}" if soft.get("pose") else "",
f"with {soft.get('expression')}" if soft.get("expression") else "", f"with {soft.get('expression')}" if soft.get("expression") else "",
@@ -482,11 +581,9 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
soft_style if detail_level != "concise" else "", soft_style if detail_level != "concise" else "",
] ]
hard_parts = [ hard_parts = [
f"{hard_level or 'hardcore'} scene", hard_action,
hard_cast_prose,
_natural_clothing_state(row.get("hardcore_clothing_state")), _natural_clothing_state(row.get("hardcore_clothing_state")),
hard_role_graph, hard_cast_prose,
f"The explicit detail shows {hard_item}" if hard_item else "",
f"set in {hard_scene}" if hard_scene else "", f"set in {hard_scene}" if hard_scene else "",
f"with {hard.get('expression')}" if hard.get("expression") else "", f"with {hard.get('expression')}" if hard.get("expression") else "",
f"framed as {hard_composition}" if hard_composition else "", f"framed as {hard_composition}" if hard_composition else "",
+24 -24
View File
@@ -551,7 +551,7 @@ def _heuristic_cast_compatible(text: str, women_count: int, men_count: int) -> b
"double penetration" in text "double penetration" in text
or "two partners penetrating" in text or "two partners penetrating" in text
or "front-and-back penetration" in text or "front-and-back penetration" in text
or "one cock in pussy and one cock in ass" in text or "one penis in pussy and one penis in ass" in text
or "pussy and ass filled" in text or "pussy and ass filled" in text
or "vaginal and anal penetration at the same time" in text or "vaginal and anal penetration at the same time" in text
or "front-and-back double penetration" in text or "front-and-back double penetration" in text
@@ -582,9 +582,9 @@ def _heuristic_cast_compatible(text: str, women_count: int, men_count: int) -> b
if any(term in text for term in penetration_terms) and not any(term in text for term in toy_terms): if any(term in text for term in penetration_terms) and not any(term in text for term in toy_terms):
return False return False
male_terms = ( male_terms = (
" cock", " penis",
"cock ", "penis ",
"cocks", "penises",
"cum", "cum",
"creampie", "creampie",
"facial", "facial",
@@ -596,7 +596,7 @@ def _heuristic_cast_compatible(text: str, women_count: int, men_count: int) -> b
) )
if any(term in text for term in male_terms) and not any(term in text for term in toy_terms): if any(term in text for term in male_terms) and not any(term in text for term in toy_terms):
return False return False
elif men_count < 2 and "cocks" in text: elif men_count < 2 and "penises" in text:
return False return False
if women_count == 0: if women_count == 0:
if "penetrative sex" in text and not any(term in text for term in ("anal", "ass", "male/male", "men")): if "penetrative sex" in text and not any(term in text for term in ("anal", "ass", "male/male", "men")):
@@ -2484,7 +2484,7 @@ def _role_graph(
return f"{solo} is shown in a solo explicit climax pose with thighs open, one hand on her body, and visible arousal on skin and sheets." return f"{solo} is shown in a solo explicit climax pose with thighs open, one hand on her body, and visible arousal on skin and sheets."
return f"{solo} is shown in a solo explicit adult pose with self-touch, open body framing, and direct camera awareness." return f"{solo} is shown in a solo explicit adult pose with self-touch, open body framing, and direct camera awareness."
if "cumshot" in slug or "climax" in slug: if "cumshot" in slug or "climax" in slug:
return f"{solo} is shown in a solo explicit climax pose with one hand on his cock, body angled toward the camera, and visible ejaculation detail." return f"{solo} is shown in a solo explicit climax pose with one hand on his penis, body angled toward the camera, and visible ejaculation detail."
return f"{solo} is shown in a solo explicit adult pose with direct camera awareness and clear body framing." return f"{solo} is shown in a solo explicit adult pose with direct camera awareness and clear body framing."
if women_count > 0 and men_count == 0: if women_count > 0 and men_count == 0:
@@ -2510,7 +2510,7 @@ def _role_graph(
c = any_man({a, b}) if len(men) >= 3 else "" c = any_man({a, b}) if len(men) >= 3 else ""
used = {a, b} used = {a, b}
if "oral" in slug: if "oral" in slug:
graph = f"{a} kneels and takes {b}'s cock in his mouth while holding his hips." graph = f"{a} kneels and takes {b}'s penis in his mouth while holding his hips."
elif "anal" in slug or "double" in slug or "penetrative" in slug: elif "anal" in slug or "double" in slug or "penetrative" in slug:
graph = f"{a} penetrates {b} anally while {b}'s hips are held open." graph = f"{a} penetrates {b} anally while {b}'s hips are held open."
elif "threesome" in slug or "group" in slug or "orgy" in slug: elif "threesome" in slug or "group" in slug or "orgy" in slug:
@@ -2518,9 +2518,9 @@ def _role_graph(
graph = f"{a} penetrates {b} anally while {helper} gives oral contact from the front." graph = f"{a} penetrates {b} anally while {helper} gives oral contact from the front."
used.add(helper) used.add(helper)
elif "cumshot" in slug or "climax" in slug: elif "cumshot" in slug or "climax" in slug:
graph = f"{a} climaxes over {b}'s body while {b} keeps eye contact and one hand on his cock." graph = f"{a} climaxes over {b}'s body while {b} keeps eye contact and one hand on his penis."
else: else:
graph = f"{a} and {b} keep explicit cock and anal contact visible." graph = f"{a} and {b} keep explicit penis and anal contact visible."
return graph + support_sentence(used) return graph + support_sentence(used)
# Mixed cast. # Mixed cast.
@@ -2529,29 +2529,29 @@ def _role_graph(
third = any_person({woman, man}) if people_count >= 3 else "" third = any_person({woman, man}) if people_count >= 3 else ""
if "oral" in slug: if "oral" in slug:
if "sixty-nine" in item_text or ("blowjob" in item_text and ("cunnilingus" in item_text or "pussy" in item_text)): if "sixty-nine" in item_text or ("blowjob" in item_text and ("cunnilingus" in item_text or "pussy" in item_text)):
graph = f"{woman} and {man} are in mutual oral contact with mouth-to-genital contact visible." graph = f"{woman} has {man}'s penis in her mouth while {man} uses his mouth on {woman}'s pussy, with both mouths pressed to genitals."
elif any(term in item_text for term in ("cunnilingus", "pussy licking", "tongue on pussy", "mouth on pussy")): elif any(term in item_text for term in ("cunnilingus", "pussy licking", "tongue on pussy", "mouth on pussy")):
graph = f"{man} gives oral to {woman} while {woman}'s thighs are held open for the camera." graph = f"{man} gives oral to {woman}, mouth on her pussy while {woman}'s thighs are held open for the camera."
else: else:
graph = f"{woman} gives oral to {man} while {man} holds her hair and hips." graph = f"{woman} takes {man}'s penis in her mouth while {man} holds her hair and hips."
elif "anal" in slug or "double" in slug: elif "anal" in slug or "double" in slug:
if "double" in item_text or "toy" in item_text: if "double" in item_text or "toy" in item_text:
if people_count >= 3: if people_count >= 3:
graph = f"{man} penetrates {woman} while {third} adds a second point of contact from the front." graph = f"{man} thrusts his penis into {woman} while {third} adds a second penetration point from the front."
else: else:
graph = f"{man} penetrates {woman} while a toy adds a second point of contact." graph = f"{man} thrusts his penis into {woman} while a toy adds a second penetration point."
elif people_count >= 3: elif people_count >= 3:
graph = f"{man} penetrates {woman} while {third} gives oral contact from the front." graph = f"{man} thrusts his penis into {woman} while {third} gives oral contact from the front."
else: else:
graph = f"{man} penetrates {woman} anally while keeping her hips held open." graph = f"{man} thrusts his penis into {woman}'s ass while keeping her hips held open."
elif "threesome" in slug: elif "threesome" in slug:
graph = f"{man} penetrates {woman} while {third or any_person({woman, man})} uses mouth or hands on the exposed body." graph = f"{man} thrusts his penis into {woman} while {third or any_person({woman, man})} uses mouth or hands on the exposed body."
elif "group" in slug or "orgy" in slug: elif "group" in slug or "orgy" in slug:
graph = f"{man} penetrates {woman} while surrounding partners give oral contact and keep hands on hips, breasts, and thighs." graph = f"{man} thrusts his penis into {woman} while surrounding partners give oral contact and keep hands on hips, breasts, and thighs."
elif "cumshot" in slug or "climax" in slug: elif "cumshot" in slug or "climax" in slug:
graph = f"{man} climaxes on {woman}'s body while {woman} stays posed with thighs open and direct eye contact." graph = f"{man} climaxes on {woman}'s body while {woman} stays posed with thighs open and direct eye contact."
else: else:
graph = f"{man} and {woman} keep penetration and body contact visible." graph = f"{man}'s penis thrusts into {woman} with penetration and body contact visible."
return graph + support_sentence({woman, man, third} if third else {woman, man}) return graph + support_sentence({woman, man, third} if third else {woman, man})
@@ -3251,10 +3251,10 @@ INSTA_OF_PLATFORM_STYLES = {
INSTA_OF_HARDCORE_CLOTHING_CONTINUITY = { INSTA_OF_HARDCORE_CLOTHING_CONTINUITY = {
"none": "", "none": "",
"same_outfit": "Woman A keeps the softcore outfit in the hardcore scene", "same_outfit": "Woman A keeps her teaser outfit on, with sexual contact still clearly visible",
"partially_removed": "Woman A's softcore outfit is partially removed or pushed aside for the hardcore scene", "partially_removed": "Woman A's teaser outfit is pushed aside and partly removed, exposing the sexual contact clearly",
"implied_nude": "Woman A is nude-adjacent in the hardcore scene, with the softcore outfit slipping off or covering only part of the body", "implied_nude": "Woman A is partly nude, with the teaser outfit slipping off or covering only part of the body",
"explicit_nude": "Woman A is fully nude in the hardcore scene, with the removed softcore outfit visible nearby", "explicit_nude": "Woman A is fully nude, with the removed teaser outfit visible nearby",
} }
INSTA_OF_NEGATIVE = ( INSTA_OF_NEGATIVE = (
@@ -3609,7 +3609,7 @@ def _insta_of_hardcore_clothing_state(mode: str, softcore_outfit: str) -> str:
if mode == "none" or not outfit: if mode == "none" or not outfit:
return "" return ""
base = INSTA_OF_HARDCORE_CLOTHING_CONTINUITY[mode] base = INSTA_OF_HARDCORE_CLOTHING_CONTINUITY[mode]
return f"Clothing state: {base}; softcore visual reference: {outfit}." return f"Clothing state: {base}; teaser outfit detail: {outfit}."
def _insta_of_partner_styling( def _insta_of_partner_styling(