from __future__ import annotations import re from typing import Any try: from . import outercourse_action_policy as outercourse_policy from .krea_action_context import ( axis_values_text, is_climax_text, is_outercourse_text, is_toy_assisted_double_text, position_context_text, ) from .krea_detail import limit_detail_for_density except ImportError: # Allows local smoke tests with `python -c`. import outercourse_action_policy as outercourse_policy from krea_action_context import ( axis_values_text, is_climax_text, is_outercourse_text, is_toy_assisted_double_text, position_context_text, ) from krea_detail import limit_detail_for_density 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 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, 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 position_text = "" if isinstance(axis_values, dict): position_text = _clean(axis_values.get("position", "")).lower() position_context = position_text or 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 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): action_kind = outercourse_policy.infer_outercourse_action_kind(position_text) if action_kind == outercourse_policy.OUTERCOURSE_GENERIC: action_kind = outercourse_policy.infer_outercourse_action_kind(context, action_lower) if action_kind == outercourse_policy.OUTERCOURSE_BOOBJOB: return outercourse_sentence( "The woman kneels low between the viewer's open thighs with her torso bent forward over his pelvis; " "both hands push her breasts inward around the viewer's penis in the lower foreground, the penis held between her breasts, " "with her chin and lips directly above the glans at the tip" ) if action_kind == outercourse_policy.OUTERCOURSE_TESTICLE: return outercourse_sentence( "The woman bends forward and kneels very low between the viewer's open thighs with her shoulders between his knees; " "her face is below the viewer's penis at testicle height, mouth and tongue licking the viewer's balls while his penis points upward in the lower foreground above her forehead" ) if action_kind == outercourse_policy.OUTERCOURSE_PENIS_LICKING: return outercourse_sentence( "The woman bends forward between the viewer's open thighs with her head low under the viewer's penis; " "her face is just under the penis while her tongue touches the underside from the base toward the glans at the tip, " "one hand steadying the base of the viewer's penis in the lower foreground" ) if action_kind == outercourse_policy.OUTERCOURSE_HANDJOB: return outercourse_sentence( "The woman kneels between the viewer's open thighs with her torso leaning forward and face visible behind the viewer's penis; " "one hand grips and strokes the viewer's penis in the lower foreground while the other hand steadies its base, " "thumb and fingers visible around the penis as she strokes toward the glans" ) if action_kind == outercourse_policy.OUTERCOURSE_FOOTJOB: 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 position_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 position_context or "straddling a partner" in position_context or "squatting on top" in position_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 position_context or "seated in a partner's lap" in position_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 position_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 position_context or "face down" in position_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 ( "edge-supported" in position_context or "raised edge" in position_context or "edge of bed" in position_context or "bed edge" in position_context or (not position_text and "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 "standing" in position_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 position_context or "side-lying" in position_context or "lies on her side" in position_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 "doggy" in position_context or "all fours" in position_context or "rear-entry" in position_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 "kneeling" in position_context: return sentence( "POV kneeling rear-entry position: the woman kneels forward in front of the viewer with hips raised and thighs apart; " f"the viewer kneels behind her at hip level with foreground hands near her waist {contact}" ) if "bent-over" in position_context or "bent over" in position_context or "bent forward" in position_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 "missionary" in position_context or (not position_text and "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