Specialize Krea POV oral positioning
This commit is contained in:
+189
-17
@@ -8,6 +8,7 @@ try:
|
|||||||
from .krea_action_context import (
|
from .krea_action_context import (
|
||||||
axis_values_text,
|
axis_values_text,
|
||||||
is_climax_text,
|
is_climax_text,
|
||||||
|
is_oral_text,
|
||||||
is_outercourse_text,
|
is_outercourse_text,
|
||||||
is_toy_assisted_double_text,
|
is_toy_assisted_double_text,
|
||||||
position_context_text,
|
position_context_text,
|
||||||
@@ -18,6 +19,7 @@ except ImportError: # Allows local smoke tests with `python -c`.
|
|||||||
from krea_action_context import (
|
from krea_action_context import (
|
||||||
axis_values_text,
|
axis_values_text,
|
||||||
is_climax_text,
|
is_climax_text,
|
||||||
|
is_oral_text,
|
||||||
is_outercourse_text,
|
is_outercourse_text,
|
||||||
is_toy_assisted_double_text,
|
is_toy_assisted_double_text,
|
||||||
position_context_text,
|
position_context_text,
|
||||||
@@ -76,12 +78,21 @@ def pov_clean_detail(detail: Any, context: str, detail_density: str) -> str:
|
|||||||
detail = re.sub(r"\bthe POV viewer\b", "the viewer", detail, flags=re.IGNORECASE)
|
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'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"\bthe man\b", "the viewer", detail, flags=re.IGNORECASE)
|
||||||
|
detail = re.sub(r"\bhe\b", "the viewer", detail, flags=re.IGNORECASE)
|
||||||
|
detail = re.sub(r"\bhis\b", "the viewer's", detail, flags=re.IGNORECASE)
|
||||||
|
detail = re.sub(r"\bhim\b", "the viewer", detail, flags=re.IGNORECASE)
|
||||||
detail = re.sub(
|
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*",
|
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,
|
detail,
|
||||||
flags=re.IGNORECASE,
|
flags=re.IGNORECASE,
|
||||||
)
|
)
|
||||||
|
detail = re.sub(
|
||||||
|
r"^(?:kneeling oral|standing oral|chair oral|side-lying oral|sixty-nine|edge-supported oral|edge-of-bed oral|reclining cunnilingus|straddled oral|spread-leg oral)\s+(?:position|pose),?\s*",
|
||||||
|
"",
|
||||||
|
detail,
|
||||||
|
flags=re.IGNORECASE,
|
||||||
|
)
|
||||||
detail = re.sub(r"^(?:featuring|with)\s+", "", detail, flags=re.IGNORECASE)
|
detail = re.sub(r"^(?:featuring|with)\s+", "", detail, flags=re.IGNORECASE)
|
||||||
detail = re.sub(
|
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*",
|
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*",
|
||||||
@@ -127,6 +138,26 @@ def pov_clean_detail(detail: Any, context: str, detail_density: str) -> str:
|
|||||||
return limit_detail_for_density(detail, detail_density, is_climax_text(context, detail))
|
return limit_detail_for_density(detail, detail_density, is_climax_text(context, detail))
|
||||||
|
|
||||||
|
|
||||||
|
def pov_clean_oral_detail(detail: Any, context: str, detail_density: str) -> str:
|
||||||
|
detail = pov_clean_detail(detail, context, detail_density)
|
||||||
|
if not detail:
|
||||||
|
return ""
|
||||||
|
duplicate_patterns = (
|
||||||
|
r"\bthe woman takes the viewer's penis in her mouth with\s+",
|
||||||
|
r"\bthe woman takes the viewer's penis in her mouth\b,?\s*",
|
||||||
|
r"\bher mouth on the viewer's penis\b,?\s*",
|
||||||
|
r"\bthe viewer's mouth on the woman's pussy\b,?\s*",
|
||||||
|
r"\bmouth on the viewer's penis\b,?\s*",
|
||||||
|
r"\bmouth on the woman's pussy\b,?\s*",
|
||||||
|
)
|
||||||
|
for pattern in duplicate_patterns:
|
||||||
|
detail = re.sub(pattern, "", detail, flags=re.IGNORECASE)
|
||||||
|
detail = re.sub(r"\bwith\s+(?=[,;.]|$)", "", detail, flags=re.IGNORECASE)
|
||||||
|
detail = re.sub(r"\s*,\s*", ", ", detail)
|
||||||
|
detail = re.sub(r",\s*,", ",", detail)
|
||||||
|
return _clean(detail).strip(" ,;")
|
||||||
|
|
||||||
|
|
||||||
def pov_hardcore_pose_sentence(
|
def pov_hardcore_pose_sentence(
|
||||||
action: Any,
|
action: Any,
|
||||||
role_graph: Any,
|
role_graph: Any,
|
||||||
@@ -154,6 +185,66 @@ def pov_hardcore_pose_sentence(
|
|||||||
def outercourse_sentence(base: str) -> str:
|
def outercourse_sentence(base: str) -> str:
|
||||||
return _clean(base).rstrip(".")
|
return _clean(base).rstrip(".")
|
||||||
|
|
||||||
|
def oral_sentence(base: str) -> str:
|
||||||
|
details = ""
|
||||||
|
if ";" in action_text:
|
||||||
|
details = pov_clean_oral_detail(action_text.split(";", 1)[1], f"{context} {base}", detail_density)
|
||||||
|
return _clean(f"{base}; {details}" if details else base).rstrip(".")
|
||||||
|
|
||||||
|
def oral_direction() -> tuple[bool, bool]:
|
||||||
|
oral_context = f"{context} {action_lower}"
|
||||||
|
woman_gives = any(
|
||||||
|
token in oral_context
|
||||||
|
for token in (
|
||||||
|
"fellatio",
|
||||||
|
"blowjob",
|
||||||
|
"deepthroat",
|
||||||
|
"penis sucking",
|
||||||
|
"penis in her mouth",
|
||||||
|
"mouth on the viewer's penis",
|
||||||
|
"mouth on viewer's penis",
|
||||||
|
"takes the viewer's penis",
|
||||||
|
"takes the man's penis",
|
||||||
|
"mouth at penis level",
|
||||||
|
"lips wrapped",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
man_gives = any(
|
||||||
|
token in oral_context
|
||||||
|
for token in (
|
||||||
|
"cunnilingus",
|
||||||
|
"pussy licking",
|
||||||
|
"mouth on the woman's pussy",
|
||||||
|
"mouth on her pussy",
|
||||||
|
"mouth pressed to her pussy",
|
||||||
|
"tongue on pussy",
|
||||||
|
"face-sitting",
|
||||||
|
"straddles his face",
|
||||||
|
"straddling the viewer's face",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if "sixty-nine" in oral_context:
|
||||||
|
return True, True
|
||||||
|
return woman_gives, man_gives
|
||||||
|
|
||||||
|
penetrative_tokens = (
|
||||||
|
"penetrat",
|
||||||
|
"thrust",
|
||||||
|
"anal",
|
||||||
|
"cowgirl",
|
||||||
|
"missionary",
|
||||||
|
"doggy",
|
||||||
|
"rear-entry",
|
||||||
|
"spooning",
|
||||||
|
"bent-over",
|
||||||
|
"face-down",
|
||||||
|
"ejaculat",
|
||||||
|
"semen",
|
||||||
|
"cumshot",
|
||||||
|
"climax",
|
||||||
|
)
|
||||||
|
has_penetrative_context = any(token in context or token in action_lower for token in penetrative_tokens)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
"face-sitting" in context
|
"face-sitting" in context
|
||||||
or "face sitting" in context
|
or "face sitting" in context
|
||||||
@@ -199,24 +290,105 @@ def pov_hardcore_pose_sentence(
|
|||||||
return outercourse_sentence(
|
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"
|
"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",
|
if is_oral_text(context, action_lower) and not has_penetrative_context:
|
||||||
"thrust",
|
woman_gives, man_gives = oral_direction()
|
||||||
"anal",
|
if "sixty-nine" in position_context:
|
||||||
"cowgirl",
|
return oral_sentence(
|
||||||
"missionary",
|
"POV sixty-nine oral position: the woman lies head-to-hips over the viewer, her pelvis close to his face and her head lowered toward his hips; "
|
||||||
"doggy",
|
"her mouth on the viewer's penis and the viewer's mouth on the woman's pussy, with her torso, hips, mouth, and the viewer's lower-foreground body cues aligned in one first-person frame"
|
||||||
"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):
|
if "side-lying oral" in position_context or "side lying oral" in position_context:
|
||||||
|
if woman_gives and not man_gives:
|
||||||
|
return oral_sentence(
|
||||||
|
"POV side-lying oral position: the viewer lies on his side with hips angled toward the woman while she lies beside his thighs; "
|
||||||
|
"her head stays at penis height with her mouth on the viewer's penis, shoulders and hands close to his pelvis in the lower foreground"
|
||||||
|
)
|
||||||
|
return oral_sentence(
|
||||||
|
"POV side-lying cunnilingus position: the woman lies on her side with her top thigh lifted while the viewer lies beside her hips; "
|
||||||
|
"his face is at pussy height, with her thigh, hip, and torso forming a clear side-profile first-person frame"
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
"edge-supported oral" in position_context
|
||||||
|
or "edge-of-bed oral" in position_context
|
||||||
|
or "edge of bed oral" in position_context
|
||||||
|
or "raised edge" in position_context
|
||||||
|
):
|
||||||
|
if woman_gives and not man_gives:
|
||||||
|
return oral_sentence(
|
||||||
|
"POV raised-edge oral position: the viewer sits at the raised edge with legs apart while the woman kneels directly between his thighs; "
|
||||||
|
"her head is at penis height, mouth on the viewer's penis, with his thighs framing her shoulders in the lower foreground"
|
||||||
|
)
|
||||||
|
return oral_sentence(
|
||||||
|
"POV raised-edge cunnilingus position: the woman reclines at the raised edge with thighs open toward the viewer; "
|
||||||
|
"the viewer kneels between her legs with his face at pussy height, her hips and open thighs framing the first-person view"
|
||||||
|
)
|
||||||
|
if "chair oral" in position_context:
|
||||||
|
if woman_gives and not man_gives:
|
||||||
|
return oral_sentence(
|
||||||
|
"POV chair oral position: the viewer sits in a chair with legs apart while the woman kneels between the viewer's thighs; "
|
||||||
|
"her head is low at his pelvis, mouth on the viewer's penis, with chair seat, thighs, and hands anchoring the lower foreground"
|
||||||
|
)
|
||||||
|
return oral_sentence(
|
||||||
|
"POV chair cunnilingus position: the woman sits in the chair with thighs open while the viewer kneels between her legs; "
|
||||||
|
"his face is at pussy height and her hips, knees, and chair seat define the first-person geometry"
|
||||||
|
)
|
||||||
|
if "standing oral" in position_context:
|
||||||
|
if man_gives and not woman_gives:
|
||||||
|
return oral_sentence(
|
||||||
|
"POV standing cunnilingus position: the woman stands braced with one thigh lifted while the viewer kneels in front of her; "
|
||||||
|
"his face is at pussy height, with her raised thigh, hips, and standing leg clearly framing the view"
|
||||||
|
)
|
||||||
|
return oral_sentence(
|
||||||
|
"POV standing oral position: the viewer stands over her with hips forward while the woman kneels directly in front of him at hip height; "
|
||||||
|
"her head is tilted up at penis level, mouth on the viewer's penis, with his thighs and hands in the lower foreground"
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
"reclining cunnilingus" in position_context
|
||||||
|
or "spread-leg oral" in position_context
|
||||||
|
or "open-thigh" in position_context
|
||||||
|
or "open thigh" in position_context
|
||||||
|
):
|
||||||
|
if woman_gives and not man_gives:
|
||||||
|
return oral_sentence(
|
||||||
|
"POV reclining oral position: the viewer reclines with thighs apart while the woman kneels low between his legs; "
|
||||||
|
"her face stays at penis height with her mouth on the viewer's penis and his thighs framing the first-person view"
|
||||||
|
)
|
||||||
|
return oral_sentence(
|
||||||
|
"POV open-thigh cunnilingus position: the woman reclines on her back with thighs spread toward the viewer; "
|
||||||
|
"the viewer kneels between her legs with his face at pussy height, her knees, hips, and torso aligned toward the camera"
|
||||||
|
)
|
||||||
|
if "straddled oral" in position_context:
|
||||||
|
if woman_gives and not man_gives:
|
||||||
|
return oral_sentence(
|
||||||
|
"POV straddled oral position: the viewer leans forward near the woman's face while she kneels below his pelvis; "
|
||||||
|
"her mouth stays on the viewer's penis with her head tilted upward and his thighs framing the lower foreground"
|
||||||
|
)
|
||||||
|
return oral_sentence(
|
||||||
|
"POV straddled cunnilingus position: the woman straddles above the viewer's face with her thighs framing his head; "
|
||||||
|
"her pussy stays directly over the viewer's mouth in a close first-person oral frame"
|
||||||
|
)
|
||||||
|
if "kneeling oral" in position_context or "kneeling" in position_context:
|
||||||
|
if man_gives and not woman_gives:
|
||||||
|
return oral_sentence(
|
||||||
|
"POV kneeling cunnilingus position: the woman kneels with thighs parted and hips angled forward while the viewer kneels in front of her; "
|
||||||
|
"his face is at pussy height, with her knees, hips, and torso readable from the first-person angle"
|
||||||
|
)
|
||||||
|
return oral_sentence(
|
||||||
|
"POV kneeling oral position: the viewer stands over her with hips forward while the woman kneels directly in front of him; "
|
||||||
|
"her head is at penis height, mouth on the viewer's penis, shoulders below his hips and his thighs framing the lower foreground"
|
||||||
|
)
|
||||||
|
if man_gives and not woman_gives:
|
||||||
|
return oral_sentence(
|
||||||
|
"POV cunnilingus position: the woman lies back with thighs open toward the viewer while he kneels between her legs; "
|
||||||
|
"his face is at pussy height, with her knees, hips, and torso forming the first-person frame"
|
||||||
|
)
|
||||||
|
return oral_sentence(
|
||||||
|
"POV oral position: the woman kneels close at the viewer's pelvis with her head at penis height; "
|
||||||
|
"her mouth is on the viewer's penis, shoulders between his thighs, and the viewer's hands or thighs anchor the lower foreground"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not has_penetrative_context:
|
||||||
return ""
|
return ""
|
||||||
oral_only = any(token in context for token in ("oral", "blowjob", "cunnilingus", "mouth on", "penis in her mouth"))
|
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")):
|
if oral_only and not any(token in context for token in ("penetrat", "thrust", "anal", "ejaculat", "semen", "cumshot", "climax")):
|
||||||
|
|||||||
+12
-6
@@ -6199,37 +6199,37 @@ def smoke_pov_oral_position_routes() -> None:
|
|||||||
"pov_oral_kneeling",
|
"pov_oral_kneeling",
|
||||||
"kneeling",
|
"kneeling",
|
||||||
("viewer's penis", "takes the viewer's penis in her mouth"),
|
("viewer's penis", "takes the viewer's penis in her mouth"),
|
||||||
("takes the viewer's penis in her mouth", "viewer stands over her"),
|
("pov kneeling oral position", "viewer stands over her", "head is at penis height", "thighs framing"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"pov_oral_face_sitting",
|
"pov_oral_face_sitting",
|
||||||
"face_sitting",
|
"face_sitting",
|
||||||
("straddling the viewer's face", "pussy directly over the viewer's mouth"),
|
("straddling the viewer's face", "pussy directly over the viewer's mouth"),
|
||||||
("straddling the viewer's face", "tongue contact visible"),
|
("close first-person underview", "straddling the viewer's face", "tongue contact visible"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"pov_oral_sixty_nine",
|
"pov_oral_sixty_nine",
|
||||||
"sixty_nine",
|
"sixty_nine",
|
||||||
("head-to-hips", "viewer's mouth on woman a's pussy"),
|
("head-to-hips", "viewer's mouth on woman a's pussy"),
|
||||||
("head-to-hips", "viewer's mouth on the woman's pussy"),
|
("pov sixty-nine oral position", "head-to-hips", "viewer's mouth on the woman's pussy", "lower-foreground body cues aligned"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"pov_oral_edge_supported",
|
"pov_oral_edge_supported",
|
||||||
"edge_supported",
|
"edge_supported",
|
||||||
("raised edge with thighs open", "viewer kneels between her legs"),
|
("raised edge with thighs open", "viewer kneels between her legs"),
|
||||||
("raised edge with thighs open", "viewer kneels between her legs"),
|
("pov raised-edge cunnilingus position", "raised edge with thighs open", "face at pussy height"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"pov_oral_side_lying",
|
"pov_oral_side_lying",
|
||||||
"side_lying",
|
"side_lying",
|
||||||
("woman a lies on her side", "viewer lies beside her hips"),
|
("woman a lies on her side", "viewer lies beside her hips"),
|
||||||
("woman lies on her side", "viewer lies beside her hips"),
|
("pov side-lying cunnilingus position", "woman lies on her side", "top thigh lifted", "viewer lies beside her hips"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"pov_oral_chair",
|
"pov_oral_chair",
|
||||||
"chair_oral",
|
"chair_oral",
|
||||||
("viewer sits in a chair", "kneels between his thighs"),
|
("viewer sits in a chair", "kneels between his thighs"),
|
||||||
("viewer sits in a chair", "kneels between the viewer's thighs"),
|
("pov chair oral position", "viewer sits in a chair", "kneels between the viewer's thighs", "head is low at his pelvis"),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
for offset, (name, position_key, role_terms, krea_terms) in enumerate(cases, start=3701):
|
for offset, (name, position_key, role_terms, krea_terms) in enumerate(cases, start=3701):
|
||||||
@@ -6272,6 +6272,12 @@ def smoke_pov_oral_position_routes() -> None:
|
|||||||
_expect("viewer" in prompt and "first-person" in prompt, f"{name} Krea prompt lost POV wording")
|
_expect("viewer" in prompt and "first-person" in prompt, f"{name} Krea prompt lost POV wording")
|
||||||
_expect("viewer lies on the viewer" not in prompt, f"{name} Krea prompt kept recursive POV wording: {prompt}")
|
_expect("viewer lies on the viewer" not in prompt, f"{name} Krea prompt kept recursive POV wording: {prompt}")
|
||||||
_expect("camera:" not in krea.get("krea_prompt", ""), f"{name} Krea prompt emitted normal third-person camera directive")
|
_expect("camera:" not in krea.get("krea_prompt", ""), f"{name} Krea prompt emitted normal third-person camera directive")
|
||||||
|
_expect("as he looks down" not in prompt, f"{name} Krea prompt kept unresolved male pronoun: {prompt}")
|
||||||
|
_expect(
|
||||||
|
"the woman takes the viewer's penis in her mouth with" not in prompt,
|
||||||
|
f"{name} Krea prompt repeated oral contact detail after POV rewrite: {prompt}",
|
||||||
|
)
|
||||||
|
_expect("edge-supported oral position;" not in prompt, f"{name} Krea prompt kept source oral-position scaffold: {prompt}")
|
||||||
for term in krea_terms:
|
for term in krea_terms:
|
||||||
_expect(term in prompt, f"{name} Krea prompt missing {term!r}: {prompt}")
|
_expect(term in prompt, f"{name} Krea prompt missing {term!r}: {prompt}")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user