Files
ComfyUI-Ethanfel-Prompt-Bui…/krea_pov_actions.py
T

329 lines
15 KiB
Python

from __future__ import annotations
import re
from typing import Any
try:
from .krea_action_context import (
axis_values_text,
is_climax_text,
is_outercourse_text,
is_toy_assisted_double_text,
position_context_text,
)
from .krea_detail import limit_detail_for_density
except ImportError: # Allows local smoke tests with `python -c`.
from krea_action_context import (
axis_values_text,
is_climax_text,
is_outercourse_text,
is_toy_assisted_double_text,
position_context_text,
)
from krea_detail import limit_detail_for_density
def _clean(value: Any) -> str:
text = "" if value is None else str(value)
text = text.replace("\n", " ")
text = re.sub(r"\s+", " ", text).strip()
text = re.sub(r"\s+([,.;:])", r"\1", text)
return text
def pov_ejaculation_target(context: str) -> str:
if any(token in context for token in ("face", "mouth", "lips", "tongue", "chin")):
return "onto her face and chest"
if any(token in context for token in ("lower back", "ass", "rear-entry", "face-down", "bent-over", "doggy")):
return "across her ass, thighs, and lower back"
if any(token in context for token in ("pussy", "open thighs", "thighs", "legs open")):
return "across her pussy and thighs"
return "onto her body"
def pov_contact_clause(
action: Any,
role_graph: Any,
hard_item: Any,
axis_values: Any,
context: str,
) -> str:
is_climax = is_climax_text(action, role_graph, hard_item, axis_values_text(axis_values))
if is_climax:
return f"as he ejaculates semen {pov_ejaculation_target(context)}"
is_anal = any(
token in context
for token in (
"anal",
"into her ass",
"penis entering ass",
"ass stretched",
"thrusts into her ass",
)
)
contact = "as his penis penetrates her ass" if is_anal else "as his penis penetrates her pussy"
if is_toy_assisted_double_text(action, role_graph, hard_item, axis_values_text(axis_values)):
contact = f"{contact} while a toy is positioned at the second penetration point"
return contact
def pov_clean_detail(detail: Any, context: str, detail_density: str) -> str:
detail = _clean(detail).strip(" .;")
if not detail:
return ""
detail = re.sub(r"\bthe POV viewer\b", "the viewer", detail, flags=re.IGNORECASE)
detail = re.sub(r"\bthe man's\b", "the viewer's", detail, flags=re.IGNORECASE)
detail = re.sub(r"\bthe man\b", "the viewer", detail, flags=re.IGNORECASE)
detail = re.sub(
r"^(?:missionary|cowgirl|reverse cowgirl|doggy style|standing sex|spooning sex|edge-supported|edge-of-bed|raised edge|kneeling straddle|lotus sex|bent-over|face-down ass-up|side-lying|kneeling rear-entry)\s+(?:position|pose)\s+(?:featuring|with|while|,)?\s*",
"",
detail,
flags=re.IGNORECASE,
)
detail = re.sub(r"^(?:featuring|with)\s+", "", detail, flags=re.IGNORECASE)
detail = re.sub(
r"^(?:full-body|explicit|close-contact|deep|hardcore|vaginal|anal)?\s*(?:penetrative sex|vaginal sex|anal sex|penetration with visible genital contact|hardcore vaginal thrusting|hardcore anal thrusting),?\s*",
"",
detail,
flags=re.IGNORECASE,
)
detail = re.sub(
r"\b(?:front-facing|close-up|wide full-body|wide|overhead|mirror-reflected|low-angle|side-profile|bed-level)\s+view of\b",
"visible",
detail,
flags=re.IGNORECASE,
)
detail = re.sub(
r",?\s*\bthe viewer is behind her at hip level with (?:his|the viewer's) hands on her hips in the foreground as (?:his|the viewer's) penis (?:thrusts into her|penetrates her pussy)\b",
"",
detail,
flags=re.IGNORECASE,
)
detail = re.sub(
r",?\s*\bthe woman is on all fours directly in front of the viewer with hips raised and back arched\b",
"",
detail,
flags=re.IGNORECASE,
)
if any(token in context for token in ("ass raised", "on all fours", "doggy", "rear-entry", "bent-over", "face-down")):
detail = re.sub(
r",?\s*\b(?:one body pinned under another|bodies stacked close together|bodies tangled on the sheets)\b",
"",
detail,
flags=re.IGNORECASE,
)
if "toy is positioned at the second penetration point" in context:
detail = re.sub(
r",?\s*\b(?:toy aligned for a second penetration point|toy-assisted second contact aligned behind the body)\b",
"",
detail,
flags=re.IGNORECASE,
)
detail = re.sub(r"\bwith with\b", "with", detail, flags=re.IGNORECASE)
detail = re.sub(r"\s*,\s*", ", ", detail)
detail = re.sub(r",\s*,", ",", detail).strip(" ,;")
return limit_detail_for_density(detail, detail_density, is_climax_text(context, detail))
def pov_hardcore_pose_sentence(
action: Any,
role_graph: Any,
hard_item: Any,
composition: Any = "",
axis_values: Any = None,
detail_density: str = "balanced",
) -> str:
context = position_context_text(role_graph, hard_item, composition, axis_values)
action_text = _clean(action)
action_lower = action_text.lower()
if not context:
context = action_lower
def sentence(base: str) -> str:
details = ""
if ";" in action_text:
details = pov_clean_detail(action_text.split(";", 1)[1], f"{context} {base}", detail_density)
return f"{base}; {details}" if details else base
def outercourse_sentence(base: str) -> str:
return _clean(base).rstrip(".")
if (
"face-sitting" in context
or "face sitting" in context
or ("straddles" in context and "face" in context and "pussy" in context)
):
return outercourse_sentence(
"The woman is above the camera in a close first-person underview, straddling the viewer's face with her thighs on both sides of his head; "
"her pussy is directly over the viewer's mouth in the lower foreground, tongue contact visible from below"
)
if is_outercourse_text(context, action_lower):
if any(term in context for term in ("boobjob", "titjob", "breast sex", "breast-sex")):
return outercourse_sentence(
"The woman kneels between the viewer's open thighs with her torso bent forward over his pelvis and shoulders low; "
"both hands lift and press her breasts tightly around the viewer's penis shaft in the lower foreground, with the glans just below her lips"
)
if any(term in context for term in ("testicle", "balls licking", "balls-licking", "balls and mouth")):
return outercourse_sentence(
"The woman kneels very low between the viewer's open thighs with her torso bent forward and shoulders between his knees; "
"her head is tucked under the penis shaft at the base of the penis, mouth and tongue licking the viewer's balls while his penis points upward above her face in the lower foreground"
)
if any(term in context for term in ("penis licking", "penis-licking", "tongue along", "tongue licking")):
return outercourse_sentence(
"The woman bends forward between the viewer's open thighs, head low under the viewer's penis with her face directly under the penis; "
"her tongue runs along the underside from the penis shaft to the glans while one hand steadies the base of the penis in the lower foreground"
)
if any(term in context for term in ("handjob", "hand job", "hand wrapped", "hand stroking", "manual stimulation")):
return outercourse_sentence(
"The woman kneels between the viewer's open thighs with her torso leaning forward and face visible behind the penis shaft; "
"one hand wraps around the penis shaft in the lower foreground while the other hand steadies the base of the penis as she strokes toward the glans"
)
if any(term in context for term in ("footjob", "soles", "toes curled", "feet stroking")):
return outercourse_sentence(
"The woman faces the viewer with her hips back, torso visible behind her raised legs, and both knees bent open toward the camera; "
"her soles wrap around the penis shaft in the lower foreground, toes curled around the penis shaft with her face visible beyond her feet"
)
return outercourse_sentence(
"The woman stays close to the viewer's pelvis, keeping the non-penetrative contact centered in the lower foreground with her face visible behind the contact"
)
penetrative_tokens = (
"penetrat",
"thrust",
"anal",
"cowgirl",
"missionary",
"doggy",
"rear-entry",
"spooning",
"side-lying",
"bent-over",
"face-down",
"ejaculat",
"semen",
"cumshot",
"climax",
)
if not any(token in context or token in action_lower for token in penetrative_tokens):
return ""
oral_only = any(token in context for token in ("oral", "blowjob", "cunnilingus", "mouth on", "penis in her mouth"))
if oral_only and not any(token in context for token in ("penetrat", "thrust", "anal", "ejaculat", "semen", "cumshot", "climax")):
return ""
contact = pov_contact_clause(action, role_graph, hard_item, axis_values, context)
if "reverse cowgirl" in context:
return sentence(
"POV reverse cowgirl position: the viewer lies on his back while the woman straddles his hips facing away; "
f"her back, ass, thighs, and the viewer's foreground legs are visible {contact}"
)
if "cowgirl" in context or "straddling a partner" in context or "squatting on top" in context:
return sentence(
"POV cowgirl position: the viewer lies on his back while the woman straddles his hips facing him; "
f"her torso, hips, and open thighs fill the frame from below {contact}"
)
if "lotus" in context or "seated in a partner's lap" in context:
return sentence(
"POV lotus position: the viewer sits upright while the woman sits in his lap facing him with her legs around his hips; "
f"her torso and hips stay close to the viewer {contact}"
)
if "kneeling straddle" in context:
return sentence(
"POV kneeling straddle position: the viewer kneels upright while the woman straddles his hips facing him; "
f"both torsos are upright and her hips press directly against him {contact}"
)
if "face-down" in context or "face down" in context:
return sentence(
"The woman is seen from behind with her ass raised toward the POV viewer, lying face-down with hips lifted; "
f"the viewer looks down at her raised ass with foreground hands on her hips {contact}"
)
if "bent-over" in context or "bent over" in context or "bent forward" in context:
return sentence(
"The woman is seen from behind with her ass raised toward the POV viewer, bent forward at the waist with hips lifted and head turned back; "
f"the viewer looks down at her raised ass from behind with foreground hands near her hips {contact}"
)
if "doggy" in context or "all fours" in context or "rear-entry" in context:
return sentence(
"The woman is seen from behind with her ass raised toward the POV viewer, on all fours directly in front of him with hips high and back arched; "
f"the viewer looks down at her raised ass with his hands on her hips in the foreground {contact}"
)
if "standing" in context:
return sentence(
"POV standing rear-entry position: the woman stands braced in front of the viewer with hips angled back and legs steady; "
f"the viewer stands behind her at hip level {contact}"
)
if "spooning" in context or "side-lying" in context or "lies on her side" in context:
return sentence(
"POV side-lying sex position: the woman lies on her side in front of the viewer with thighs parted; "
f"the viewer is behind her along the same body line {contact}"
)
if (
"edge-supported" in context
or "raised edge" in context
or "edge of bed" in context
or "bed edge" in context
or "kneels between her legs" in context
):
return sentence(
"POV raised-edge penetration position: the woman reclines at the raised edge with thighs open toward the viewer; "
f"the viewer kneels between her legs with his hands near her hips {contact}"
)
if "missionary" in context or ("lies on her back" in context and ("legs open" in context or "thighs open" in context)):
return sentence(
"POV missionary position: the woman lies on her back with legs open around the viewer's hips; "
f"the viewer is above her with foreground arms braced beside her body {contact}"
)
return sentence(
"POV penetrative sex position: the woman is directly in front of the viewer with legs open around his hips; "
f"the viewer's foreground hands and body position define the first-person angle {contact}"
)
def pov_action_phrase(
action: Any,
pov_labels: list[str],
role_graph: Any = "",
hard_item: Any = "",
composition: Any = "",
axis_values: Any = None,
detail_density: str = "balanced",
) -> str:
rendered = _clean(action)
if not rendered or not pov_labels:
return rendered
if "Man A" in pov_labels:
pov_sentence = pov_hardcore_pose_sentence(
rendered,
role_graph,
hard_item,
composition,
axis_values,
detail_density,
)
if pov_sentence:
return pov_sentence
for label in sorted(pov_labels, key=len, reverse=True):
escaped = re.escape(label)
rendered = re.sub(rf"\b{escaped}'s\b", "the viewer's", rendered)
rendered = re.sub(rf"\b{escaped}\b", "the viewer", rendered)
if "Man A" in pov_labels:
rendered = re.sub(r"\bthe man's\b", "the viewer's", rendered, flags=re.IGNORECASE)
rendered = re.sub(r"\bthe man\b", "the viewer", rendered, flags=re.IGNORECASE)
rendered = re.sub(r"\bhe\b", "the viewer", rendered, flags=re.IGNORECASE)
rendered = re.sub(r"\bhim\b", "the viewer", rendered, flags=re.IGNORECASE)
rendered = re.sub(r"\bhis\b", "the viewer's", rendered, flags=re.IGNORECASE)
rendered = re.sub(
r"\bthe viewer lies on the viewer's back under her\b",
"the viewer reclines underneath her",
rendered,
flags=re.IGNORECASE,
)
rendered = re.sub(
r"\bthe viewer lies on the viewer's back\b",
"the viewer reclines",
rendered,
flags=re.IGNORECASE,
)
rendered = re.sub(r"\bthe viewer is positioned\b", "the POV camera is positioned", rendered, flags=re.IGNORECASE)
return rendered