diff --git a/docs/prompt-architecture-improvement-plan.md b/docs/prompt-architecture-improvement-plan.md index 69b1172..ee2adc7 100644 --- a/docs/prompt-architecture-improvement-plan.md +++ b/docs/prompt-architecture-improvement-plan.md @@ -152,8 +152,10 @@ Already isolated: support text. - `krea_detail.py` owns generic detail-clause splitting, deduping, joining, and density limiting for Krea action prose. -- `krea_actions.py` owns non-POV hardcore action sentence rewriting, pose - anchors, arrangements, climax detail cleanup, and action-position phrasing. +- `krea_action_positions.py` owns non-POV pose anchors, body-arrangement text, + rear-entry detection, and action-position phrasing. +- `krea_actions.py` owns non-POV hardcore action sentence rewriting, item/detail + cleanup, and climax detail cleanup. - `krea_pov_actions.py` owns POV hardcore action sentence rewriting and first-person body geometry. diff --git a/docs/prompt-pool-routing-map.md b/docs/prompt-pool-routing-map.md index 136bc77..214b069 100644 --- a/docs/prompt-pool-routing-map.md +++ b/docs/prompt-pool-routing-map.md @@ -276,6 +276,7 @@ Edit targets: - Position filtering UI: `build_hardcore_position_pool_json`, `build_hardcore_action_filter_json`, `_apply_hardcore_position_config_to_subcategory`. - Krea2 action rewrite orchestration: `krea_formatter.py`. +- Krea2 non-POV position anchors/arrangements: `krea_action_positions.py`. - Krea2 non-POV action rewrite: `krea_actions.py`. - Krea2 POV position rewrite: `krea_pov_actions.py`. @@ -464,8 +465,10 @@ What each part owns: - `prompt_builder.py`: filters which templates/axes remain available. - `krea_formatter.py`: orchestrates the selected action rewrite into model-readable prose. -- `krea_actions.py`: rewrites non-POV hardcore action sentences, pose anchors, - arrangements, detail dedupe, and climax cleanup. +- `krea_action_positions.py`: resolves non-POV pose anchors, body-arrangement + text, duplicate arrangement checks, and action-position phrases. +- `krea_actions.py`: rewrites non-POV hardcore action sentences, item/detail + dedupe, and climax cleanup. - `krea_pov_actions.py`: rewrites POV variants with first-person geometry. Current broad hardcore families: @@ -553,6 +556,7 @@ Key Krea2 ownership: - Cast descriptor naturalization: `krea_cast.cast_prose`, `krea_cast.natural_label_text`. - Action context and family predicates: `krea_action_context.py`. +- Non-POV pose anchors and arrangements: `krea_action_positions.py`. - Non-POV hardcore action sentence: `krea_actions.hardcore_action_sentence`. - POV labels, filtering, and camera/composition support: `krea_pov.py`. - Detail clause splitting and density limiting: `krea_detail.py`. @@ -716,7 +720,7 @@ pair metadata through the core Python APIs, then verifies: | Wrong expression intensity | Character slot expression settings, `_expression_entries_for_intensity`, expression pools. | | Expression appears when disabled | `_disable_row_expression`, formatter expression extraction. | | Same hardcore action repeats | Hardcore filter config, `sexual_poses.json` weights, `_apply_hardcore_position_config_to_subcategory`. | -| Hardcore interaction beat falls back to penetration/oral | `sexual_poses.json` interaction subcategory, `_role_graph`, and `krea_action_context.is_foreplay_text` / `krea_actions.hardcore_pose_anchor`. | +| Hardcore interaction beat falls back to penetration/oral | `sexual_poses.json` interaction subcategory, `_role_graph`, and `krea_action_context.is_foreplay_text` / `krea_action_positions.hardcore_pose_anchor`. | | Raw hardcore prompt position is vague | `sexual_poses.json` item templates and role graph templates. | | Krea2 hardcore prompt position is vague | `krea_actions.hardcore_action_sentence` or `krea_pov_actions.py`. | | Man appears described in POV | POV labels, `krea_cast.cast_prose` omit labels, `krea_pov_actions.pov_action_phrase`. | @@ -741,8 +745,9 @@ Use these traces to narrow a problem in one pass. 3. Inspect `categories/sexual_poses.json` for the selected subcategory, `item_templates`, `axes`, and `weight`. 4. If raw `item` differs but Krea output looks identical, inspect - `krea_action_context.py` family predicates first, then `krea_actions.py` - pose anchors, arrangements, item detail cleanup, and action sentence dispatch. + `krea_action_context.py` family predicates first, then + `krea_action_positions.py` pose anchors/arrangements and `krea_actions.py` + item detail cleanup/action sentence dispatch. ### POV position is spatially wrong diff --git a/krea_action_positions.py b/krea_action_positions.py new file mode 100644 index 0000000..436a93a --- /dev/null +++ b/krea_action_positions.py @@ -0,0 +1,473 @@ +from __future__ import annotations + +import re +from typing import Any + +try: + from .krea_action_context import ( + axis_values_text, + is_close_foreplay_text, + is_foreplay_text, + is_outercourse_text, + is_toy_assisted_double_text, + position_context_text, + ) +except ImportError: # Allows local smoke tests with `python -c`. + from krea_action_context import ( + axis_values_text, + is_close_foreplay_text, + is_foreplay_text, + is_outercourse_text, + is_toy_assisted_double_text, + position_context_text, + ) + + +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 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) + position_text = "" + if isinstance(axis_values, dict): + position_text = _clean(axis_values.get("position", "")).lower() + if not text: + return "" + if is_foreplay_text(role_graph, hard_item, composition, axis_values_text(axis_values)): + return "" + if is_outercourse_text(role_graph, hard_item, composition, axis_values_text(axis_values)): + if any(term in text for term in ("boobjob", "titjob", "breast sex", "breast-sex")): + return "breast-sex outercourse pose" + if any(term in text for term in ("testicle", "balls licking", "balls-licking", "balls and mouth")): + return "testicle-sucking outercourse pose" + if any(term in text for term in ("penis licking", "penis-licking", "tongue along", "tongue licking")): + return "penis-licking outercourse pose" + if any(term in text for term in ("handjob", "hand job", "hand wrapped", "hand stroking", "manual stimulation")): + return "handjob outercourse pose" + if any(term in text for term in ("footjob", "soles", "toes curled", "feet stroking")): + return "footjob outercourse pose" + return "non-penetrative outercourse pose" + if is_toy_assisted_double_text(role_graph, hard_item, composition, axis_values_text(axis_values)): + if "face-down ass-up" in text or "face-down" in text: + return "toy-assisted face-down rear-entry double-penetration pose" + if "doggy style" in text or "doggy-style" in text or "all fours" in text or "rear-entry" in text: + return "toy-assisted rear-entry double-penetration pose" + if "bent-over" in text or "bent forward" in text: + return "toy-assisted bent-over double-penetration pose" + if "spooning anal" in text or "side-lying anal" in text or "side-lying" in text: + return "toy-assisted side-lying double-penetration pose" + if "edge-supported" in text or "bed-edge" in text or "edge-of-bed" in text: + return "toy-assisted edge-supported double-penetration pose" + if "standing anal" in text or "standing supported" in text or "standing" in text: + return "toy-assisted standing double-penetration pose" + if "kneeling anal" in text or "kneeling" in text: + return "toy-assisted kneeling rear-entry double-penetration pose" + return "toy-assisted rear-entry double-penetration pose" + if "double penetration" in text or "vaginal and anal penetration" in text or "front-and-back" in text: + if "face-down ass-up" in text: + return "face-down rear-entry double-penetration pose" + if "doggy style" in text or "doggy-style" in text: + return "doggy-style double-penetration pose" + if "bent-over" in text: + return "bent-over double-penetration pose" + if "spooning anal" in text or "side-lying anal" in text: + return "side-lying double-penetration pose" + if "bed-edge" in text or "edge-of-bed" in text: + return "bed-edge front-and-back double-penetration pose" + if "standing anal" in text or "standing supported" in text: + return "standing supported front-and-back double-penetration pose" + if "kneeling anal" in text: + return "kneeling rear-entry double-penetration pose" + if "standing supported" in text: + return "standing supported front-and-back double-penetration pose" + if "kneeling" in text: + return "kneeling front-and-back double-penetration pose" + return "front-and-back double-penetration pose" + if "sixty-nine" in position_text or ("sixty-nine" in text and not position_text): + return "sixty-nine oral pose" + if "face-sitting" in position_text or ("face-sitting" in text and not position_text): + return "face-sitting oral pose" + if "side-lying oral" in position_text or (("side-lying oral position" in item_text or "side-lying oral" in text) and not position_text): + return "side-lying oral pose" + if ( + "edge-of-bed oral" in position_text + or "edge-supported oral" in position_text + or (("edge-of-bed oral position" in item_text or "edge-of-bed oral" in text or "edge-supported oral" in text) and not position_text) + ): + return "edge-supported oral pose" + if "standing oral" in position_text or (("standing oral position" in item_text or "standing oral" in text) and not position_text): + return "standing oral pose" + if "chair oral" in position_text or (("chair oral position" in item_text or "chair oral" in text) and not position_text): + return "chair oral pose" + if "kneeling oral" in position_text or (("kneeling oral position" in item_text or "kneeling oral" in text) and not position_text): + return "kneeling oral pose" + if "straddled oral" in position_text or (("straddled oral position" in item_text or "straddled oral" in text) and not position_text): + return "straddled cunnilingus pose" + if "reclining cunnilingus" in position_text or (("reclining cunnilingus position" in item_text or "reclining cunnilingus" in text) and not position_text): + return "reclining cunnilingus pose" + if "spread-leg oral" in position_text or (("spread-leg oral position" in item_text or "spread-leg oral" in text) and not position_text): + return "spread-leg oral pose" + if "cunnilingus" in text or "pussy licking" in text or "mouth on her pussy" in text: + if "reclining" in text: + return "reclining cunnilingus pose" + if "straddled" in text: + return "straddled cunnilingus pose" + return "open-thigh cunnilingus pose" + if "oral" in text or "blowjob" in text or "penis in her mouth" in text or "penis in mouth" in text: + if "side-lying oral position" in item_text: + return "side-lying oral pose" + if "spread-leg oral position" in item_text: + return "spread-leg oral pose" + if "edge-of-bed oral position" in item_text: + return "edge-supported oral pose" + if "standing oral position" in item_text: + return "standing oral pose" + if "chair oral position" in item_text: + return "chair oral pose" + if "kneeling oral position" in item_text or "kneeling" in text: + return "kneeling oral pose" + if "standing" in text: + return "standing oral pose" + if "side-lying" in text: + return "side-lying oral pose" + if "edge-of-bed" in text or "bed-edge" in text: + return "edge-supported oral pose" + if "spread-leg" in text: + return "spread-leg oral pose" + if "chair oral" in text: + return "chair oral pose" + return "mouth-to-genitals oral pose" + 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: + return "doggy-style anal pose" + if "bed-edge" in text or "edge-of-bed" in text: + return "bed-edge rear-entry anal pose" + if "bent-over" in text: + return "bent-over rear-entry anal pose" + if "spooning anal" in text or "side-lying anal" in text: + return "side-lying rear-entry anal pose" + if "kneeling anal" in text: + return "kneeling rear-entry anal pose" + if "standing anal" in text: + return "standing rear-entry anal pose" + 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", + "cowgirl", + "doggy style", + "standing sex", + "spooning sex", + "edge-of-bed", + "kneeling straddle", + "lotus", + "bent-over", + ) + for position in positions: + if position in text: + return f"{position.replace('doggy style', 'doggy-style')} pose" + if "threesome" in text or "three-body" in text: + return "three-body explicit sex pose" + if "group" in text or "orgy" in text: + return "multi-body explicit sex pose" + if re.search(r"(? str: + text = position_context_text(anchor, f"{role_graph} {hard_item}", composition, axis_values) + position_text = "" + if isinstance(axis_values, dict): + position_text = _clean(axis_values.get("position", "")).lower() + if not text: + return "" + mixed_woman_man = "the woman" in text and "the man" in text + is_double = "double-penetration" in text or "double penetration" in text + + def cast_phrase(mixed: str, generic: str) -> str: + return mixed if mixed_woman_man else generic + + def double_tail() -> str: + return "" if "toy" in text else ", with the second penetration point aligned" + + if "sixty-nine" in position_text or ("sixty-nine" in text and not position_text): + return cast_phrase( + "with the woman and man inverted head-to-hips so both mouths align with genitals", + "with both bodies inverted head-to-hips so both mouths align with genitals", + ) + if "face-sitting" in position_text or ("face-sitting" in text and not position_text): + return cast_phrase( + "with the man lying back while the woman straddles his face", + "with one partner lying back while the other straddles the face", + ) + if ( + "reclining cunnilingus" in position_text + or "spread-leg oral" in position_text + or (("reclining cunnilingus" in text or "spread-leg oral" in text) and not position_text) + ): + if "takes the man's penis" in text or "penis in her mouth" in text: + return cast_phrase( + "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", + "with the receiving partner lying back, thighs spread, and the giver positioned between the legs", + ) + if ( + "straddled oral" in position_text + or (("straddled cunnilingus" in text or "straddled oral" in text) and not position_text) + ): + return cast_phrase( + "with the woman straddling above the man's mouth and her thighs framing his face", + "with the receiver straddling above the giver's mouth", + ) + if ( + "edge-of-bed oral" in position_text + or "edge-supported oral" in position_text + or ("edge-of-bed oral" in text and not position_text) + or ("edge-supported oral" in text and not position_text) + ): + if "takes the man's penis" in text or "penis in her mouth" in text: + return cast_phrase( + "with the man at a raised edge and the woman kneeling at his hips", + "with the receiver at a raised edge and the giver positioned at hip height", + ) + return cast_phrase( + "with the woman lying at a raised edge and the man positioned between her open thighs", + "with the receiver lying at a raised edge and the giver positioned between open thighs", + ) + if "standing oral" in position_text or ("standing oral" in text and not position_text): + if "takes the man's penis" in text or "penis in her mouth" in text: + return cast_phrase( + "with the man standing and the woman kneeling in front of his hips", + "with the receiver standing and the giver kneeling at hip height", + ) + return cast_phrase( + "with the woman standing braced and the man kneeling between her thighs", + "with the receiver standing braced and the giver kneeling between the thighs", + ) + if "chair oral" in position_text or ("chair oral" in text and not position_text): + if "takes the man's penis" in text or "penis in her mouth" in text: + return cast_phrase( + "with the man seated in the chair and the woman kneeling between his legs at hip level", + "with the receiver seated in the chair and the giver kneeling between the legs at hip level", + ) + return cast_phrase( + "with one partner seated in a chair and the other kneeling between the open thighs", + "with the receiver seated in a chair and the giver kneeling between the open thighs", + ) + if "side-lying oral" in position_text or ("side-lying oral" in text and not position_text): + return "with both bodies lying on their sides and mouth aligned to genitals" + if "kneeling oral" in position_text or ("kneeling oral" in text and not position_text): + if "takes the man's penis" in text or "penis in her mouth" in text: + return cast_phrase( + "with the woman kneeling in front of the man's hips, her mouth at penis level", + "with the giver kneeling in front of the receiver's hips", + ) + if "mouth on her pussy" in text or "uses his mouth on" in text: + return cast_phrase( + "with the man kneeling between the woman's open thighs, his mouth at her pussy", + "with the giver kneeling between the receiver's open thighs", + ) + return "with the giver kneeling at the receiver's hips" + if "reverse cowgirl" in text: + return cast_phrase( + "with the man lying on his back under the woman while she straddles his hips facing away", + "with the lower partner lying on their back while the upper partner straddles them facing away", + ) + if "cowgirl" in text: + return cast_phrase( + "with the man lying on his back under the woman while she straddles his hips on top", + "with the lower partner lying on their back while the upper partner straddles their hips on top", + ) + if "missionary" in text: + return cast_phrase( + "with the woman lying on her back under the man, legs open around his hips", + "with the receiving partner lying on their back under the penetrating partner, legs open around the hips", + ) + if "lotus" in text: + return cast_phrase( + "with the man seated upright and the woman seated in his lap facing him, legs wrapped around his hips", + "with one partner seated upright and the other seated in their lap facing them, legs wrapped around the hips", + ) + if "kneeling straddle" in text: + return cast_phrase( + "with the woman straddling the man's kneeling lap, both torsos upright and hips pressed together", + "with one partner straddling the other's kneeling lap, torsos upright and hips pressed together", + ) + if "doggy-style" in text: + return cast_phrase( + f"with the woman on all fours and the man positioned behind her at hip level{double_tail() if is_double else ''}", + f"with the receiving partner on all fours and the penetrating partner positioned behind at hip level{double_tail() if is_double else ''}", + ) + if "face-down" in text: + return cast_phrase( + f"with the woman face-down, hips raised, and the man positioned behind her{double_tail() if is_double else ''}", + f"with the receiving partner face-down, hips raised, and the penetrating partner positioned behind{double_tail() if is_double else ''}", + ) + if "bent-over" in text: + return cast_phrase( + f"with the woman bent forward at the waist and the man positioned behind her{double_tail() if is_double else ''}", + f"with the receiving partner bent forward at the waist and the penetrating partner positioned behind{double_tail() if is_double else ''}", + ) + if "spooning" in text or ("side-lying" in text and "oral" not in text): + return cast_phrase( + f"with both lying on their sides and the man positioned behind the woman{double_tail() if is_double else ''}", + f"with both bodies lying on their sides and the penetrating partner positioned behind{double_tail() if is_double else ''}", + ) + 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 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( + f"with the woman braced standing and the man aligned at her hips{double_tail() if is_double else ''}", + f"with both partners standing and the penetrating partner aligned at the receiver's hips{double_tail() if is_double else ''}", + ) + if "kneeling" in text and ("anal" in text or "rear-entry" in text): + return cast_phrase( + f"with the woman kneeling forward and the man positioned behind her{double_tail() if is_double else ''}", + f"with the receiving partner kneeling forward and the penetrating partner positioned behind{double_tail() if is_double else ''}", + ) + if "double-penetration" in text or "double penetration" in text: + if "toy" in text: + return cast_phrase( + "with the woman on all fours and the man positioned behind her at hip level", + "with the receiving body on all fours and the penetrating partner positioned behind at hip level", + ) + if "from the front" in text: + return cast_phrase( + "with the woman held between the man behind her and a second partner in front", + "with the receiving body held between one partner behind and a second partner in front", + ) + return cast_phrase( + "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 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", + ) + if "cunnilingus" in text or "mouth on her pussy" in text or "pussy licking" in text: + return cast_phrase( + "with the woman's thighs open and the man's mouth pressed to her pussy", + "with the receiver's thighs open and the giver's mouth pressed to genitals", + ) + if "oral" in text or "blowjob" in text or "penis in her mouth" in text or "penis in mouth" in text: + if "takes the man's penis in her mouth" in text or "penis in her mouth" in text: + return cast_phrase( + "with the woman's mouth at the man's hips", + "with the giver's mouth positioned at the receiver's hips", + ) + return "with mouth and genitals aligned clearly" + if "threesome" in text or "three-body" in text: + return "with all three adult bodies clearly placed around the central subject" + if "group" in text or "orgy" in text: + return "with each adult body readable in the shared sex act" + if re.search(r"(? bool: + arrangement_lower = _clean(arrangement).lower() + role_lower = _clean(role_graph).lower() + if not arrangement_lower or not role_lower: + return False + markers = ( + "bed edge", + "on all fours", + "face-down", + "hips raised", + "bent forward", + "straddl", + "on her back", + "on their sides", + "on her side", + "seated in", + "sits in", + "lap", + "kneeling between", + "kneels between", + "kneeling in front", + "kneels in front", + "positioned behind", + "standing", + ) + return any(marker in arrangement_lower and marker in role_lower for marker in markers) + + +def action_position_phrase(action: str) -> str: + action = _clean(action).lower() + if is_close_foreplay_text(action): + return "single-frame close-body first-person position" + 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: + return "all-fours raised-hip position" + if "bends forward" in action or "bent forward" in action: + return "bent-over raised-hip position" + if "lies on her back" in action and ("thighs open" in action or "legs open" in action): + return "open-thigh reclined position" + if "lies at the bed edge" in action or "bed edge" in action: + return "bed-edge position" + if "lies on her side" in action: + return "side-lying position" + if "kneels in front" in action: + return "kneeling-at-hip-height position" + if "straddles" in action or "squats over" in action: + return "straddling position" + if "sits in the man's lap" in action: + return "lap-straddling position" + if "stands braced" in action: + return "standing braced position" + if "held between" in action or "front-and-back" in action: + return "front-and-back position" + if "lies between" in action: + return "between-partners position" + return "" diff --git a/krea_actions.py b/krea_actions.py index a5daa9d..986cfcc 100644 --- a/krea_actions.py +++ b/krea_actions.py @@ -17,6 +17,13 @@ try: position_context_text, ) from .krea_detail import detail_clauses, join_detail_clauses, limit_detail_for_density + from .krea_action_positions import ( + action_position_phrase, + arrangement_duplicates_role, + hardcore_pose_anchor, + hardcore_pose_arrangement, + mentions_rear_entry, + ) except ImportError: # Allows local smoke tests with `python -c`. from krea_action_context import ( axis_values_text, @@ -31,6 +38,13 @@ except ImportError: # Allows local smoke tests with `python -c`. position_context_text, ) from krea_detail import detail_clauses, join_detail_clauses, limit_detail_for_density + from krea_action_positions import ( + action_position_phrase, + arrangement_duplicates_role, + hardcore_pose_anchor, + hardcore_pose_arrangement, + mentions_rear_entry, + ) def _clean(value: Any) -> str: @@ -83,397 +97,6 @@ def _sanitize_foreplay_detail(detail: str, role_graph: str = "", composition: st return _clean(detail) -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) - position_text = "" - if isinstance(axis_values, dict): - position_text = _clean(axis_values.get("position", "")).lower() - if not text: - return "" - if is_foreplay_text(role_graph, hard_item, composition, axis_values_text(axis_values)): - return "" - if is_outercourse_text(role_graph, hard_item, composition, axis_values_text(axis_values)): - if any(term in text for term in ("boobjob", "titjob", "breast sex", "breast-sex")): - return "breast-sex outercourse pose" - if any(term in text for term in ("testicle", "balls licking", "balls-licking", "balls and mouth")): - return "testicle-sucking outercourse pose" - if any(term in text for term in ("penis licking", "penis-licking", "tongue along", "tongue licking")): - return "penis-licking outercourse pose" - if any(term in text for term in ("handjob", "hand job", "hand wrapped", "hand stroking", "manual stimulation")): - return "handjob outercourse pose" - if any(term in text for term in ("footjob", "soles", "toes curled", "feet stroking")): - return "footjob outercourse pose" - return "non-penetrative outercourse pose" - if is_toy_assisted_double_text(role_graph, hard_item, composition, axis_values_text(axis_values)): - if "face-down ass-up" in text or "face-down" in text: - return "toy-assisted face-down rear-entry double-penetration pose" - if "doggy style" in text or "doggy-style" in text or "all fours" in text or "rear-entry" in text: - return "toy-assisted rear-entry double-penetration pose" - if "bent-over" in text or "bent forward" in text: - return "toy-assisted bent-over double-penetration pose" - if "spooning anal" in text or "side-lying anal" in text or "side-lying" in text: - return "toy-assisted side-lying double-penetration pose" - if "edge-supported" in text or "bed-edge" in text or "edge-of-bed" in text: - return "toy-assisted edge-supported double-penetration pose" - if "standing anal" in text or "standing supported" in text or "standing" in text: - return "toy-assisted standing double-penetration pose" - if "kneeling anal" in text or "kneeling" in text: - return "toy-assisted kneeling rear-entry double-penetration pose" - return "toy-assisted rear-entry double-penetration pose" - if "double penetration" in text or "vaginal and anal penetration" in text or "front-and-back" in text: - if "face-down ass-up" in text: - return "face-down rear-entry double-penetration pose" - if "doggy style" in text or "doggy-style" in text: - return "doggy-style double-penetration pose" - if "bent-over" in text: - return "bent-over double-penetration pose" - if "spooning anal" in text or "side-lying anal" in text: - return "side-lying double-penetration pose" - if "bed-edge" in text or "edge-of-bed" in text: - return "bed-edge front-and-back double-penetration pose" - if "standing anal" in text or "standing supported" in text: - return "standing supported front-and-back double-penetration pose" - if "kneeling anal" in text: - return "kneeling rear-entry double-penetration pose" - if "standing supported" in text: - return "standing supported front-and-back double-penetration pose" - if "kneeling" in text: - return "kneeling front-and-back double-penetration pose" - return "front-and-back double-penetration pose" - if "sixty-nine" in position_text or ("sixty-nine" in text and not position_text): - return "sixty-nine oral pose" - if "face-sitting" in position_text or ("face-sitting" in text and not position_text): - return "face-sitting oral pose" - if "side-lying oral" in position_text or (("side-lying oral position" in item_text or "side-lying oral" in text) and not position_text): - return "side-lying oral pose" - if ( - "edge-of-bed oral" in position_text - or "edge-supported oral" in position_text - or (("edge-of-bed oral position" in item_text or "edge-of-bed oral" in text or "edge-supported oral" in text) and not position_text) - ): - return "edge-supported oral pose" - if "standing oral" in position_text or (("standing oral position" in item_text or "standing oral" in text) and not position_text): - return "standing oral pose" - if "chair oral" in position_text or (("chair oral position" in item_text or "chair oral" in text) and not position_text): - return "chair oral pose" - if "kneeling oral" in position_text or (("kneeling oral position" in item_text or "kneeling oral" in text) and not position_text): - return "kneeling oral pose" - if "straddled oral" in position_text or (("straddled oral position" in item_text or "straddled oral" in text) and not position_text): - return "straddled cunnilingus pose" - if "reclining cunnilingus" in position_text or (("reclining cunnilingus position" in item_text or "reclining cunnilingus" in text) and not position_text): - return "reclining cunnilingus pose" - if "spread-leg oral" in position_text or (("spread-leg oral position" in item_text or "spread-leg oral" in text) and not position_text): - return "spread-leg oral pose" - if "cunnilingus" in text or "pussy licking" in text or "mouth on her pussy" in text: - if "reclining" in text: - return "reclining cunnilingus pose" - if "straddled" in text: - return "straddled cunnilingus pose" - return "open-thigh cunnilingus pose" - if "oral" in text or "blowjob" in text or "penis in her mouth" in text or "penis in mouth" in text: - if "side-lying oral position" in item_text: - return "side-lying oral pose" - if "spread-leg oral position" in item_text: - return "spread-leg oral pose" - if "edge-of-bed oral position" in item_text: - return "edge-supported oral pose" - if "standing oral position" in item_text: - return "standing oral pose" - if "chair oral position" in item_text: - return "chair oral pose" - if "kneeling oral position" in item_text or "kneeling" in text: - return "kneeling oral pose" - if "standing" in text: - return "standing oral pose" - if "side-lying" in text: - return "side-lying oral pose" - if "edge-of-bed" in text or "bed-edge" in text: - return "edge-supported oral pose" - if "spread-leg" in text: - return "spread-leg oral pose" - if "chair oral" in text: - return "chair oral pose" - return "mouth-to-genitals oral pose" - 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: - return "doggy-style anal pose" - if "bed-edge" in text or "edge-of-bed" in text: - return "bed-edge rear-entry anal pose" - if "bent-over" in text: - return "bent-over rear-entry anal pose" - if "spooning anal" in text or "side-lying anal" in text: - return "side-lying rear-entry anal pose" - if "kneeling anal" in text: - return "kneeling rear-entry anal pose" - if "standing anal" in text: - return "standing rear-entry anal pose" - 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", - "cowgirl", - "doggy style", - "standing sex", - "spooning sex", - "edge-of-bed", - "kneeling straddle", - "lotus", - "bent-over", - ) - for position in positions: - if position in text: - return f"{position.replace('doggy style', 'doggy-style')} pose" - if "threesome" in text or "three-body" in text: - return "three-body explicit sex pose" - if "group" in text or "orgy" in text: - return "multi-body explicit sex pose" - if re.search(r"(? str: - text = position_context_text(anchor, f"{role_graph} {hard_item}", composition, axis_values) - position_text = "" - if isinstance(axis_values, dict): - position_text = _clean(axis_values.get("position", "")).lower() - if not text: - return "" - mixed_woman_man = "the woman" in text and "the man" in text - is_double = "double-penetration" in text or "double penetration" in text - - def cast_phrase(mixed: str, generic: str) -> str: - return mixed if mixed_woman_man else generic - - def double_tail() -> str: - return "" if "toy" in text else ", with the second penetration point aligned" - - if "sixty-nine" in position_text or ("sixty-nine" in text and not position_text): - return cast_phrase( - "with the woman and man inverted head-to-hips so both mouths align with genitals", - "with both bodies inverted head-to-hips so both mouths align with genitals", - ) - if "face-sitting" in position_text or ("face-sitting" in text and not position_text): - return cast_phrase( - "with the man lying back while the woman straddles his face", - "with one partner lying back while the other straddles the face", - ) - if ( - "reclining cunnilingus" in position_text - or "spread-leg oral" in position_text - or (("reclining cunnilingus" in text or "spread-leg oral" in text) and not position_text) - ): - if "takes the man's penis" in text or "penis in her mouth" in text: - return cast_phrase( - "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", - "with the receiving partner lying back, thighs spread, and the giver positioned between the legs", - ) - if ( - "straddled oral" in position_text - or (("straddled cunnilingus" in text or "straddled oral" in text) and not position_text) - ): - return cast_phrase( - "with the woman straddling above the man's mouth and her thighs framing his face", - "with the receiver straddling above the giver's mouth", - ) - if ( - "edge-of-bed oral" in position_text - or "edge-supported oral" in position_text - or ("edge-of-bed oral" in text and not position_text) - or ("edge-supported oral" in text and not position_text) - ): - if "takes the man's penis" in text or "penis in her mouth" in text: - return cast_phrase( - "with the man at a raised edge and the woman kneeling at his hips", - "with the receiver at a raised edge and the giver positioned at hip height", - ) - return cast_phrase( - "with the woman lying at a raised edge and the man positioned between her open thighs", - "with the receiver lying at a raised edge and the giver positioned between open thighs", - ) - if "standing oral" in position_text or ("standing oral" in text and not position_text): - if "takes the man's penis" in text or "penis in her mouth" in text: - return cast_phrase( - "with the man standing and the woman kneeling in front of his hips", - "with the receiver standing and the giver kneeling at hip height", - ) - return cast_phrase( - "with the woman standing braced and the man kneeling between her thighs", - "with the receiver standing braced and the giver kneeling between the thighs", - ) - if "chair oral" in position_text or ("chair oral" in text and not position_text): - if "takes the man's penis" in text or "penis in her mouth" in text: - return cast_phrase( - "with the man seated in the chair and the woman kneeling between his legs at hip level", - "with the receiver seated in the chair and the giver kneeling between the legs at hip level", - ) - return cast_phrase( - "with one partner seated in a chair and the other kneeling between the open thighs", - "with the receiver seated in a chair and the giver kneeling between the open thighs", - ) - if "side-lying oral" in position_text or ("side-lying oral" in text and not position_text): - return "with both bodies lying on their sides and mouth aligned to genitals" - if "kneeling oral" in position_text or ("kneeling oral" in text and not position_text): - if "takes the man's penis" in text or "penis in her mouth" in text: - return cast_phrase( - "with the woman kneeling in front of the man's hips, her mouth at penis level", - "with the giver kneeling in front of the receiver's hips", - ) - if "mouth on her pussy" in text or "uses his mouth on" in text: - return cast_phrase( - "with the man kneeling between the woman's open thighs, his mouth at her pussy", - "with the giver kneeling between the receiver's open thighs", - ) - return "with the giver kneeling at the receiver's hips" - if "reverse cowgirl" in text: - return cast_phrase( - "with the man lying on his back under the woman while she straddles his hips facing away", - "with the lower partner lying on their back while the upper partner straddles them facing away", - ) - if "cowgirl" in text: - return cast_phrase( - "with the man lying on his back under the woman while she straddles his hips on top", - "with the lower partner lying on their back while the upper partner straddles their hips on top", - ) - if "missionary" in text: - return cast_phrase( - "with the woman lying on her back under the man, legs open around his hips", - "with the receiving partner lying on their back under the penetrating partner, legs open around the hips", - ) - if "lotus" in text: - return cast_phrase( - "with the man seated upright and the woman seated in his lap facing him, legs wrapped around his hips", - "with one partner seated upright and the other seated in their lap facing them, legs wrapped around the hips", - ) - if "kneeling straddle" in text: - return cast_phrase( - "with the woman straddling the man's kneeling lap, both torsos upright and hips pressed together", - "with one partner straddling the other's kneeling lap, torsos upright and hips pressed together", - ) - if "doggy-style" in text: - return cast_phrase( - f"with the woman on all fours and the man positioned behind her at hip level{double_tail() if is_double else ''}", - f"with the receiving partner on all fours and the penetrating partner positioned behind at hip level{double_tail() if is_double else ''}", - ) - if "face-down" in text: - return cast_phrase( - f"with the woman face-down, hips raised, and the man positioned behind her{double_tail() if is_double else ''}", - f"with the receiving partner face-down, hips raised, and the penetrating partner positioned behind{double_tail() if is_double else ''}", - ) - if "bent-over" in text: - return cast_phrase( - f"with the woman bent forward at the waist and the man positioned behind her{double_tail() if is_double else ''}", - f"with the receiving partner bent forward at the waist and the penetrating partner positioned behind{double_tail() if is_double else ''}", - ) - if "spooning" in text or ("side-lying" in text and "oral" not in text): - return cast_phrase( - f"with both lying on their sides and the man positioned behind the woman{double_tail() if is_double else ''}", - f"with both bodies lying on their sides and the penetrating partner positioned behind{double_tail() if is_double else ''}", - ) - 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 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( - f"with the woman braced standing and the man aligned at her hips{double_tail() if is_double else ''}", - f"with both partners standing and the penetrating partner aligned at the receiver's hips{double_tail() if is_double else ''}", - ) - if "kneeling" in text and ("anal" in text or "rear-entry" in text): - return cast_phrase( - f"with the woman kneeling forward and the man positioned behind her{double_tail() if is_double else ''}", - f"with the receiving partner kneeling forward and the penetrating partner positioned behind{double_tail() if is_double else ''}", - ) - if "double-penetration" in text or "double penetration" in text: - if "toy" in text: - return cast_phrase( - "with the woman on all fours and the man positioned behind her at hip level", - "with the receiving body on all fours and the penetrating partner positioned behind at hip level", - ) - if "from the front" in text: - return cast_phrase( - "with the woman held between the man behind her and a second partner in front", - "with the receiving body held between one partner behind and a second partner in front", - ) - return cast_phrase( - "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 _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", - ) - if "cunnilingus" in text or "mouth on her pussy" in text or "pussy licking" in text: - return cast_phrase( - "with the woman's thighs open and the man's mouth pressed to her pussy", - "with the receiver's thighs open and the giver's mouth pressed to genitals", - ) - if "oral" in text or "blowjob" in text or "penis in her mouth" in text or "penis in mouth" in text: - if "takes the man's penis in her mouth" in text or "penis in her mouth" in text: - return cast_phrase( - "with the woman's mouth at the man's hips", - "with the giver's mouth positioned at the receiver's hips", - ) - return "with mouth and genitals aligned clearly" - if "threesome" in text or "three-body" in text: - return "with all three adult bodies clearly placed around the central subject" - if "group" in text or "orgy" in text: - return "with each adult body readable in the shared sex act" - if re.search(r"(? bool: - arrangement_lower = _clean(arrangement).lower() - role_lower = _clean(role_graph).lower() - if not arrangement_lower or not role_lower: - return False - markers = ( - "bed edge", - "on all fours", - "face-down", - "hips raised", - "bent forward", - "straddl", - "on her back", - "on their sides", - "on her side", - "seated in", - "sits in", - "lap", - "kneeling between", - "kneels between", - "kneeling in front", - "kneels in front", - "positioned behind", - "standing", - ) - return any(marker in arrangement_lower and marker in role_lower for marker in markers) - - def hardcore_item_detail(hard_item: str) -> str: text = _clean(hard_item).rstrip(".") if not text: @@ -792,57 +415,6 @@ def _dedupe_penetration_detail(detail: str, role_graph: str, hard_item: str = "" return join_detail_clauses(clauses) -def action_position_phrase(action: str) -> str: - action = _clean(action).lower() - if is_close_foreplay_text(action): - return "single-frame close-body first-person position" - 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: - return "all-fours raised-hip position" - if "bends forward" in action or "bent forward" in action: - return "bent-over raised-hip position" - if "lies on her back" in action and ("thighs open" in action or "legs open" in action): - return "open-thigh reclined position" - if "lies at the bed edge" in action or "bed edge" in action: - return "bed-edge position" - if "lies on her side" in action: - return "side-lying position" - if "kneels in front" in action: - return "kneeling-at-hip-height position" - if "straddles" in action or "squats over" in action: - return "straddling position" - if "sits in the man's lap" in action: - return "lap-straddling position" - if "stands braced" in action: - return "standing braced position" - if "held between" in action or "front-and-back" in action: - return "front-and-back position" - if "lies between" in action: - return "between-partners position" - return "" - - def _normalize_climax_view_clause(clause: str, role_graph: str) -> str: lower = clause.lower() if "view" not in lower and "frame" not in lower: @@ -929,7 +501,7 @@ def climax_role_graph(role_graph: str, hard_item: str, axis_values: Any = None) return "the woman straddles the man's hips while the man lies on his back under her and ejaculates semen onto 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 he ejaculates semen 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): + 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 with visible semen 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 he ejaculates semen onto her face, lips, and chest" @@ -1101,7 +673,7 @@ def hardcore_action_sentence( detail = limit_detail_for_density(detail, detail_density, False) 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 and not _arrangement_duplicates_role(arrangement, role_graph): + if arrangement and anchor_phrase and not arrangement_duplicates_role(arrangement, role_graph): anchor_phrase = f"{anchor_phrase} {arrangement}" if role_graph and anchor_phrase: sentence = f"In {anchor_phrase}, {_lowercase_for_inline_join(role_graph)}" diff --git a/krea_formatter.py b/krea_formatter.py index ba02621..23b508f 100644 --- a/krea_formatter.py +++ b/krea_formatter.py @@ -22,10 +22,8 @@ try: prompt_cast_descriptors as _prompt_cast_descriptors, ) from .krea_clothing import natural_clothing_state as _natural_clothing_state - from .krea_actions import ( - action_position_phrase as _action_position_phrase, - hardcore_action_sentence as _hardcore_action_sentence, - ) + from .krea_action_positions import action_position_phrase as _action_position_phrase + from .krea_actions import hardcore_action_sentence as _hardcore_action_sentence from .krea_pov import ( filter_pov_labeled_clauses as _filter_pov_labeled_clauses, merge_labels as _merge_labels, @@ -53,10 +51,8 @@ except ImportError: # Allows local smoke tests with `python -c`. prompt_cast_descriptors as _prompt_cast_descriptors, ) from krea_clothing import natural_clothing_state as _natural_clothing_state - from krea_actions import ( - action_position_phrase as _action_position_phrase, - hardcore_action_sentence as _hardcore_action_sentence, - ) + from krea_action_positions import action_position_phrase as _action_position_phrase + from krea_actions import hardcore_action_sentence as _hardcore_action_sentence from krea_pov import ( filter_pov_labeled_clauses as _filter_pov_labeled_clauses, merge_labels as _merge_labels,