Improve oral hardcore pose variation
This commit is contained in:
+142
-26
@@ -6,7 +6,7 @@ import random
|
||||
import re
|
||||
from pathlib import Path
|
||||
from string import Formatter
|
||||
from typing import Any
|
||||
from typing import Any, Callable
|
||||
|
||||
try:
|
||||
from . import generate_prompt_batches as g
|
||||
@@ -327,7 +327,10 @@ HARDCORE_POSITION_KEY_CHOICES = [
|
||||
"lotus_lap",
|
||||
"face_sitting",
|
||||
"sixty_nine",
|
||||
"reclining_oral",
|
||||
"straddled_oral",
|
||||
"spread_leg_oral",
|
||||
"chair_oral",
|
||||
"open_thighs",
|
||||
"front_back",
|
||||
]
|
||||
@@ -361,7 +364,10 @@ HARDCORE_POSITION_KEY_MATCHES = {
|
||||
"lotus_lap": ("lotus", "lap", "seated in a partner's lap"),
|
||||
"face_sitting": ("face-sitting", "face sitting"),
|
||||
"sixty_nine": ("sixty-nine", "69"),
|
||||
"reclining_oral": ("reclining cunnilingus",),
|
||||
"straddled_oral": ("straddled oral",),
|
||||
"spread_leg_oral": ("spread-leg", "spread leg", "reclining cunnilingus"),
|
||||
"chair_oral": ("chair oral",),
|
||||
"open_thighs": ("thighs open", "legs spread", "open thighs", "legs open", "reclining with thighs open"),
|
||||
"front_back": ("front-and-back", "front and back", "one behind and one in front", "between two partners"),
|
||||
}
|
||||
@@ -863,7 +869,7 @@ HARDCORE_ENVIRONMENT_ANCHOR_REPLACEMENTS = (
|
||||
(r"\bsitting on the edge of the bed\b", "sitting on a raised edge"),
|
||||
(r"\blying at the bed edge with thighs open\b", "lying near a raised edge with thighs open"),
|
||||
(r"\bedge[- ]of[- ]bed\b", "edge-supported"),
|
||||
(r"\bbed[- ]edge\b", "edge-supported"),
|
||||
(r"\bbed[- ]edge\b", "raised edge"),
|
||||
(r"\bedge of (?:the )?bed\b", "raised edge"),
|
||||
(r"\bbed edge\b", "raised edge"),
|
||||
(r"\bhands? braced on the bed\b", "hands braced beside the body"),
|
||||
@@ -960,6 +966,33 @@ def _merged_axes(category: dict[str, Any], subcategory: dict[str, Any], item: An
|
||||
return axes
|
||||
|
||||
|
||||
def _oral_acts_for_position(values: list[Any], position: str) -> list[Any]:
|
||||
position_text = str(position or "").lower()
|
||||
if not position_text:
|
||||
return values
|
||||
|
||||
def act_text(value: Any) -> str:
|
||||
return _entry_text(value).lower()
|
||||
|
||||
def filtered(predicate: Callable[[str], bool]) -> list[Any]:
|
||||
matches = [value for value in values if predicate(act_text(value))]
|
||||
return matches or values
|
||||
|
||||
penis_terms = ("fellatio", "blowjob", "deepthroat", "penis sucking", "penis in mouth")
|
||||
cunnilingus_terms = ("cunnilingus", "pussy licking", "tongue on pussy", "oral sex with tongue and fingers", "mouth on genitals")
|
||||
if "sixty-nine" in position_text:
|
||||
return filtered(lambda text: "sixty-nine" in text)
|
||||
if "face-sitting" in position_text:
|
||||
return filtered(lambda text: "face-sitting" in text or any(term in text for term in cunnilingus_terms))
|
||||
if "straddled oral" in position_text or "reclining cunnilingus" in position_text:
|
||||
return filtered(lambda text: "sixty-nine" not in text and not any(term in text for term in penis_terms))
|
||||
if "spread-leg oral" in position_text:
|
||||
return filtered(lambda text: "sixty-nine" not in text and "face-sitting" not in text)
|
||||
if any(term in position_text for term in ("standing oral", "kneeling oral", "edge-of-bed oral", "chair oral", "side-lying oral")):
|
||||
return filtered(lambda text: "sixty-nine" not in text and "face-sitting" not in text)
|
||||
return values
|
||||
|
||||
|
||||
def _compose_item(
|
||||
rng: random.Random,
|
||||
category: dict[str, Any],
|
||||
@@ -972,12 +1005,19 @@ def _compose_item(
|
||||
axes = _merged_axes(category, subcategory, item)
|
||||
if templates and axes:
|
||||
template = _entry_text(_weighted_choice(rng, _compatible_entries(templates, women_count, men_count)))
|
||||
fields = {key for _, key, _, _ in Formatter().parse(template) if key}
|
||||
axis_values = {
|
||||
name: _entry_text(_weighted_choice(rng, _compatible_entries(axes[name], women_count, men_count)))
|
||||
for name in fields
|
||||
if name in axes and axes[name]
|
||||
}
|
||||
fields = [key for _, key, _, _ in Formatter().parse(template) if key]
|
||||
unique_fields = list(dict.fromkeys(fields))
|
||||
axis_values: dict[str, str] = {}
|
||||
if str(subcategory.get("slug") or "").lower() == "oral_sex" and "position" in unique_fields and axes.get("position"):
|
||||
position_values = _compatible_entries(axes["position"], women_count, men_count)
|
||||
axis_values["position"] = _entry_text(_weighted_choice(rng, position_values))
|
||||
for name in unique_fields:
|
||||
if name in axis_values or name not in axes or not axes[name]:
|
||||
continue
|
||||
values = _compatible_entries(axes[name], women_count, men_count)
|
||||
if str(subcategory.get("slug") or "").lower() == "oral_sex" and name == "oral_act":
|
||||
values = _oral_acts_for_position(values, axis_values.get("position", ""))
|
||||
axis_values[name] = _entry_text(_weighted_choice(rng, values))
|
||||
item_text = _format(template, axis_values).strip()
|
||||
item_name = _item_name(item) or subcategory["name"]
|
||||
return item_text, item_name, axis_values
|
||||
@@ -1736,6 +1776,12 @@ def _hardcore_position_config_active(config: dict[str, Any]) -> bool:
|
||||
return bool(config.get("enabled"))
|
||||
|
||||
|
||||
def _hardcore_position_template_required(config: dict[str, Any]) -> bool:
|
||||
if not _hardcore_position_config_active(config):
|
||||
return False
|
||||
return bool(config.get("positions")) or _normalize_hardcore_position_family(config.get("family")) != "any"
|
||||
|
||||
|
||||
def _is_hardcore_sexual_category(category: dict[str, Any]) -> bool:
|
||||
return str(category.get("slug") or "").strip() == "hardcore_sexual_poses" or str(category.get("name") or "").strip().lower() == "hardcore sexual poses"
|
||||
|
||||
@@ -1841,7 +1887,7 @@ def _hardcore_position_entry_conflicts(entry: Any, config: dict[str, Any]) -> bo
|
||||
|
||||
|
||||
def _hardcore_subcategory_supports_positions(subcategory: dict[str, Any], config: dict[str, Any]) -> bool:
|
||||
if not config.get("positions"):
|
||||
if not _hardcore_position_template_required(config):
|
||||
return True
|
||||
axes = subcategory.get("item_axes")
|
||||
if not isinstance(axes, dict):
|
||||
@@ -1875,7 +1921,7 @@ def _filter_hardcore_templates(templates: list[Any], config: dict[str, Any]) ->
|
||||
for template in templates:
|
||||
text = _entry_text(template)
|
||||
fields = {key for _, key, _, _ in Formatter().parse(text) if key}
|
||||
blocked = bool(config.get("positions")) and not bool(fields & HARDCORE_POSITION_AXIS_KEYS)
|
||||
blocked = _hardcore_position_template_required(config) and not bool(fields & HARDCORE_POSITION_AXIS_KEYS)
|
||||
blocked = blocked or any(_hardcore_text_blocked_by_action(text, field, config) for field in fields | {""})
|
||||
if not blocked:
|
||||
filtered.append(template)
|
||||
@@ -4881,6 +4927,91 @@ def _role_graph(
|
||||
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."
|
||||
|
||||
def oral_position_graph(woman: str, man: str) -> str:
|
||||
position_text = str((item_axis_values or {}).get("position") or "").lower()
|
||||
text = " ".join(
|
||||
str(part or "").lower()
|
||||
for part in (
|
||||
item_text,
|
||||
*((item_axis_values or {}).values()),
|
||||
)
|
||||
)
|
||||
woman_gives = any(
|
||||
term in text
|
||||
for term in (
|
||||
"fellatio",
|
||||
"blowjob",
|
||||
"deepthroat",
|
||||
"penis sucking",
|
||||
"penis in mouth",
|
||||
"penis in her mouth",
|
||||
"mouth stretched around a penis",
|
||||
"lips wrapped",
|
||||
)
|
||||
)
|
||||
man_gives = any(
|
||||
term in text
|
||||
for term in (
|
||||
"cunnilingus",
|
||||
"pussy licking",
|
||||
"tongue on pussy",
|
||||
"mouth on pussy",
|
||||
"pussy and tongue",
|
||||
"face-sitting",
|
||||
"tongue contact clearly visible",
|
||||
)
|
||||
)
|
||||
if "mouth on genitals" in text and not woman_gives and not man_gives:
|
||||
if any(term in text for term in ("face-sitting", "reclining", "straddled", "spread-leg", "open thighs")):
|
||||
man_gives = True
|
||||
else:
|
||||
woman_gives = True
|
||||
|
||||
if "sixty-nine" in position_text or ("sixty-nine" in text and not position_text):
|
||||
return f"{woman} and {man} lie head-to-hips in a sixty-nine position, with {woman}'s mouth on {man}'s penis and {man}'s mouth on {woman}'s pussy."
|
||||
if "face-sitting" in position_text or ("face-sitting" in text and not position_text):
|
||||
return f"{man} lies on his back while {woman} straddles his face with her thighs around his head and {man}'s mouth pressed to her pussy."
|
||||
if "straddled oral" in position_text or ("straddled oral" in text and not position_text):
|
||||
if woman_gives and not man_gives:
|
||||
return f"{man} straddles forward near {woman}'s face while {woman} kneels below him with her mouth on his penis."
|
||||
return f"{woman} straddles above {man}'s face with her thighs framing his head while {man}'s mouth stays pressed to her pussy."
|
||||
if "side-lying oral" in position_text or ("side-lying oral" in text and not position_text):
|
||||
if woman_gives and not man_gives:
|
||||
return f"{man} lies on his side with hips angled toward {woman} while {woman} lies beside his thighs and takes his penis in her mouth."
|
||||
return f"{woman} lies on her side with her top thigh lifted while {man} lies beside her hips with his mouth pressed to her pussy."
|
||||
if (
|
||||
"edge-of-bed oral" in position_text
|
||||
or "edge of bed oral" in position_text
|
||||
or "edge-supported oral" in position_text
|
||||
or (("edge-of-bed oral" in text or "edge of bed oral" in text or "edge-supported oral" in text) and not position_text)
|
||||
):
|
||||
if woman_gives and not man_gives:
|
||||
return f"{man} sits at a raised edge with legs apart while {woman} kneels between his thighs and takes his penis in her mouth."
|
||||
return f"{woman} lies at a raised edge with thighs open while {man} kneels between her legs with his mouth on her pussy."
|
||||
if "standing oral" in position_text or ("standing oral" in text and not position_text):
|
||||
if man_gives and not woman_gives:
|
||||
return f"{woman} stands braced with one thigh lifted while {man} kneels between her legs with his mouth on her pussy."
|
||||
return f"{man} stands with hips forward while {woman} kneels in front of him at hip height and takes his penis in her mouth."
|
||||
if "chair oral" in position_text or ("chair oral" in text and not position_text):
|
||||
if man_gives and not woman_gives:
|
||||
return f"{woman} sits in a chair with thighs open while {man} kneels between her legs with his mouth pressed to her pussy."
|
||||
return f"{man} sits in a chair with legs apart while {woman} kneels between his thighs and takes his penis in her mouth."
|
||||
if (
|
||||
"reclining cunnilingus" in position_text
|
||||
or "spread-leg oral" in position_text
|
||||
or (("reclining cunnilingus" in text or "spread-leg oral" in text) and not position_text)
|
||||
):
|
||||
if woman_gives and not man_gives:
|
||||
return f"{man} reclines with legs apart while {woman} kneels between his thighs and takes his penis in her mouth."
|
||||
return f"{woman} reclines on her back with thighs spread while {man} kneels between her legs with his mouth on her pussy."
|
||||
if "kneeling oral" in position_text or ("kneeling oral" in text and not position_text):
|
||||
if man_gives and not woman_gives:
|
||||
return f"{woman} kneels with thighs parted and hips angled forward while {man} kneels in front of her with his mouth on her pussy."
|
||||
return f"{woman} kneels in front of {man}'s hips with her mouth at penis level while {man} stands or sits close above her."
|
||||
if man_gives and not woman_gives:
|
||||
return f"{woman} lies on her back with thighs open while {man} kneels between her legs with his mouth pressed to her pussy."
|
||||
return f"{woman} kneels in front of {man}'s hips and takes his penis in her mouth while {man} keeps his hips aligned with her face."
|
||||
|
||||
if people_count == 1:
|
||||
solo = people[0]
|
||||
if women_count == 1:
|
||||
@@ -4932,22 +5063,7 @@ def _role_graph(
|
||||
man = any_man()
|
||||
third = any_person({woman, man}) if people_count >= 3 else ""
|
||||
if "oral" in slug:
|
||||
if "sixty-nine" in item_text or ("blowjob" in item_text and ("cunnilingus" in item_text or "pussy" in item_text)):
|
||||
graph = f"{woman} has {man}'s penis in her mouth while {man} uses his mouth on {woman}'s pussy, with both mouths pressed to genitals."
|
||||
elif any(
|
||||
term in item_text
|
||||
for term in (
|
||||
"cunnilingus",
|
||||
"pussy licking",
|
||||
"tongue on pussy",
|
||||
"mouth on pussy",
|
||||
"pussy and tongue",
|
||||
"tongue contact",
|
||||
)
|
||||
) or ("pussy" in item_text and "penis" not in item_text):
|
||||
graph = f"{man} gives oral to {woman}, mouth on her pussy while {woman}'s thighs are held open for the camera."
|
||||
else:
|
||||
graph = f"{woman} takes {man}'s penis in her mouth while {man} holds her hair and hips."
|
||||
graph = oral_position_graph(woman, man)
|
||||
elif "anal" in slug or "double" in slug:
|
||||
if "double" in item_text or "toy" in item_text:
|
||||
if people_count >= 3:
|
||||
|
||||
Reference in New Issue
Block a user