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

419 lines
27 KiB
Python

from __future__ import annotations
import random
import re
from typing import Any
try:
from .hardcore_role_oral import build_oral_role_graph
from .hardcore_role_outercourse import build_outercourse_role_graph
except ImportError: # Allows local smoke tests with `python -c`.
from hardcore_role_oral import build_oral_role_graph
from hardcore_role_outercourse import build_outercourse_role_graph
def _lettered(prefix: str, count: int) -> list[str]:
letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
return [f"{prefix.capitalize()} {letters[index]}" for index in range(max(0, count))]
def _pick_distinct(rng: random.Random, items: list[str], count: int) -> list[str]:
if not items:
return []
if len(items) >= count:
return rng.sample(items, count)
picked = list(items)
while len(picked) < count:
picked.append(items[rng.randrange(len(items))])
return picked
def _participant_context(women_count: int, men_count: int) -> dict[str, list[str]]:
women = _lettered("woman", women_count)
men = _lettered("man", men_count)
return {"women": women, "men": men, "people": women + men}
def build_hardcore_role_graph(
rng: random.Random,
subcategory: dict[str, Any],
context: dict[str, Any],
item_axis_values: dict[str, Any] | None = None,
pov_labels: list[str] | None = None,
) -> str:
if context.get("subject_type") != "configured_cast":
return ""
women_count = int(context.get("women_count") or 0)
men_count = int(context.get("men_count") or 0)
people_count = women_count + men_count
if people_count <= 0:
return ""
participants = _participant_context(women_count, men_count)
women = participants["women"]
men = participants["men"]
people = participants["people"]
slug = str(subcategory.get("slug") or subcategory.get("name") or "").lower()
item_text = " ".join((item_axis_values or {}).values()).lower()
def any_person(exclude: set[str] | None = None) -> str:
exclude = exclude or set()
pool = [person for person in people if person not in exclude] or people
return rng.choice(pool)
def any_woman(exclude: set[str] | None = None) -> str:
exclude = exclude or set()
pool = [person for person in women if person not in exclude] or [person for person in people if person not in exclude] or people
return rng.choice(pool)
def any_man(exclude: set[str] | None = None) -> str:
exclude = exclude or set()
pool = [person for person in men if person not in exclude] or [person for person in people if person not in exclude] or people
return rng.choice(pool)
def support_sentence(exclude: set[str]) -> str:
extras = [person for person in people if person not in exclude]
if not extras:
return ""
extra = rng.choice(extras)
actions = [
"kisses and grips the nearest body",
"holds hips open for the camera",
"touches breasts, thighs, and stomach",
"keeps one hand on a partner's ass",
"watches close and joins the body contact",
"presses in from the side with hands on skin",
]
return f" {extra} {rng.choice(actions)}."
def foreplay_position_graph(primary: str, partner: str) -> str:
text = " ".join(
str(part or "").lower()
for part in (
item_text,
*((item_axis_values or {}).values()),
)
)
if any(term in text for term in ("undressing", "removing clothing", "removing clothes", "pulling clothing", "sliding straps", "unbuttoning")):
return (
f"{primary} and {partner} stand close while {partner}'s hands pull clothing aside from {primary}'s body; "
f"{primary}'s exposed skin and the clothing being removed stay clearly visible."
)
if any(term in text for term in ("breast", "breasts", "nipple", "cupping breasts", "touching breasts")):
return (
f"{primary} and {partner} press their bodies close while {partner}'s hand cups {primary}'s breast; "
f"their faces stay close and the breast-touching gesture is clear."
)
if any(term in text for term in ("face", "cheek", "jaw", "chin", "hand on the cheek", "fingers under the chin")):
return (
f"{primary} and {partner} stand face-to-face at close range while one hand holds {primary}'s cheek and jaw; "
f"their lips are close and the face-touching gesture is clear."
)
if any(term in text for term in ("kiss", "kissing", "mouth-to-mouth", "lips pressed")):
return (
f"{primary} and {partner} press their bodies together and kiss deeply, "
f"with hands on each other's face, waist, and hips."
)
return (
f"{primary} and {partner} are pressed close in a heated foreplay setup, "
f"hands caressing skin while clothing is pulled aside."
)
def interaction_text() -> str:
return " ".join(
str(part or "").lower()
for part in (
item_text,
*((item_axis_values or {}).values()),
)
)
def manual_position_graph(primary: str, partner: str = "") -> str:
text = interaction_text()
if not partner:
if "mutual" in text:
return f"{primary} faces the camera with thighs open, both hands on her body for solo mutual-style masturbation framing."
return f"{primary} reclines with thighs open, one hand between her legs and fingers visibly stimulating her pussy."
if "mutual" in text:
return f"{primary} and {partner} sit close facing each other, both touching themselves while keeping hands, faces, and bodies visible."
if "clit" in text or "clitoris" in text:
return f"{primary} reclines with thighs open while {partner}'s hand is between her legs, fingers rubbing her clit as her hips tilt toward the touch."
if "toy" in text or "vibrator" in text:
return f"{primary} reclines with thighs open while {partner} holds a vibrator or toy against her clit, one hand keeping her thigh open."
return f"{primary} reclines with thighs open while {partner}'s hand is between her legs, fingers visibly stimulating her pussy."
def interaction_position_graph(primary: str, partner: str, third: str = "") -> str:
text = interaction_text()
if "aftercare" in slug or any(term in text for term in ("aftercare", "cleanup", "wiping", "towel", "post-sex", "cuddle")):
if "cleanup" in text or "wiping" in text or "towel" in text:
return f"{primary} reclines after sex while {partner} kneels close and wipes her skin with a towel, hands and relaxed body contact visible."
return f"{primary} and {partner} lie close together after sex, bodies relaxed and hands resting on skin in a post-sex cuddle."
if "camera_performance" in slug or any(term in text for term in ("camera", "presenting", "showing", "viewer", "creator-shot")):
if third:
return f"{primary} faces the camera while {partner} and {third} hold and present her body, hands framing the exposed skin for the viewer."
return f"{primary} faces the camera and presents her body while {partner}'s hands hold her hips or thighs open for a clear creator-shot reveal."
if "body_worship" in slug or any(term in text for term in ("body worship", "nipple", "thigh", "mouth on skin", "kissing down", "ass grabbing")):
if "ass" in text:
return f"{primary} stands or kneels with hips angled back while {partner}'s hands grip her ass, fingers pressing into skin."
if "thigh" in text:
return f"{primary} reclines with thighs open while {partner} kneels close and kisses along her inner thighs, hands holding her legs in place."
if "nipple" in text or "breast" in text:
return f"{primary} arches toward {partner} while {partner}'s mouth is on her breast and one hand cups or squeezes the other breast."
return f"{primary} reclines or leans back while {partner} kisses down her body, hands tracing breasts, waist, hips, and thighs."
if "clothing_position" in slug or any(term in text for term in ("transition", "turning", "pulling onto", "lifting", "guided backward", "clothing", "garment")):
if "turn" in text or "rear-facing" in text:
return f"{partner}'s hands turn {primary} around by the hips, clothing partly moved aside as her body rotates into the next pose."
if "legs" in text or "thigh" in text:
return f"{primary} lies back while {partner} lifts and spreads her legs into position, hands and clothing movement clearly visible."
return f"{primary} and {partner} are mid-transition, with {partner}'s hands moving clothing aside and guiding {primary}'s hips toward the next pose."
if "dominant" in slug or any(term in text for term in ("hair", "wrist", "wrists", "jaw", "chin", "guided", "dominant", "control", "dirty talk", "whisper", "mouth near the ear", "verbal teasing")):
if "dirty talk" in text or "whisper" in text or "mouth near the ear" in text or "verbal teasing" in text:
return f"{partner} leans close to {primary}'s ear for dirty talk while holding her waist and keeping their bodies pressed close."
if "wrist" in text or "wrists" in text:
return f"{primary} lies back while {partner} pins her wrists above her head, both bodies close and the consensual control gesture clearly visible."
if "hair" in text:
return f"{partner} holds {primary}'s hair back while guiding her body closer, face and hair-hold gesture visible."
if "thigh" in text or "spread" in text:
return f"{primary} reclines with thighs open while {partner}'s hands spread her legs and hold the position for the camera."
return f"{partner} guides {primary}'s body with hands on her jaw, waist, and hips, keeping the consensual control gesture readable."
return foreplay_position_graph(primary, partner)
def group_coordination_graph(primary: str, partner: str, third: str) -> str:
observer = third or any_person({primary, partner})
text = interaction_text()
if "camera" in text or "hold" in text or "present" in text:
return f"{primary} is centered while {partner} and {observer} hold and present the body for the camera, each role clearly visible."
if "watch" in text or "waiting" in text:
return f"{primary} is centered while {partner} touches her body and {observer} watches close beside them, hands and faces readable."
return f"{primary} is centered while {partner} touches her body and {observer} stays close as the watching or guiding partner."
def mentions_ass(text: str) -> bool:
return bool(
re.search(
r"\bass\b|ass[- ](?:up|raised|exposed|lifted)|spread cheeks|lower back and ass|cum (?:on|dripping from) ass|pussy, ass|ass and",
text,
)
)
def climax_position_graph(woman: str, man: str, third: str = "") -> str:
if "lying between two partners" in item_text and third:
return f"{woman} lies between {man} and {third}, with {man} under her hips and {third} positioned above her torso as visible semen lands on her body."
if "held between front-and-back partners" in item_text and third:
return f"{woman} is held between {man} behind her and {third} in front of her as visible semen lands across her body."
if "kneeling between standing partners" in item_text and third:
return f"{woman} kneels between {man} and {third} while both stand close around her face and torso for visible ejaculation."
if "side-lying with thighs parted" in item_text:
return f"{woman} lies on her side with thighs parted while {man} kneels beside her hips and ejaculates semen across her thighs and pussy."
if "sitting on the edge of the bed" in item_text:
return f"{woman} sits on the edge of the bed with knees spread while {man} stands close between her legs and ejaculates semen across her body."
if "lying at the bed edge with thighs open" in item_text:
return f"{woman} lies at the bed edge with thighs open while {man} kneels between her legs and ejaculates semen across her pussy and thighs."
if "reclining with thighs open" in item_text or "lying on the back with legs spread" in item_text:
return f"{woman} lies on her back with thighs open while {man} kneels between her legs and ejaculates semen across her pussy and thighs."
if "on all fours with hips raised" in item_text:
return f"{woman} is on all fours with hips raised while {man} is positioned behind her and ejaculates semen across her ass, thighs, and lower back."
if "face-down ass-up" in item_text:
return f"{woman} lies face-down with ass raised while {man} is positioned behind her and ejaculates semen across her lower back and ass."
if "bent over with ass raised" in item_text or "bent over" in item_text:
return f"{woman} is bent forward with hips raised while {man} is positioned behind her, visible semen across her lower back, ass, and thighs."
if "kneeling with mouth open" in item_text:
return f"{woman} kneels in front of {man} at hip height while {man} ejaculates semen onto her face, lips, and chest."
if "kneeling in front of a standing partner" in item_text:
return f"{woman} kneels in front of {man} at hip height while {man} stands over her for visible ejaculation."
if "standing with cum on the body" in item_text:
return f"{woman} stands braced in front of {man} while he stays close at hip level and ejaculates semen across her body."
if "squatting on top of a partner" in item_text:
return f"{woman} squats over {man}'s hips while {man} lies on his back under her and ejaculates semen onto her body."
if "reverse cowgirl over a partner's hips" in item_text:
return f"{woman} straddles {man}'s hips facing away while {man} lies on his back under her and ejaculates semen onto her body."
if any(term in item_text for term in ("straddling a partner", "straddling a partner's hips", "shared climax after penetration", "orgasm during penetration")):
return f"{woman} straddles {man}'s hips while {man} lies on his back under her, their bodies still aligned from penetration as he ejaculates semen onto her body."
if "seated in a partner's lap facing them" in item_text:
return f"{woman} sits in {man}'s lap facing him, legs wrapped around his hips as he ejaculates semen across her body."
if any(term in item_text for term in ("lower back", "cum dripping from ass", "cum on lower back")) or mentions_ass(item_text):
return f"{woman} is bent forward with hips raised while {man} is positioned behind her, visible semen across her lower back, ass, and thighs."
if any(term in item_text for term in ("cum on face", "cum on tongue", "cum on lips", "cum on face and lips", "cum on tongue and chin")):
if third:
return f"{woman} kneels in the center while {man} and {third} stand close around her face and torso for visible ejaculation."
return f"{woman} kneels in front of {man} at hip height while {man} ejaculates semen onto her face, lips, and chest."
return f"{woman} lies on her back with thighs open while {man} kneels between her legs and ejaculates semen onto her body."
def penetration_position_graph(woman: str, man: str) -> str:
text = " ".join(
str(part or "").lower()
for part in (
item_text,
*((item_axis_values or {}).values()),
)
)
if "missionary" in text:
return (
f"{woman} lies on her back with legs open around {man}'s hips while {man} is above her between her thighs; "
f"{man}'s hips press close and {man}'s penis thrusts into her pussy."
)
if "reverse cowgirl" in text:
return f"{woman} straddles {man}'s hips facing away while {man} lies under her and {man}'s penis thrusts into her pussy."
if "cowgirl" in text or "straddling" in text:
return f"{woman} straddles {man}'s hips facing him while {man} lies under her and {man}'s penis thrusts into her pussy."
if "doggy" in text or "rear-entry" in text or "bent-over" in text or "bent over" in text:
return f"{woman} is on all fours with hips raised while {man} is positioned behind her and {man}'s penis thrusts into her pussy."
if "standing" in text:
return f"{woman} stands braced with hips angled back while {man} stands behind her and {man}'s penis thrusts into her pussy."
if "spooning" in text or "side-lying" in text:
return f"{woman} lies on her side with thighs parted while {man} presses behind her and {man}'s penis thrusts into her pussy."
if "edge-of-bed" in text or "edge of bed" in text or "bed edge" in text or "edge-supported" in text or "raised edge" in text:
return (
f"{woman} lies back at a raised edge with hips at the edge and legs open while {man} kneels between her thighs; "
f"{man}'s hips press close and {man}'s penis thrusts into her pussy."
)
if "kneeling straddle" in text:
return f"{woman} kneels straddling {man}'s hips while {man} supports her waist and {man}'s penis thrusts into her pussy."
if "lotus" in text:
return f"{woman} sits in {man}'s lap facing him with legs around his hips while {man}'s penis thrusts into her pussy."
return (
f"{woman} lies on her back with legs spread wide and knees bent outward while {man} kneels between her open thighs facing her; "
f"{man}'s hips are pressed between her legs and {man}'s penis thrusts into her pussy."
)
def anal_position_graph(woman: str, man: str) -> str:
text = " ".join(
str(part or "").lower()
for part in (
item_text,
*((item_axis_values or {}).values()),
)
)
if "bent-over" in text or "bent over" in text:
return f"{woman} is bent forward with hips raised while {man} stands behind her and thrusts his penis into her ass."
if "face-down" in text:
return f"{woman} lies face-down with ass raised while {man} is positioned behind her and thrusts his penis into her ass."
if "doggy" in text or "rear-entry" in text:
return f"{woman} is on all fours with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
if "standing" in text:
return f"{woman} stands braced with hips angled back while {man} stands behind her and thrusts his penis into her ass."
if "spooning" in text or "side-lying" in text:
return f"{woman} lies on her side with thighs parted while {man} presses behind her and thrusts his penis into her ass."
if "edge-of-bed" in text or "edge of bed" in text or "bed edge" in text:
return f"{woman} lies near a raised edge with hips exposed while {man} kneels behind her and thrusts his penis into her ass."
if "kneeling" in text:
return f"{woman} kneels forward with hips raised while {man} kneels behind her and thrusts his penis into her ass."
return f"{woman} is on all fours with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
if people_count == 1:
solo = people[0]
if women_count == 1:
if "manual_stimulation" in slug:
return manual_position_graph(solo)
if "camera_performance" in slug:
return f"{solo} faces the camera and presents her body with hands framing the exposed skin in a solo creator-shot pose."
if "cumshot" in slug or "climax" in slug:
return f"{solo} is shown in a solo explicit orgasm pose with thighs open, one hand on her body, and visible arousal on skin and sheets."
return f"{solo} is shown in a solo explicit adult pose with self-touch, open body framing, and direct camera awareness."
if "cumshot" in slug or "climax" in slug:
return f"{solo} is shown in a solo visible ejaculation pose with one hand on his penis, body angled toward the camera, and semen visible."
return f"{solo} is shown in a solo explicit adult pose with direct camera awareness and clear body framing."
if women_count > 0 and men_count == 0:
a, b = _pick_distinct(rng, women, 2)
c = any_woman({a, b}) if len(women) >= 3 else ""
used = {a, b}
if "manual_stimulation" in slug:
graph = manual_position_graph(a, b)
elif "group_coordination" in slug and c:
graph = group_coordination_graph(a, b, c)
used.add(c)
elif any(token in slug for token in ("foreplay", "body_worship", "clothing_position", "dominant_guidance", "camera_performance", "aftercare")):
graph = interaction_position_graph(a, b, c)
if c and "camera_performance" in slug:
used.add(c)
elif "foreplay" in slug:
graph = foreplay_position_graph(a, b)
elif "outercourse" in slug:
graph = f"{a} kneels close to {b}'s body and uses mouth, hands, breasts, or feet for explicit non-penetrative contact."
elif "oral" in slug:
graph = f"{a} kneels between {b}'s spread thighs and uses tongue and fingers on her pussy."
elif "anal" in slug or "double" in slug:
graph = f"{a} uses a strap-on on {b} while keeping her hips held open."
elif "threesome" in slug or "group" in slug or "orgy" in slug:
helper = c or any_woman({a})
graph = f"{a} uses a strap-on on {b} while {helper} gives oral contact and touches both bodies."
used.add(helper)
elif "cumshot" in slug or "climax" in slug:
graph = f"{a} brings {b} to orgasm with mouth and fingers while wetness is visible on thighs and sheets."
else:
graph = f"{a} uses a strap-on on {b} while their bodies stay pressed together."
return graph + support_sentence(used)
if men_count > 0 and women_count == 0:
a, b = _pick_distinct(rng, men, 2)
c = any_man({a, b}) if len(men) >= 3 else ""
used = {a, b}
if "manual_stimulation" in slug:
graph = f"{a} and {b} sit or recline close together with hands visibly stimulating bodies in a manual sex setup."
elif "group_coordination" in slug and c:
graph = group_coordination_graph(a, b, c)
used.add(c)
elif any(token in slug for token in ("foreplay", "body_worship", "clothing_position", "dominant_guidance", "camera_performance", "aftercare")):
graph = f"{a} and {b} press close together, kissing and caressing skin while clothing is pulled aside."
elif "foreplay" in slug:
graph = f"{a} and {b} press close together, kissing and caressing skin while clothing is pulled aside."
elif "outercourse" in slug:
graph = f"{a} and {b} keep explicit non-penetrative penis contact visible with hands, mouth, or feet."
elif "oral" in slug:
graph = f"{a} kneels and takes {b}'s penis in his mouth while holding his hips."
elif "anal" in slug or "double" in slug or "penetrative" in slug:
graph = f"{a} penetrates {b} anally while {b}'s hips are held open."
elif "threesome" in slug or "group" in slug or "orgy" in slug:
helper = c or any_man({a})
graph = f"{a} penetrates {b} anally while {helper} gives oral contact from the front."
used.add(helper)
elif "cumshot" in slug or "climax" in slug:
graph = f"{a} ejaculates semen over {b}'s body while {b} keeps eye contact and one hand on his penis."
else:
graph = f"{a} and {b} keep explicit penis and anal contact visible."
return graph + support_sentence(used)
woman = any_woman()
man = any_man()
third = any_person({woman, man}) if people_count >= 3 else ""
if "manual_stimulation" in slug:
graph = manual_position_graph(woman, man)
elif "group_coordination" in slug:
graph = group_coordination_graph(woman, man, third)
elif any(token in slug for token in ("foreplay", "body_worship", "clothing_position", "dominant_guidance", "camera_performance", "aftercare")):
graph = interaction_position_graph(woman, man, third)
elif "foreplay" in slug:
graph = foreplay_position_graph(woman, man)
elif "outercourse" in slug:
graph = build_outercourse_role_graph(woman, man, item_text, item_axis_values, pov_labels)
elif "oral" in slug:
graph = build_oral_role_graph(woman, man, item_text, item_axis_values, pov_labels)
elif "anal" in slug or "double" in slug:
if "double" in item_text or "toy" in item_text:
if people_count >= 3:
graph = f"{man} thrusts his penis into {woman} while {third} adds a second penetration point from the front."
else:
if "bent-over" in item_text or "bent over" in item_text:
graph = f"{woman} is bent forward with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
elif "face-down" in item_text:
graph = f"{woman} lies face-down with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
elif "standing" in item_text:
graph = f"{woman} stands braced with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
elif "kneeling" in item_text:
graph = f"{woman} kneels forward with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
else:
graph = f"{woman} is on all fours with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
elif people_count >= 3:
graph = f"{man} thrusts his penis into {woman} while {third} gives oral contact from the front."
else:
graph = anal_position_graph(woman, man)
elif "threesome" in slug:
graph = f"{man} thrusts his penis into {woman} while {third or any_person({woman, man})} uses mouth and hands on the exposed body."
elif "group" in slug or "orgy" in slug:
graph = f"{man} thrusts his penis into {woman} while surrounding partners give oral contact and keep hands on hips, breasts, and thighs."
elif "cumshot" in slug or "climax" in slug:
graph = climax_position_graph(woman, man, third)
else:
graph = penetration_position_graph(woman, man)
return graph + support_sentence({woman, man, third} if third else {woman, man})