From e65b67fe850cddf8172b19ce0d035bac5ecec631 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Thu, 25 Jun 2026 02:33:02 +0200 Subject: [PATCH] Improve Krea POV position formatting --- krea_formatter.py | 290 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 268 insertions(+), 22 deletions(-) diff --git a/krea_formatter.py b/krea_formatter.py index c8b0ff5..dee4af9 100644 --- a/krea_formatter.py +++ b/krea_formatter.py @@ -358,33 +358,239 @@ def _filter_pov_labeled_clauses(text: Any, pov_labels: list[str]) -> str: return "; ".join(filtered) -def _pov_action_phrase(action: Any, pov_labels: list[str]) -> str: +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 thrusts into her ass" if is_anal else "as his penis thrusts into her" + 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, + ) + 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 + penetrative_tokens = ( + "penetrat", + "thrust", + "penis", + "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) + + 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 + + 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( + "POV face-down rear-entry position: the woman lies face-down in front of the viewer with ass raised toward him; " + f"the viewer is behind her at hip level with foreground hands on her hips {contact}" + ) + if "bent-over" in context or "bent over" in context or "bent forward" in context: + return sentence( + "POV bent-over rear-entry position: the woman bends forward at the waist in front of the viewer with hips raised and head turned back; " + f"the viewer stands behind her at hip level {contact}" + ) + if "doggy" in context or "all fours" in context or "rear-entry" in context: + return sentence( + "POV doggy position: the woman is on all fours directly in front of the viewer with hips raised and back arched; " + f"the viewer is behind her at hip level 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 POV viewer's", rendered) - rendered = re.sub(rf"\b{escaped}\b", "the POV viewer", rendered) + 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 POV viewer's", rendered, flags=re.IGNORECASE) - rendered = re.sub(r"\bthe man\b", "the POV viewer", rendered, flags=re.IGNORECASE) - rendered = re.sub(r"\bhe\b", "the POV viewer", rendered, flags=re.IGNORECASE) - rendered = re.sub(r"\bhim\b", "the POV viewer", rendered, flags=re.IGNORECASE) - rendered = re.sub(r"\bhis\b", "the POV viewer's", rendered, flags=re.IGNORECASE) + 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 POV viewer lies on the POV viewer's back under her\b", - "the POV viewer reclines underneath her", + 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 POV viewer lies on the POV viewer's back\b", - "the POV viewer reclines", + r"\bthe viewer lies on the viewer's back\b", + "the viewer reclines", rendered, flags=re.IGNORECASE, ) - rendered = re.sub(r"\bthe POV viewer is positioned\b", "the POV camera is positioned", rendered, flags=re.IGNORECASE) + rendered = re.sub(r"\bthe viewer is positioned\b", "the POV camera is positioned", rendered, flags=re.IGNORECASE) return rendered @@ -393,12 +599,10 @@ def _pov_camera_phrase(pov_labels: list[str], softcore: bool = False) -> str: return "" if softcore: return ( - "first-person POV creator framing from the male participant; " - "the woman is the visible subject, and the participant is implied by camera position or foreground cues" + "Camera is the male participant's first-person creator view, with him implied by perspective or foreground cues" ) return ( - "first-person POV from the male participant; keep the visible partners readable from the viewer's position, " - "using only foreground hands, body position, or camera perspective cues for the POV participant" + "Camera is the male participant's first-person view; only his foreground hands or body cues appear" ) @@ -411,8 +615,13 @@ def _pov_composition_text(composition: Any, pov_labels: list[str]) -> str: 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) - if "pov" not in text.lower() and "first-person" not in text.lower(): - text = f"{text}, adapted for first-person POV with the POV participant kept off-camera" + 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) @@ -657,6 +866,8 @@ def _hardcore_pose_anchor(role_graph: str, hard_item: str, composition: str = "" 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", @@ -1014,7 +1225,8 @@ def _dedupe_hardcore_detail(detail: str, anchor: str) -> str: "reverse cowgirl": (r"reverse cowgirl position",), "cowgirl": (r"cowgirl position",), "doggy-style": (r"doggy style position",), - "edge-of-bed": (r"edge-of-bed 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"), @@ -1084,6 +1296,26 @@ def _join_detail_clauses(clauses: list[str]) -> str: def _action_position_phrase(action: str) -> str: action = _clean(action).lower() + 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: @@ -1606,6 +1838,8 @@ def _normal_row_to_krea(row: dict[str, Any], detail_level: str, style_mode: str) 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"] @@ -1624,7 +1858,7 @@ def _normal_row_to_krea(row: dict[str, Any], detail_level: str, style_mode: str) 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) + 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, @@ -1715,6 +1949,10 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str) _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" @@ -1745,7 +1983,15 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str) hard_axis_values, hard_detail_density, ) - hard_action = _pov_action_phrase(hard_action, pov_labels) + 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 [])