Add axis-aware hardcore position grammar
This commit is contained in:
+206
-19
@@ -280,9 +280,60 @@ def _natural_clothing_state(text: Any) -> str:
|
||||
return text
|
||||
|
||||
|
||||
def _hardcore_pose_anchor(role_graph: str, hard_item: str, composition: str = "") -> str:
|
||||
text = " ".join(_clean(part).lower() for part in (role_graph, hard_item, composition) if _clean(part))
|
||||
item_text = _clean(hard_item).lower()
|
||||
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 _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)
|
||||
if not text:
|
||||
return ""
|
||||
if "double penetration" in text or "vaginal and anal penetration" in text or "front-and-back" in text:
|
||||
@@ -339,7 +390,7 @@ def _hardcore_pose_anchor(role_graph: str, hard_item: str, composition: str = ""
|
||||
if "chair oral" in text:
|
||||
return "chair oral pose"
|
||||
return "mouth-to-genitals oral pose"
|
||||
if "anal" in text or "ass" in text or "rear-entry" in text:
|
||||
if "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:
|
||||
@@ -381,8 +432,8 @@ def _hardcore_pose_anchor(role_graph: str, hard_item: str, composition: str = ""
|
||||
return ""
|
||||
|
||||
|
||||
def _hardcore_pose_arrangement(anchor: str, role_graph: str, hard_item: str, composition: str = "") -> str:
|
||||
text = " ".join(_clean(part).lower() for part in (anchor, role_graph, hard_item, composition) if _clean(part))
|
||||
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)
|
||||
if not text:
|
||||
return ""
|
||||
mixed_woman_man = "the woman" in text and "the man" in text
|
||||
@@ -407,8 +458,8 @@ def _hardcore_pose_arrangement(anchor: str, role_graph: str, hard_item: str, com
|
||||
if "reclining cunnilingus" in text or "spread-leg oral" in text:
|
||||
if "takes the man's penis" in text or "penis in her mouth" in text:
|
||||
return cast_phrase(
|
||||
"with the man seated or lying back with legs apart and the woman positioned at his hips",
|
||||
"with the receiver seated or lying back with legs apart and the giver positioned at the hips",
|
||||
"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",
|
||||
@@ -510,8 +561,8 @@ def _hardcore_pose_arrangement(anchor: str, role_graph: str, hard_item: str, com
|
||||
)
|
||||
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 standing or 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 standing or kneeling between the legs{double_tail() if is_double else ''}",
|
||||
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(
|
||||
@@ -526,8 +577,8 @@ def _hardcore_pose_arrangement(anchor: str, role_graph: str, hard_item: str, com
|
||||
if "double-penetration" in text or "double penetration" in text:
|
||||
if "toy" in text:
|
||||
return cast_phrase(
|
||||
"with the woman bent forward or on all fours, the man behind her, and the toy aligned at the second penetration point",
|
||||
"with the receiving body bent forward or on all fours and the toy aligned at the second penetration point",
|
||||
"with the woman on all fours, the man behind her, and the toy aligned at the second penetration point",
|
||||
"with the receiving body on all fours and the toy aligned at the second penetration point",
|
||||
)
|
||||
if "from the front" in text:
|
||||
return cast_phrase(
|
||||
@@ -538,7 +589,7 @@ def _hardcore_pose_arrangement(anchor: str, role_graph: str, hard_item: str, com
|
||||
"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 "rear-entry" in text:
|
||||
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",
|
||||
@@ -570,6 +621,7 @@ def _hardcore_item_detail(hard_item: str) -> str:
|
||||
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)
|
||||
@@ -591,6 +643,7 @@ def _hardcore_item_detail(hard_item: str) -> str:
|
||||
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",
|
||||
@@ -669,6 +722,9 @@ def _dedupe_hardcore_detail(detail: str, anchor: str) -> str:
|
||||
"cowgirl": (r"cowgirl position",),
|
||||
"doggy-style": (r"doggy style position",),
|
||||
"edge-of-bed": (r"edge-of-bed 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:
|
||||
@@ -679,7 +735,129 @@ def _dedupe_hardcore_detail(detail: str, anchor: str) -> str:
|
||||
return _clean(detail).strip(" ,;")
|
||||
|
||||
|
||||
def _hardcore_action_sentence(role_graph: str, hard_item: str, composition: str = "") -> str:
|
||||
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 the climax 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 the climax lands across her body"
|
||||
if "kneeling between standing partners" in text:
|
||||
return "the woman kneels between standing partners gathered around her face and torso as the climax lands across her body"
|
||||
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 as the climax lands 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 as the climax lands 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 as the climax lands 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 as the climax lands 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 as the climax lands across her ass, thighs, and lower back"
|
||||
if "face-down ass-up" in text:
|
||||
return "the woman lies face-down with ass raised while the man is positioned behind her as the climax lands 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 as the climax lands 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 the climax lands across 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 the climax"
|
||||
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 as the climax lands 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 as the climax lands on 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 as the climax lands on 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 as the climax lands on 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 the climax lands 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 as the climax lands 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 the climax lands across 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 as the climax lands 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 as the climax lands across her body"
|
||||
|
||||
|
||||
def _dedupe_climax_detail(detail: str, role_graph: str) -> 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)
|
||||
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)
|
||||
return _clean(detail).strip(" ,;")
|
||||
|
||||
|
||||
def _hardcore_action_sentence(role_graph: str, hard_item: str, composition: str = "", axis_values: Any = None) -> str:
|
||||
role_graph = _clean(role_graph).rstrip(".")
|
||||
hard_item = _clean(hard_item).rstrip(".")
|
||||
role_graph = re.sub(
|
||||
@@ -730,8 +908,11 @@ def _hardcore_action_sentence(role_graph: str, hard_item: str, composition: str
|
||||
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)
|
||||
anchor = _hardcore_pose_anchor(role_graph, hard_item, composition, axis_values)
|
||||
if "double-penetration" in anchor.lower() and "toy" in role_graph.lower():
|
||||
role_graph = re.sub(
|
||||
r"\s+while a toy adds (?:the|a) second penetration point\b",
|
||||
@@ -739,8 +920,12 @@ def _hardcore_action_sentence(role_graph: str, hard_item: str, composition: str
|
||||
role_graph,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
detail = _dedupe_hardcore_detail(detail, anchor) if anchor else detail
|
||||
arrangement = _hardcore_pose_arrangement(anchor, role_graph, hard_item, composition)
|
||||
if is_climax:
|
||||
anchor = ""
|
||||
detail = _dedupe_climax_detail(detail, role_graph)
|
||||
else:
|
||||
detail = _dedupe_hardcore_detail(detail, anchor) if anchor else detail
|
||||
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:
|
||||
anchor_phrase = f"{anchor_phrase} {arrangement}"
|
||||
@@ -926,7 +1111,8 @@ def _normal_row_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
|
||||
item = _sanitize_scene_text_for_cast(item, cast_labels)
|
||||
role_graph = _natural_label_text(role_graph, cast_labels)
|
||||
item = _natural_label_text(item, cast_labels)
|
||||
action = _hardcore_action_sentence(role_graph, item, composition)
|
||||
axis_values = row.get("item_axis_values") if isinstance(row.get("item_axis_values"), dict) else {}
|
||||
action = _hardcore_action_sentence(role_graph, item, composition, axis_values)
|
||||
parts = [
|
||||
action,
|
||||
cast_prose,
|
||||
@@ -1020,7 +1206,8 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
|
||||
hard_role_graph = _sanitize_scene_text_for_cast(hard.get("role_graph"), hard_labels)
|
||||
hard_item = _natural_label_text(hard_item, hard_labels)
|
||||
hard_role_graph = _natural_label_text(hard_role_graph, hard_labels)
|
||||
hard_action = _hardcore_action_sentence(hard_role_graph, hard_item, hard_composition)
|
||||
hard_axis_values = hard.get("item_axis_values") if isinstance(hard.get("item_axis_values"), dict) else {}
|
||||
hard_action = _hardcore_action_sentence(hard_role_graph, hard_item, hard_composition, hard_axis_values)
|
||||
same_soft_cast = options.get("softcore_cast") == "same_as_hardcore"
|
||||
soft_cast_presence = (
|
||||
f"{_label_join(soft_labels)} are together in a non-explicit teaser pose, with no sex act or genital contact"
|
||||
|
||||
Reference in New Issue
Block a user