Extract Krea action detail helpers
This commit is contained in:
@@ -154,14 +154,16 @@ Already isolated:
|
||||
density limiting for Krea action prose.
|
||||
- `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_action_details.py` owns non-climax item/detail cleanup for foreplay,
|
||||
outercourse, oral, penetration, toy/double-contact, and anchor dedupe paths.
|
||||
- `krea_actions.py` owns non-POV hardcore action sentence dispatch and
|
||||
climax-specific role/detail cleanup.
|
||||
- `krea_pov_actions.py` owns POV hardcore action sentence rewriting and
|
||||
first-person body geometry.
|
||||
|
||||
Improve later:
|
||||
|
||||
- split `krea_actions.py` by action family once the extracted module is stable;
|
||||
- split remaining climax-specific cleanup out of `krea_actions.py`;
|
||||
- add route-level smoke fixtures for representative metadata rows;
|
||||
- make `krea_actions.hardcore_action_sentence` dispatch by action family instead
|
||||
of long conditional chains.
|
||||
@@ -342,8 +344,8 @@ Medium-term:
|
||||
|
||||
## Recommended Next Passes
|
||||
|
||||
1. Split `krea_actions.py` by action family, using `krea_cast.py` as the
|
||||
pattern for stable import aliases and smoke coverage.
|
||||
1. Split climax-specific role/detail cleanup out of `krea_actions.py`, using
|
||||
`krea_cast.py` as the pattern for stable import aliases and smoke coverage.
|
||||
2. Split `__init__.py` node classes by family after behavior is covered by smoke
|
||||
checks.
|
||||
3. Add metadata fields such as `action_family` / `position_family` to reduce
|
||||
|
||||
@@ -277,6 +277,7 @@ Edit targets:
|
||||
`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-climax item/detail cleanup: `krea_action_details.py`.
|
||||
- Krea2 non-POV action rewrite: `krea_actions.py`.
|
||||
- Krea2 POV position rewrite: `krea_pov_actions.py`.
|
||||
|
||||
@@ -467,8 +468,11 @@ What each part owns:
|
||||
model-readable prose.
|
||||
- `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_action_details.py`: normalizes non-climax item/detail text and dedupes
|
||||
foreplay, outercourse, oral, penetration, toy/double-contact, and anchor
|
||||
details.
|
||||
- `krea_actions.py`: rewrites non-POV hardcore action sentences and handles
|
||||
climax-specific role/detail cleanup.
|
||||
- `krea_pov_actions.py`: rewrites POV variants with first-person geometry.
|
||||
|
||||
Current broad hardcore families:
|
||||
@@ -557,6 +561,7 @@ Key Krea2 ownership:
|
||||
`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-climax item/detail cleanup: `krea_action_details.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`.
|
||||
@@ -746,8 +751,9 @@ Use these traces to narrow a problem in one pass.
|
||||
`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_action_positions.py` pose anchors/arrangements and `krea_actions.py`
|
||||
item detail cleanup/action sentence dispatch.
|
||||
`krea_action_positions.py` pose anchors/arrangements,
|
||||
`krea_action_details.py` item/detail cleanup, and `krea_actions.py` action
|
||||
sentence dispatch.
|
||||
|
||||
### POV position is spatially wrong
|
||||
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from .krea_action_context import (
|
||||
is_close_foreplay_text,
|
||||
position_context_text,
|
||||
)
|
||||
from .krea_detail import detail_clauses, join_detail_clauses
|
||||
except ImportError: # Allows local smoke tests with `python -c`.
|
||||
from krea_action_context import (
|
||||
is_close_foreplay_text,
|
||||
position_context_text,
|
||||
)
|
||||
from krea_detail import detail_clauses, join_detail_clauses
|
||||
|
||||
|
||||
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 sanitize_foreplay_detail(detail: str, role_graph: str = "", composition: str = "") -> str:
|
||||
detail = _clean(detail)
|
||||
if not detail:
|
||||
return ""
|
||||
if not is_close_foreplay_text(role_graph, detail, composition):
|
||||
return detail
|
||||
detail = re.sub(
|
||||
r"\b(?:raised edge|edge-supported|edge-of-bed|bed-edge)\s+undressing position\s+(?:featuring|while|with)\s+",
|
||||
"",
|
||||
detail,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
detail = re.sub(
|
||||
r"\b(?:standing kissing|wall-pressed kissing|mirror undressing)\s+position\s+(?:featuring|while|with)\s+",
|
||||
"",
|
||||
detail,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
detail = re.sub(
|
||||
r"\b(?:raised edge|edge-supported|edge-of-bed|bed-edge)\s+undressing position\b",
|
||||
"close standing undressing",
|
||||
detail,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
detail = re.sub(r"\braised-edge open-thigh position\b", "close-body first-person position", detail, flags=re.IGNORECASE)
|
||||
detail = re.sub(r"\s*,\s*", ", ", detail).strip(" ,;")
|
||||
return _clean(detail)
|
||||
|
||||
|
||||
def hardcore_item_detail(hard_item: str) -> str:
|
||||
text = _clean(hard_item).rstrip(".")
|
||||
if not text:
|
||||
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)
|
||||
act_patterns = (
|
||||
r"(?:penis and toy|toy and strap-on|toy-assisted|front-and-back|hardcore|deep|kneeling|standing supported)?\s*double penetration",
|
||||
r"toy-assisted vaginal and anal penetration at the same time",
|
||||
r"vaginal and anal penetration at the same time",
|
||||
r"one penis in pussy and one penis in ass",
|
||||
r"anal penetration with visible genital contact",
|
||||
r"rear-entry anal penetration",
|
||||
r"anal sex with spread cheeks",
|
||||
r"ass stretched around a penis",
|
||||
r"penis entering ass",
|
||||
r"deep anal sex",
|
||||
r"bent-over anal sex",
|
||||
r"hardcore anal thrusting",
|
||||
r"vaginal penetration with visible genital contact",
|
||||
r"penis entering pussy",
|
||||
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",
|
||||
r"fellatio with penis in mouth",
|
||||
r"deepthroat blowjob",
|
||||
r"blowjob",
|
||||
r"penis sucking with visible saliva",
|
||||
r"cunnilingus with tongue on pussy",
|
||||
r"face-sitting cunnilingus",
|
||||
r"pussy licking with thighs spread",
|
||||
r"oral sex with tongue and fingers",
|
||||
r"oral contact with mouth on the visible genitals",
|
||||
r"sixty-nine oral sex",
|
||||
)
|
||||
act_pattern = "|".join(act_patterns)
|
||||
position_pattern = (
|
||||
r"missionary position|cowgirl position|reverse cowgirl position|doggy style position|"
|
||||
r"standing sex position|spooning sex position|edge-of-bed position|kneeling straddle position|"
|
||||
r"lotus sex position|bent-over position|kneeling oral position|face-sitting position|"
|
||||
r"sixty-nine position|edge-of-bed oral position|edge-supported oral position|standing oral position|reclining cunnilingus position|"
|
||||
r"straddled oral position|side-lying oral position|spread-leg oral position|chair oral position"
|
||||
)
|
||||
text = re.sub(
|
||||
rf"^({position_pattern})\s+(?:while|with|featuring)\s+(?:{act_pattern})\s*,?\s*",
|
||||
r"\1, ",
|
||||
text,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
text = re.sub(
|
||||
rf"^(?:{act_pattern})\s*(?:in|from|on|with|while|featuring)?\s*",
|
||||
"",
|
||||
text,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
text = re.sub(r"^(?:position|pose)\s+", "", text, flags=re.IGNORECASE)
|
||||
text = re.sub(r"^with\s+", "", text, flags=re.IGNORECASE)
|
||||
text = re.sub(r"\bwith with\b", "with", text, flags=re.IGNORECASE)
|
||||
text = re.sub(r",\s*with\s+", ", ", text, flags=re.IGNORECASE)
|
||||
text = re.sub(r",\s+and\s+", ", ", text)
|
||||
text = re.sub(r"\s*,\s*", ", ", text).strip(" ,;")
|
||||
return _clean(text)
|
||||
|
||||
|
||||
def dedupe_anchor_detail(detail: str, anchor: str) -> str:
|
||||
detail = _clean(detail)
|
||||
anchor_lower = anchor.lower()
|
||||
duplicate_phrases = {
|
||||
"front-and-back": (r"front-and-back contact",),
|
||||
"side-lying oral": (r"side-lying oral position",),
|
||||
"kneeling oral": (r"kneeling oral position",),
|
||||
"face-sitting": (r"face-sitting position",),
|
||||
"sixty-nine": (
|
||||
r"sixty-nine position",
|
||||
r"sixty-nine oral sex",
|
||||
r"kneeling oral position",
|
||||
r"face-sitting position",
|
||||
r"edge-of-bed oral position",
|
||||
r"standing oral position",
|
||||
r"reclining cunnilingus position",
|
||||
r"straddled oral position",
|
||||
r"side-lying oral position",
|
||||
r"spread-leg oral position",
|
||||
r"chair oral position",
|
||||
),
|
||||
"edge-supported oral": (r"edge-of-bed oral position", r"edge-supported oral position"),
|
||||
"edge-of-bed oral": (r"edge-of-bed oral position", r"edge-supported oral position"),
|
||||
"standing oral": (r"standing oral position",),
|
||||
"spread-leg oral": (r"spread-leg oral position",),
|
||||
"chair oral": (r"chair oral position",),
|
||||
"reclining cunnilingus": (r"reclining cunnilingus position",),
|
||||
"straddled cunnilingus": (r"straddled oral position", r"straddled cunnilingus position"),
|
||||
"open-thigh cunnilingus": (r"reclining cunnilingus position", r"straddled cunnilingus position"),
|
||||
"bent-over": (r"bent-over position",),
|
||||
"face-down": (r"face-down ass-up position",),
|
||||
"missionary": (r"missionary position",),
|
||||
"reverse cowgirl": (r"reverse cowgirl position",),
|
||||
"cowgirl": (r"cowgirl position",),
|
||||
"doggy-style": (r"doggy style 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"),
|
||||
}
|
||||
for anchor_token, phrases in duplicate_phrases.items():
|
||||
if anchor_token in anchor_lower:
|
||||
for phrase in phrases:
|
||||
detail = re.sub(rf"\b{phrase}\b,?\s*", "", detail, flags=re.IGNORECASE)
|
||||
detail = re.sub(r"^\s*,\s*", "", detail)
|
||||
detail = re.sub(r",\s*,", ",", detail)
|
||||
return _clean(detail).strip(" ,;")
|
||||
|
||||
|
||||
def dedupe_toy_double_detail(detail: str) -> str:
|
||||
detail = _clean(detail)
|
||||
if not detail:
|
||||
return ""
|
||||
angle_view = (
|
||||
r"(?:rear-view|side-profile|low-angle|mirror-reflected|overhead|close-up|wide full-body|front-facing with hips turned)"
|
||||
)
|
||||
toy_act = (
|
||||
r"(?:penis and toy double penetration|toy-assisted vaginal and anal penetration at the same time|toy and strap-on double penetration)"
|
||||
)
|
||||
detail = re.sub(
|
||||
rf"\b({angle_view}\s+view of\s+){toy_act}\b",
|
||||
r"\1the rear-entry contact",
|
||||
detail,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
detail = re.sub(rf",?\s*\b{toy_act}\b", "", detail, flags=re.IGNORECASE)
|
||||
duplicate_phrases = (
|
||||
"toy-assisted second contact aligned behind the body",
|
||||
"toy aligned for a second penetration point",
|
||||
"rear-entry body alignment",
|
||||
"close body alignment",
|
||||
"stacked bodies in close contact",
|
||||
"one body between two partners",
|
||||
"one partner behind and one partner in front",
|
||||
"two partners penetrating at once",
|
||||
"one partner held between two bodies",
|
||||
"front-and-back contact",
|
||||
"three bodies locked together",
|
||||
"kneeling center partner",
|
||||
)
|
||||
for phrase in duplicate_phrases:
|
||||
detail = re.sub(rf",?\s*\b{re.escape(phrase)}\b", "", detail, flags=re.IGNORECASE)
|
||||
detail = re.sub(r"^\s*,\s*", "", detail)
|
||||
detail = re.sub(r",\s*,", ",", detail)
|
||||
return _clean(detail).strip(" ,;")
|
||||
|
||||
|
||||
def dedupe_outercourse_detail(detail: str, role_graph: str, hard_item: str = "", axis_values: Any = None) -> str:
|
||||
detail = _clean(detail)
|
||||
if not detail:
|
||||
return ""
|
||||
context = position_context_text(role_graph, hard_item, "", axis_values)
|
||||
context_lower = context.lower()
|
||||
breast_sex = any(term in context_lower for term in ("boobjob", "titjob", "breast sex", "breast-sex"))
|
||||
clauses: list[str] = []
|
||||
for clause in detail_clauses(detail):
|
||||
lower = clause.lower()
|
||||
if breast_sex:
|
||||
if lower in ("penis", "breasts", "mouth clearly visible"):
|
||||
continue
|
||||
if any(
|
||||
term in lower
|
||||
for term in (
|
||||
"boobjob",
|
||||
"titjob",
|
||||
"breast-sex",
|
||||
"breast sex",
|
||||
"seated titjob position",
|
||||
"kneeling boobjob position",
|
||||
"tight close-up breast-sex position",
|
||||
"penis shaft compressed between breasts",
|
||||
"penis squeezed between both breasts",
|
||||
"hands pressing the breasts tightly",
|
||||
"hands pressing breasts firmly together",
|
||||
"fingers spreading the breasts around the penis shaft",
|
||||
"soft flesh squeezed around the penis shaft",
|
||||
"hand wrapped around the penis shaft",
|
||||
"glans near the mouth",
|
||||
"glans visible",
|
||||
"penis, breasts, and mouth clearly visible",
|
||||
)
|
||||
):
|
||||
continue
|
||||
clauses.append(clause)
|
||||
return join_detail_clauses(clauses)
|
||||
|
||||
|
||||
def dedupe_oral_detail(detail: str, role_graph: str, hard_item: str = "", axis_values: Any = None) -> str:
|
||||
detail = _clean(detail)
|
||||
if not detail:
|
||||
return ""
|
||||
context = position_context_text(role_graph, hard_item, "", axis_values)
|
||||
woman_gives = any(
|
||||
term in context
|
||||
for term in (
|
||||
"takes the man's penis",
|
||||
"takes his penis",
|
||||
"penis in her mouth",
|
||||
"mouth at penis level",
|
||||
"mouth on his penis",
|
||||
"fellatio",
|
||||
"blowjob",
|
||||
"deepthroat",
|
||||
"penis sucking",
|
||||
)
|
||||
)
|
||||
clauses: list[str] = []
|
||||
for clause in detail_clauses(detail):
|
||||
lower = clause.lower()
|
||||
if any(
|
||||
term in lower
|
||||
for term in (
|
||||
"kneeling oral position",
|
||||
"standing oral position",
|
||||
"edge-of-bed oral position",
|
||||
"side-lying oral position",
|
||||
"chair oral position",
|
||||
"reclining cunnilingus position",
|
||||
"face-sitting position",
|
||||
"sixty-nine position",
|
||||
"fellatio with penis in mouth",
|
||||
"deepthroat blowjob",
|
||||
"penis sucking with visible saliva",
|
||||
"cunnilingus with tongue on pussy",
|
||||
"oral sex with tongue and fingers",
|
||||
"oral contact with mouth on the visible genitals",
|
||||
"bodies stacked close together",
|
||||
"body angle keeps the penis and face readable",
|
||||
)
|
||||
):
|
||||
continue
|
||||
if woman_gives and lower == "wet shine on genitals":
|
||||
clause = "saliva dripping on the penis"
|
||||
clauses.append(clause)
|
||||
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)
|
||||
+24
-357
@@ -7,14 +7,12 @@ try:
|
||||
from .krea_action_context import (
|
||||
axis_values_text,
|
||||
is_climax_text,
|
||||
is_close_foreplay_text,
|
||||
is_foreplay_text,
|
||||
is_oral_text,
|
||||
is_outercourse_text,
|
||||
is_toy_assisted_double_text,
|
||||
is_vaginal_penetration_text,
|
||||
normalize_hardcore_detail_density,
|
||||
position_context_text,
|
||||
)
|
||||
from .krea_detail import detail_clauses, join_detail_clauses, limit_detail_for_density
|
||||
from .krea_action_positions import (
|
||||
@@ -24,18 +22,25 @@ try:
|
||||
hardcore_pose_arrangement,
|
||||
mentions_rear_entry,
|
||||
)
|
||||
from .krea_action_details import (
|
||||
dedupe_anchor_detail,
|
||||
dedupe_oral_detail,
|
||||
dedupe_outercourse_detail,
|
||||
dedupe_penetration_detail,
|
||||
dedupe_toy_double_detail,
|
||||
hardcore_item_detail,
|
||||
sanitize_foreplay_detail,
|
||||
)
|
||||
except ImportError: # Allows local smoke tests with `python -c`.
|
||||
from krea_action_context import (
|
||||
axis_values_text,
|
||||
is_climax_text,
|
||||
is_close_foreplay_text,
|
||||
is_foreplay_text,
|
||||
is_oral_text,
|
||||
is_outercourse_text,
|
||||
is_toy_assisted_double_text,
|
||||
is_vaginal_penetration_text,
|
||||
normalize_hardcore_detail_density,
|
||||
position_context_text,
|
||||
)
|
||||
from krea_detail import detail_clauses, join_detail_clauses, limit_detail_for_density
|
||||
from krea_action_positions import (
|
||||
@@ -45,6 +50,15 @@ except ImportError: # Allows local smoke tests with `python -c`.
|
||||
hardcore_pose_arrangement,
|
||||
mentions_rear_entry,
|
||||
)
|
||||
from krea_action_details import (
|
||||
dedupe_anchor_detail,
|
||||
dedupe_oral_detail,
|
||||
dedupe_outercourse_detail,
|
||||
dedupe_penetration_detail,
|
||||
dedupe_toy_double_detail,
|
||||
hardcore_item_detail,
|
||||
sanitize_foreplay_detail,
|
||||
)
|
||||
|
||||
|
||||
def _clean(value: Any) -> str:
|
||||
@@ -68,353 +82,6 @@ def _with_indefinite_article(text: str) -> str:
|
||||
return f"{article} {text}"
|
||||
|
||||
|
||||
def _sanitize_foreplay_detail(detail: str, role_graph: str = "", composition: str = "") -> str:
|
||||
detail = _clean(detail)
|
||||
if not detail:
|
||||
return ""
|
||||
if not is_close_foreplay_text(role_graph, detail, composition):
|
||||
return detail
|
||||
detail = re.sub(
|
||||
r"\b(?:raised edge|edge-supported|edge-of-bed|bed-edge)\s+undressing position\s+(?:featuring|while|with)\s+",
|
||||
"",
|
||||
detail,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
detail = re.sub(
|
||||
r"\b(?:standing kissing|wall-pressed kissing|mirror undressing)\s+position\s+(?:featuring|while|with)\s+",
|
||||
"",
|
||||
detail,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
detail = re.sub(
|
||||
r"\b(?:raised edge|edge-supported|edge-of-bed|bed-edge)\s+undressing position\b",
|
||||
"close standing undressing",
|
||||
detail,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
detail = re.sub(r"\braised-edge open-thigh position\b", "close-body first-person position", detail, flags=re.IGNORECASE)
|
||||
detail = re.sub(r"\s*,\s*", ", ", detail).strip(" ,;")
|
||||
return _clean(detail)
|
||||
|
||||
|
||||
def hardcore_item_detail(hard_item: str) -> str:
|
||||
text = _clean(hard_item).rstrip(".")
|
||||
if not text:
|
||||
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)
|
||||
act_patterns = (
|
||||
r"(?:penis and toy|toy and strap-on|toy-assisted|front-and-back|hardcore|deep|kneeling|standing supported)?\s*double penetration",
|
||||
r"toy-assisted vaginal and anal penetration at the same time",
|
||||
r"vaginal and anal penetration at the same time",
|
||||
r"one penis in pussy and one penis in ass",
|
||||
r"anal penetration with visible genital contact",
|
||||
r"rear-entry anal penetration",
|
||||
r"anal sex with spread cheeks",
|
||||
r"ass stretched around a penis",
|
||||
r"penis entering ass",
|
||||
r"deep anal sex",
|
||||
r"bent-over anal sex",
|
||||
r"hardcore anal thrusting",
|
||||
r"vaginal penetration with visible genital contact",
|
||||
r"penis entering pussy",
|
||||
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",
|
||||
r"fellatio with penis in mouth",
|
||||
r"deepthroat blowjob",
|
||||
r"blowjob",
|
||||
r"penis sucking with visible saliva",
|
||||
r"cunnilingus with tongue on pussy",
|
||||
r"face-sitting cunnilingus",
|
||||
r"pussy licking with thighs spread",
|
||||
r"oral sex with tongue and fingers",
|
||||
r"oral contact with mouth on the visible genitals",
|
||||
r"sixty-nine oral sex",
|
||||
)
|
||||
act_pattern = "|".join(act_patterns)
|
||||
position_pattern = (
|
||||
r"missionary position|cowgirl position|reverse cowgirl position|doggy style position|"
|
||||
r"standing sex position|spooning sex position|edge-of-bed position|kneeling straddle position|"
|
||||
r"lotus sex position|bent-over position|kneeling oral position|face-sitting position|"
|
||||
r"sixty-nine position|edge-of-bed oral position|edge-supported oral position|standing oral position|reclining cunnilingus position|"
|
||||
r"straddled oral position|side-lying oral position|spread-leg oral position|chair oral position"
|
||||
)
|
||||
text = re.sub(
|
||||
rf"^({position_pattern})\s+(?:while|with|featuring)\s+(?:{act_pattern})\s*,?\s*",
|
||||
r"\1, ",
|
||||
text,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
text = re.sub(
|
||||
rf"^(?:{act_pattern})\s*(?:in|from|on|with|while|featuring)?\s*",
|
||||
"",
|
||||
text,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
text = re.sub(r"^(?:position|pose)\s+", "", text, flags=re.IGNORECASE)
|
||||
text = re.sub(r"^with\s+", "", text, flags=re.IGNORECASE)
|
||||
text = re.sub(r"\bwith with\b", "with", text, flags=re.IGNORECASE)
|
||||
text = re.sub(r",\s*with\s+", ", ", text, flags=re.IGNORECASE)
|
||||
text = re.sub(r",\s+and\s+", ", ", text)
|
||||
text = re.sub(r"\s*,\s*", ", ", text).strip(" ,;")
|
||||
return _clean(text)
|
||||
|
||||
|
||||
def _dedupe_hardcore_detail(detail: str, anchor: str) -> str:
|
||||
detail = _clean(detail)
|
||||
anchor_lower = anchor.lower()
|
||||
duplicate_phrases = {
|
||||
"front-and-back": (r"front-and-back contact",),
|
||||
"side-lying oral": (r"side-lying oral position",),
|
||||
"kneeling oral": (r"kneeling oral position",),
|
||||
"face-sitting": (r"face-sitting position",),
|
||||
"sixty-nine": (
|
||||
r"sixty-nine position",
|
||||
r"sixty-nine oral sex",
|
||||
r"kneeling oral position",
|
||||
r"face-sitting position",
|
||||
r"edge-of-bed oral position",
|
||||
r"standing oral position",
|
||||
r"reclining cunnilingus position",
|
||||
r"straddled oral position",
|
||||
r"side-lying oral position",
|
||||
r"spread-leg oral position",
|
||||
r"chair oral position",
|
||||
),
|
||||
"edge-supported oral": (r"edge-of-bed oral position", r"edge-supported oral position"),
|
||||
"edge-of-bed oral": (r"edge-of-bed oral position", r"edge-supported oral position"),
|
||||
"standing oral": (r"standing oral position",),
|
||||
"spread-leg oral": (r"spread-leg oral position",),
|
||||
"chair oral": (r"chair oral position",),
|
||||
"reclining cunnilingus": (r"reclining cunnilingus position",),
|
||||
"straddled cunnilingus": (r"straddled oral position", r"straddled cunnilingus position"),
|
||||
"open-thigh cunnilingus": (r"reclining cunnilingus position", r"straddled cunnilingus position"),
|
||||
"bent-over": (r"bent-over position",),
|
||||
"face-down": (r"face-down ass-up position",),
|
||||
"missionary": (r"missionary position",),
|
||||
"reverse cowgirl": (r"reverse cowgirl position",),
|
||||
"cowgirl": (r"cowgirl position",),
|
||||
"doggy-style": (r"doggy style 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"),
|
||||
}
|
||||
for anchor_token, phrases in duplicate_phrases.items():
|
||||
if anchor_token in anchor_lower:
|
||||
for phrase in phrases:
|
||||
detail = re.sub(rf"\b{phrase}\b,?\s*", "", detail, flags=re.IGNORECASE)
|
||||
detail = re.sub(r"^\s*,\s*", "", detail)
|
||||
detail = re.sub(r",\s*,", ",", detail)
|
||||
return _clean(detail).strip(" ,;")
|
||||
|
||||
|
||||
def _dedupe_toy_double_detail(detail: str) -> str:
|
||||
detail = _clean(detail)
|
||||
if not detail:
|
||||
return ""
|
||||
angle_view = (
|
||||
r"(?:rear-view|side-profile|low-angle|mirror-reflected|overhead|close-up|wide full-body|front-facing with hips turned)"
|
||||
)
|
||||
toy_act = (
|
||||
r"(?:penis and toy double penetration|toy-assisted vaginal and anal penetration at the same time|toy and strap-on double penetration)"
|
||||
)
|
||||
detail = re.sub(
|
||||
rf"\b({angle_view}\s+view of\s+){toy_act}\b",
|
||||
r"\1the rear-entry contact",
|
||||
detail,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
detail = re.sub(rf",?\s*\b{toy_act}\b", "", detail, flags=re.IGNORECASE)
|
||||
duplicate_phrases = (
|
||||
"toy-assisted second contact aligned behind the body",
|
||||
"toy aligned for a second penetration point",
|
||||
"rear-entry body alignment",
|
||||
"close body alignment",
|
||||
"stacked bodies in close contact",
|
||||
"one body between two partners",
|
||||
"one partner behind and one partner in front",
|
||||
"two partners penetrating at once",
|
||||
"one partner held between two bodies",
|
||||
"front-and-back contact",
|
||||
"three bodies locked together",
|
||||
"kneeling center partner",
|
||||
)
|
||||
for phrase in duplicate_phrases:
|
||||
detail = re.sub(rf",?\s*\b{re.escape(phrase)}\b", "", detail, flags=re.IGNORECASE)
|
||||
detail = re.sub(r"^\s*,\s*", "", detail)
|
||||
detail = re.sub(r",\s*,", ",", detail)
|
||||
return _clean(detail).strip(" ,;")
|
||||
|
||||
|
||||
def _dedupe_outercourse_detail(detail: str, role_graph: str, hard_item: str = "", axis_values: Any = None) -> str:
|
||||
detail = _clean(detail)
|
||||
if not detail:
|
||||
return ""
|
||||
context = position_context_text(role_graph, hard_item, "", axis_values)
|
||||
context_lower = context.lower()
|
||||
breast_sex = any(term in context_lower for term in ("boobjob", "titjob", "breast sex", "breast-sex"))
|
||||
clauses: list[str] = []
|
||||
for clause in detail_clauses(detail):
|
||||
lower = clause.lower()
|
||||
if breast_sex:
|
||||
if lower in ("penis", "breasts", "mouth clearly visible"):
|
||||
continue
|
||||
if any(
|
||||
term in lower
|
||||
for term in (
|
||||
"boobjob",
|
||||
"titjob",
|
||||
"breast-sex",
|
||||
"breast sex",
|
||||
"seated titjob position",
|
||||
"kneeling boobjob position",
|
||||
"tight close-up breast-sex position",
|
||||
"penis shaft compressed between breasts",
|
||||
"penis squeezed between both breasts",
|
||||
"hands pressing the breasts tightly",
|
||||
"hands pressing breasts firmly together",
|
||||
"fingers spreading the breasts around the penis shaft",
|
||||
"soft flesh squeezed around the penis shaft",
|
||||
"hand wrapped around the penis shaft",
|
||||
"glans near the mouth",
|
||||
"glans visible",
|
||||
"penis, breasts, and mouth clearly visible",
|
||||
)
|
||||
):
|
||||
continue
|
||||
clauses.append(clause)
|
||||
return join_detail_clauses(clauses)
|
||||
|
||||
|
||||
def _dedupe_oral_detail(detail: str, role_graph: str, hard_item: str = "", axis_values: Any = None) -> str:
|
||||
detail = _clean(detail)
|
||||
if not detail:
|
||||
return ""
|
||||
context = position_context_text(role_graph, hard_item, "", axis_values)
|
||||
woman_gives = any(
|
||||
term in context
|
||||
for term in (
|
||||
"takes the man's penis",
|
||||
"takes his penis",
|
||||
"penis in her mouth",
|
||||
"mouth at penis level",
|
||||
"mouth on his penis",
|
||||
"fellatio",
|
||||
"blowjob",
|
||||
"deepthroat",
|
||||
"penis sucking",
|
||||
)
|
||||
)
|
||||
clauses: list[str] = []
|
||||
for clause in detail_clauses(detail):
|
||||
lower = clause.lower()
|
||||
if any(
|
||||
term in lower
|
||||
for term in (
|
||||
"kneeling oral position",
|
||||
"standing oral position",
|
||||
"edge-of-bed oral position",
|
||||
"side-lying oral position",
|
||||
"chair oral position",
|
||||
"reclining cunnilingus position",
|
||||
"face-sitting position",
|
||||
"sixty-nine position",
|
||||
"fellatio with penis in mouth",
|
||||
"deepthroat blowjob",
|
||||
"penis sucking with visible saliva",
|
||||
"cunnilingus with tongue on pussy",
|
||||
"oral sex with tongue and fingers",
|
||||
"oral contact with mouth on the visible genitals",
|
||||
"bodies stacked close together",
|
||||
"body angle keeps the penis and face readable",
|
||||
)
|
||||
):
|
||||
continue
|
||||
if woman_gives and lower == "wet shine on genitals":
|
||||
clause = "saliva dripping on the penis"
|
||||
clauses.append(clause)
|
||||
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 _normalize_climax_view_clause(clause: str, role_graph: str) -> str:
|
||||
lower = clause.lower()
|
||||
if "view" not in lower and "frame" not in lower:
|
||||
@@ -652,24 +319,24 @@ def hardcore_action_sentence(
|
||||
detail = _dedupe_climax_detail(detail, role_graph, detail_density)
|
||||
elif is_foreplay_text(role_graph, hard_item, composition, axis_values_text(axis_values)):
|
||||
anchor = ""
|
||||
detail = _sanitize_foreplay_detail(detail, role_graph, composition)
|
||||
detail = sanitize_foreplay_detail(detail, role_graph, composition)
|
||||
detail = limit_detail_for_density(detail, detail_density, False)
|
||||
elif is_outercourse:
|
||||
anchor = ""
|
||||
detail = _dedupe_outercourse_detail(detail, role_graph, hard_item, axis_values)
|
||||
detail = dedupe_outercourse_detail(detail, role_graph, hard_item, axis_values)
|
||||
detail = limit_detail_for_density(detail, detail_density, False)
|
||||
elif is_oral and role_graph:
|
||||
anchor = ""
|
||||
detail = _dedupe_oral_detail(detail, role_graph, hard_item, axis_values)
|
||||
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 = 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
|
||||
detail = dedupe_anchor_detail(detail, anchor) if anchor else detail
|
||||
if is_toy_assisted_double_text(role_graph, hard_item, composition, axis_values_text(axis_values)):
|
||||
detail = _dedupe_toy_double_detail(detail)
|
||||
detail = dedupe_toy_double_detail(detail)
|
||||
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 ""
|
||||
|
||||
Reference in New Issue
Block a user