diff --git a/krea_formatter.py b/krea_formatter.py index ae6089f..b8159e7 100644 --- a/krea_formatter.py +++ b/krea_formatter.py @@ -856,6 +856,40 @@ def _is_oral_text(*parts: Any) -> bool: ) +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): + 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: @@ -1521,6 +1555,74 @@ def _dedupe_oral_detail(detail: str, role_graph: str, hard_item: str = "", axis_ 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(" ,;")] @@ -1846,6 +1948,7 @@ def _hardcore_action_sentence( 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", @@ -1864,6 +1967,10 @@ def _hardcore_action_sentence( 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)): diff --git a/prompt_builder.py b/prompt_builder.py index 0bd8c81..82f7eca 100644 --- a/prompt_builder.py +++ b/prompt_builder.py @@ -5251,24 +5251,33 @@ def _role_graph( ) ) if "missionary" in text: - return f"{woman} lies on her back with legs open while {man} is above her and {man}'s penis thrusts into her." + return ( + f"{woman} lies on her back with legs open around {man}'s hips while {man} is above her between her thighs; " + f"{man}'s hips press close and {man}'s penis thrusts into her pussy." + ) if "reverse cowgirl" in text: - return f"{woman} straddles {man}'s hips facing away while {man} lies under her and {man}'s penis thrusts into her." + return f"{woman} straddles {man}'s hips facing away while {man} lies under her and {man}'s penis thrusts into her pussy." if "cowgirl" in text or "straddling" in text: - return f"{woman} straddles {man}'s hips facing him while {man} lies under her and {man}'s penis thrusts into her." + return f"{woman} straddles {man}'s hips facing him while {man} lies under her and {man}'s penis thrusts into her pussy." if "doggy" in text or "rear-entry" in text or "bent-over" in text or "bent over" in text: - return f"{woman} is on all fours with hips raised while {man} is positioned behind her and {man}'s penis thrusts into her." + return f"{woman} is on all fours with hips raised while {man} is positioned behind her and {man}'s penis thrusts into her pussy." if "standing" in text: - return f"{woman} stands braced with hips angled back while {man} stands behind her and {man}'s penis thrusts into her." + return f"{woman} stands braced with hips angled back while {man} stands behind her and {man}'s penis thrusts into her pussy." if "spooning" in text or "side-lying" in text: - return f"{woman} lies on her side with thighs parted while {man} presses behind her and {man}'s penis thrusts into her." - if "edge-of-bed" in text or "edge of bed" in text or "bed edge" in text: - return f"{woman} lies at the bed edge with hips near the edge while {man} kneels between her legs and {man}'s penis thrusts into her." + return f"{woman} lies on her side with thighs parted while {man} presses behind her and {man}'s penis thrusts into her pussy." + if "edge-of-bed" in text or "edge of bed" in text or "bed edge" in text or "edge-supported" in text or "raised edge" in text: + return ( + f"{woman} lies back at a raised edge with hips at the edge and legs open while {man} kneels between her thighs; " + f"{man}'s hips press close and {man}'s penis thrusts into her pussy." + ) if "kneeling straddle" in text: - return f"{woman} kneels straddling {man}'s hips while {man} supports her waist and {man}'s penis thrusts into her." + return f"{woman} kneels straddling {man}'s hips while {man} supports her waist and {man}'s penis thrusts into her pussy." if "lotus" in text: - return f"{woman} sits in {man}'s lap facing him with legs around his hips while {man}'s penis thrusts into her." - return f"{woman} lies on her back with thighs open while {man} kneels between her legs and {man}'s penis thrusts into her." + return f"{woman} sits in {man}'s lap facing him with legs around his hips while {man}'s penis thrusts into her pussy." + return ( + f"{woman} lies on her back with legs spread wide and knees bent outward while {man} kneels between her open thighs facing her; " + f"{man}'s hips are pressed between her legs and {man}'s penis thrusts into her pussy." + ) def anal_position_graph(woman: str, man: str) -> str: text = " ".join(