Tune hardcore detail density

This commit is contained in:
2026-06-24 17:42:11 +02:00
parent e2bdff6075
commit fb6d99ac20
7 changed files with 206 additions and 19 deletions
+4
View File
@@ -320,6 +320,10 @@ Options:
neutral with no connected camera config, and uses `SxCP Camera Control` when
one is connected.
- `camera_detail`: `off`, `compact`, or `full` for the pair prompt camera text.
- `hardcore_detail_density`: `compact` keeps the Krea hardcore rewrite mostly
to the position/action sentence, `balanced` keeps one useful non-duplicated
motion or aftermath detail, and `dense` keeps more detail after dedupe. This
is separate from expression intensity.
## Built-In Categories
+5
View File
@@ -41,6 +41,7 @@ try:
character_woman_body_choices,
ethnicity_choices,
generation_profile_choices,
hardcore_detail_density_choices,
load_character_profile_json,
seed_mode_choices,
subcategory_choices,
@@ -85,6 +86,7 @@ except ImportError:
character_woman_body_choices,
ethnicity_choices,
generation_profile_choices,
hardcore_detail_density_choices,
load_character_profile_json,
seed_mode_choices,
subcategory_choices,
@@ -1040,6 +1042,7 @@ class SxCPInstaOFOptions:
"softcore_camera_mode": (camera_mode_choices(), {"default": "handheld_selfie"}),
"hardcore_camera_mode": (["from_camera_config", "same_as_softcore"] + camera_mode_choices(), {"default": "from_camera_config"}),
"camera_detail": (camera_detail_choices(), {"default": "compact"}),
"hardcore_detail_density": (hardcore_detail_density_choices(), {"default": "balanced"}),
}
}
@@ -1066,6 +1069,7 @@ class SxCPInstaOFOptions:
softcore_camera_mode,
hardcore_camera_mode,
camera_detail,
hardcore_detail_density,
):
return (
build_insta_of_options_json(
@@ -1085,6 +1089,7 @@ class SxCPInstaOFOptions:
softcore_camera_mode=softcore_camera_mode,
hardcore_camera_mode=hardcore_camera_mode,
camera_detail=camera_detail,
hardcore_detail_density=hardcore_detail_density,
),
)
+3 -3
View File
@@ -516,15 +516,15 @@
{"text": "full-room group-sex composition with clear adult spacing", "min_people": 4}
],
"climax_compositions": [
{"text": "tight post-ejaculation crop on face, body, hands, and visible fluids", "min_people": 1},
{"text": "direct-flash close-up of orgasm aftermath", "min_people": 1},
{"text": "tight post-ejaculation crop with body position, hands, and visible fluids readable", "min_people": 1},
{"text": "direct-flash close-up of aftermath with the body position still readable", "min_people": 1},
{"text": "bed-level ejaculation frame with thighs, face, and fluid detail visible", "min_people": 1},
{"text": "mirror-reflected post-ejaculation composition", "min_people": 1},
{"text": "front-facing cumshot composition with body and expression centered", "min_people": 1},
{"text": "overhead post-orgasm frame on rumpled sheets", "min_people": 1},
{"text": "kneeling ejaculation frame with open mouth and body visible", "min_people": 1},
{"text": "wide aftermath composition with all adult bodies visible", "min_people": 2},
{"text": "tight subscriber-view crop of orgasm expression and fluid detail", "min_people": 1},
{"text": "tight subscriber-view crop with body position and fluid detail readable", "min_people": 1},
{"text": "side-profile post-ejaculation body-line composition", "min_people": 1}
]
}
+4 -4
View File
@@ -967,10 +967,10 @@
"item_templates": [
"{climax_act} with {fluid_location}, {body_position}, {expression_detail}, and {visibility}",
"{body_position} during {climax_act}, with {hand_detail}, {fluid_location}, and {fluid_detail}",
"{angle} ejaculation view featuring {climax_act}, {body_position}, {body_contact}, {fluid_detail}, and {visibility}",
"{angle} aftermath view with {body_position}, {body_contact}, and {visibility}",
"hardcore post-ejaculation scene with {fluid_location}, {body_position}, {expression_detail}, and {visibility}",
"{climax_act} on {surface}, with {body_position}, {body_contact}, {hand_detail}, and {fluid_detail}",
"{angle} view of {fluid_location}, {body_position}, {climax_act}, and {visibility}",
"{angle} aftermath view of {body_position}, with {fluid_location} and {visibility}",
"explicit orgasm scene: {climax_act}, {body_position}, {fluid_detail}, {expression_detail}, and {body_contact}",
"{body_position} with {fluid_location}, {hand_detail}, {visibility}, and {climax_act}"
],
@@ -1137,8 +1137,8 @@
"post-ejaculation fluids anatomically clear",
"face and body covered in visible cum",
"open thighs and wetness visible",
"explicit semen aftermath visible",
"hardcore ejaculation detail visible",
"aftermath detail visible",
"ejaculation detail visible",
"sexual fluids and body contact visible"
]
}
+2 -2
View File
@@ -210,7 +210,7 @@
"id": 12,
"type": "SxCPInstaOFOptions",
"pos": [-1220, 290],
"size": [360, 366],
"size": [360, 390],
"flags": {},
"order": 11,
"mode": 0,
@@ -219,7 +219,7 @@
{"name": "options_json", "type": "STRING", "links": [10], "slot_index": 0}
],
"properties": {"Node name for S&R": "SxCPInstaOFOptions"},
"widgets_values": ["same_as_hardcore", "couple", 1, 1, "lingerie_tease", "hardcore", true, true, 0.45, 0.85, "hybrid", "same_creator_same_room", "partially_removed", "handheld_selfie", "from_camera_config", "compact"]
"widgets_values": ["same_as_hardcore", "couple", 1, 1, "lingerie_tease", "hardcore", true, true, 0.45, 0.85, "hybrid", "same_creator_same_room", "partially_removed", "handheld_selfie", "from_camera_config", "compact", "balanced"]
},
{
"id": 13,
+163 -10
View File
@@ -9,6 +9,7 @@ TRIGGER_CANDIDATES = (
"sxcpinup_coloredpencil",
"sxcppnl7",
)
HARDCORE_DETAIL_DENSITY_CHOICES = {"compact", "balanced", "dense"}
PROMPT_FIELD_LABELS = (
"Ages",
@@ -52,6 +53,11 @@ def _expression_disabled(row: dict[str, Any]) -> bool:
return bool(row.get("expression_disabled")) or _is_false(row.get("expression_enabled", True))
def _normalize_hardcore_detail_density(value: Any) -> str:
text = _clean(value).lower()
return text if text in HARDCORE_DETAIL_DENSITY_CHOICES else "balanced"
def _sentence(text: str) -> str:
text = _clean(text).strip(" ,;")
if not text:
@@ -747,6 +753,112 @@ def _dedupe_hardcore_detail(detail: str, anchor: str) -> str:
return _clean(detail).strip(" ,;")
def _detail_clauses(detail: str) -> list[str]:
return [part.strip(" ,;") for part in re.split(r",\s*(?:and\s+)?", _clean(detail)) if part.strip(" ,;")]
def _join_detail_clauses(clauses: list[str]) -> str:
cleaned: list[str] = []
seen: set[str] = set()
for clause in clauses:
clause = _clean(clause).strip(" ,;")
key = clause.lower()
if clause and key not in seen:
cleaned.append(clause)
seen.add(key)
return ", ".join(cleaned)
def _action_position_phrase(action: str) -> str:
action = _clean(action).lower()
if "face-down" in action and "ass raised" in action:
return "face-down raised-hip position"
if "on all fours" in action:
return "all-fours raised-hip position"
if "bends forward" in action or "bent forward" in action:
return "bent-over raised-hip position"
if "lies on her back" in action and ("thighs open" in action or "legs open" in action):
return "open-thigh reclined position"
if "lies at the bed edge" in action or "bed edge" in action:
return "bed-edge position"
if "lies on her side" in action:
return "side-lying position"
if "kneels in front" in action:
return "kneeling-at-hip-height position"
if "straddles" in action or "squats over" in action:
return "straddling position"
if "sits in the man's lap" in action:
return "lap-straddling position"
if "stands braced" in action:
return "standing braced position"
if "held between" in action or "front-and-back" in action:
return "front-and-back position"
if "lies between" in action:
return "between-partners position"
return ""
def _normalize_climax_view_clause(clause: str, role_graph: str) -> str:
lower = clause.lower()
if "view" not in lower and "frame" not in lower:
return clause
angle_match = re.search(
r"\b(front-facing|close-up|wide full-body|wide|overhead|mirror-reflected|low-angle|side-profile|bed-level)\b",
lower,
)
if not angle_match:
return clause
angle = angle_match.group(1)
if angle == "wide":
angle = "wide full-body"
position = _action_position_phrase(role_graph)
if position:
return f"{angle} aftermath view with the {position} readable"
return f"{angle} aftermath view"
def _climax_clause_duplicates_role(clause: str, role_graph: str) -> bool:
clause_lower = clause.lower()
role_lower = role_graph.lower()
role_has_ejaculation = any(token in role_lower for token in ("ejaculates semen", "visible semen", "semen lands"))
if role_has_ejaculation and re.search(
r"\b(?:cum clearly visible|explicit semen aftermath visible|hardcore ejaculation detail visible|"
r"post-ejaculation fluids anatomically clear|sexual fluids and body contact visible|"
r"visible external ejaculation|hardcore ejaculation scene|visible orgasm aftermath)\b",
clause_lower,
):
return True
duplicate_pairs = (
(("lower back", "ass"), ("lower back", "ass")),
(("ass",), ("ass",)),
(("pussy", "thigh"), ("pussy", "thigh")),
(("face", "lips"), ("face", "lips")),
(("tongue", "chin"), ("face", "lips", "mouth", "tongue")),
(("breast",), ("breast", "chest")),
(("belly",), ("belly", "torso")),
(("body",), ("body",)),
)
if any(token in clause_lower for token in ("cum", "semen", "fluid")):
for clause_tokens, role_tokens in duplicate_pairs:
if any(token in clause_lower for token in clause_tokens) and any(token in role_lower for token in role_tokens):
return True
return False
def _limit_detail_for_density(detail: str, density: str, is_climax: bool) -> str:
density = _normalize_hardcore_detail_density(density)
if density == "compact":
return ""
clauses = _detail_clauses(detail)
if not clauses:
return ""
if density == "balanced":
limit = 1 if is_climax else 2
else:
limit = 3 if is_climax else 4
return _join_detail_clauses(clauses[:limit])
def _is_climax_text(*parts: str) -> bool:
text = " ".join(_clean(part).lower() for part in parts if _clean(part))
return any(
@@ -790,7 +902,7 @@ def _climax_role_graph(role_graph: str, hard_item: str, axis_values: Any = None)
return "the woman lies on her back with thighs open while the man kneels between her legs and ejaculates semen across her pussy and thighs"
if "on all fours with hips raised" in text:
return "the woman is on all fours with hips raised while the man is positioned behind her and ejaculates semen across her ass, thighs, and lower back"
if "face-down ass-up" in text:
if "face-down ass-up" in text or "lies face-down" in text or "face down" in text:
return "the woman lies face-down with ass raised while the man is positioned behind her and ejaculates semen across her lower back and ass"
if "bent over with ass raised" in text or "bent over" in text:
return "the woman bends forward with hips raised while the man stands behind her with visible semen across her lower back, ass, and thighs"
@@ -823,7 +935,7 @@ def _climax_role_graph(role_graph: str, hard_item: str, axis_values: Any = None)
return "the woman lies on her back with thighs open while the man kneels between her legs and ejaculates semen across her body"
def _dedupe_climax_detail(detail: str, role_graph: str) -> str:
def _dedupe_climax_detail(detail: str, role_graph: str, density: str = "balanced") -> str:
detail = _clean(detail)
lower = role_graph.lower()
patterns: list[str] = []
@@ -870,10 +982,25 @@ def _dedupe_climax_detail(detail: str, role_graph: str) -> str:
detail = re.sub(r"\bwith\s*,\s*", "with ", detail, flags=re.IGNORECASE)
detail = re.sub(r"^with\s+", "", detail, flags=re.IGNORECASE)
detail = re.sub(r"^and\s+", "", detail, flags=re.IGNORECASE)
return _clean(detail).strip(" ,;")
clauses: list[str] = []
for clause in _detail_clauses(detail):
normalized = _normalize_climax_view_clause(clause, role_graph)
if _climax_clause_duplicates_role(normalized, role_graph):
continue
if density != "dense" and normalized.lower() in ("orgasm during penetration", "post-orgasm visible release"):
continue
clauses.append(normalized)
return _limit_detail_for_density(_join_detail_clauses(clauses), density, True)
def _hardcore_action_sentence(role_graph: str, hard_item: str, composition: str = "", axis_values: Any = None) -> str:
def _hardcore_action_sentence(
role_graph: str,
hard_item: str,
composition: str = "",
axis_values: Any = None,
detail_density: str = "balanced",
) -> str:
detail_density = _normalize_hardcore_detail_density(detail_density)
role_graph = _clean(role_graph).rstrip(".")
hard_item = _clean(hard_item).rstrip(".")
role_graph = re.sub(
@@ -938,9 +1065,10 @@ def _hardcore_action_sentence(role_graph: str, hard_item: str, composition: str
)
if is_climax:
anchor = ""
detail = _dedupe_climax_detail(detail, role_graph)
detail = _dedupe_climax_detail(detail, role_graph, detail_density)
else:
detail = _dedupe_hardcore_detail(detail, anchor) if anchor else 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 ""
if arrangement and anchor_phrase:
@@ -960,12 +1088,18 @@ def _hardcore_action_sentence(role_graph: str, hard_item: str, composition: str
return sentence
def _composition_phrase(composition: Any, action: str = "", prefix: str = "framed as") -> str:
def _composition_phrase(
composition: Any,
action: str = "",
prefix: str = "framed as",
detail_density: str = "balanced",
) -> str:
composition = _clean(composition)
if not composition:
return ""
action_lower = _clean(action).lower()
composition_lower = composition.lower()
detail_density = _normalize_hardcore_detail_density(detail_density)
oral_pose_tokens = (
"kneeling oral",
"side-lying oral",
@@ -983,6 +1117,15 @@ def _composition_phrase(composition: Any, action: str = "", prefix: str = "frame
if composition_oral_tokens and not any(token in action_lower for token in composition_oral_tokens):
match = re.search(r"\bwith\s+(.+)$", composition, flags=re.IGNORECASE)
return f"framed with {match.group(1)}" if match else ""
position = _action_position_phrase(action)
close_or_aftermath = any(
token in composition_lower
for token in ("close-up", "close crop", "tight", "direct-flash", "subscriber-view", "post-ejaculation", "aftermath")
)
if position and close_or_aftermath:
if detail_density == "compact":
return f"{prefix} {composition}, with the {position} still readable"
return f"{prefix} {composition}, keeping the {position} and action geography readable"
return f"{prefix} {composition}"
@@ -1140,7 +1283,8 @@ def _normal_row_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
role_graph = _natural_label_text(role_graph, cast_labels)
item = _natural_label_text(item, cast_labels)
axis_values = row.get("item_axis_values") if isinstance(row.get("item_axis_values"), dict) else {}
action = _hardcore_action_sentence(role_graph, item, composition, axis_values)
detail_density = _normalize_hardcore_detail_density(row.get("hardcore_detail_density"))
action = _hardcore_action_sentence(role_graph, item, composition, axis_values, detail_density)
parts = [
action,
cast_prose,
@@ -1148,7 +1292,7 @@ def _normal_row_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
f"The cast includes {cast}" if cast and not cast_prose and not (women_count == 1 and men_count == 1) else "",
f"The setting is {scene}" if scene else "",
_expression_phrase(expression),
_composition_phrase(composition, action, "The image is framed as"),
_composition_phrase(composition, action, "The image is framed as", detail_density),
camera,
style if detail_level != "concise" else "",
]
@@ -1235,7 +1379,16 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
hard_item = _natural_label_text(hard_item, hard_labels)
hard_role_graph = _natural_label_text(hard_role_graph, hard_labels)
hard_axis_values = hard.get("item_axis_values") if isinstance(hard.get("item_axis_values"), dict) else {}
hard_action = _hardcore_action_sentence(hard_role_graph, hard_item, hard_composition, hard_axis_values)
hard_detail_density = _normalize_hardcore_detail_density(
hard.get("hardcore_detail_density") or row.get("hardcore_detail_density") or options.get("hardcore_detail_density")
)
hard_action = _hardcore_action_sentence(
hard_role_graph,
hard_item,
hard_composition,
hard_axis_values,
hard_detail_density,
)
same_soft_cast = options.get("softcore_cast") == "same_as_hardcore"
soft_cast_presence = (
f"{_label_join(soft_labels)} are together in a non-explicit teaser pose, with no sex act or genital contact"
@@ -1284,7 +1437,7 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
hard_cast_prose,
f"set in {hard_scene}" if hard_scene else "",
_expression_phrase(hard_expression),
_composition_phrase(hard_composition, hard_action),
_composition_phrase(hard_composition, hard_action, detail_density=hard_detail_density),
hard_camera,
hard_style if detail_level != "concise" else "",
]
+25
View File
@@ -178,6 +178,7 @@ CHARACTER_DESCRIPTOR_DETAIL_CHOICES = ["auto", "full", "medium", "compact", "min
CHARACTER_RANDOM_TOKENS = {"", "random", "auto", "global", "from_global", "default"}
CAMERA_DETAIL_CHOICES = ["off", "compact", "full"]
HARDCORE_DETAIL_DENSITY_CHOICES = ["compact", "balanced", "dense"]
GENERIC_POSITIVE_SUFFIX = (
"Use crisp clean comic linework, detailed hatching, soft blended shading, "
@@ -1440,6 +1441,10 @@ def camera_detail_choices() -> list[str]:
return list(CAMERA_DETAIL_CHOICES)
def hardcore_detail_density_choices() -> list[str]:
return list(HARDCORE_DETAIL_DENSITY_CHOICES)
def camera_shot_choices() -> list[str]:
return list(CAMERA_SHOT_PROMPTS)
@@ -3785,7 +3790,11 @@ def build_insta_of_options_json(
hardcore_expression_intensity: float = 0.85,
softcore_expression_enabled: bool = True,
hardcore_expression_enabled: bool = True,
hardcore_detail_density: str = "balanced",
) -> str:
hardcore_detail_density = (
hardcore_detail_density if hardcore_detail_density in HARDCORE_DETAIL_DENSITY_CHOICES else "balanced"
)
return json.dumps(
{
"softcore_cast": softcore_cast,
@@ -3804,6 +3813,7 @@ def build_insta_of_options_json(
"hardcore_expression_enabled": not _is_false(hardcore_expression_enabled),
"softcore_expression_intensity": _clamped_float(softcore_expression_intensity, 0.45),
"hardcore_expression_intensity": _clamped_float(hardcore_expression_intensity, 0.85),
"hardcore_detail_density": hardcore_detail_density,
},
ensure_ascii=True,
sort_keys=True,
@@ -3828,6 +3838,7 @@ def _parse_insta_of_options(options_json: str | dict[str, Any] | None) -> dict[s
"hardcore_expression_enabled": True,
"softcore_expression_intensity": 0.45,
"hardcore_expression_intensity": 0.85,
"hardcore_detail_density": "balanced",
}
if not options_json:
return defaults
@@ -3869,6 +3880,11 @@ def _parse_insta_of_options(options_json: str | dict[str, Any] | None) -> dict[s
parsed.get("hardcore_expression_intensity"),
defaults["hardcore_expression_intensity"],
)
parsed["hardcore_detail_density"] = (
parsed["hardcore_detail_density"]
if parsed.get("hardcore_detail_density") in HARDCORE_DETAIL_DENSITY_CHOICES
else defaults["hardcore_detail_density"]
)
for key in ("hardcore_women_count", "hardcore_men_count"):
try:
parsed[key] = max(0, min(12, int(parsed[key])))
@@ -4153,6 +4169,7 @@ def build_insta_of_pair(
expression_intensity=options["hardcore_expression_intensity"],
character_cast=character_cast or "",
)
hard_row["hardcore_detail_density"] = options["hardcore_detail_density"]
descriptor = _insta_of_descriptor(soft_row)
cast_descriptors = _insta_of_cast_descriptors(
@@ -4220,6 +4237,12 @@ def build_insta_of_pair(
options["hardcore_clothing_continuity"],
soft_row["item"],
)
hard_detail_density = options["hardcore_detail_density"]
hard_detail_directive = {
"compact": "Use one compact position-first sexual action sentence; avoid repeated aftermath wording. ",
"balanced": "",
"dense": "Use dense but coherent motion, contact, and aftermath detail while keeping one readable body position. ",
}[hard_detail_density]
soft_descriptor_sentence = (
f"Cast descriptors: {soft_cast_descriptor_text}. "
if options["softcore_cast"] == "same_as_hardcore"
@@ -4249,6 +4272,7 @@ def build_insta_of_pair(
f"Setting: {hard_scene}. "
f"{_labeled_expression_sentence('Facial expressions', hard_row.get('expression'))}"
f"Composition: {hard_composition}. "
f"{hard_detail_directive}"
f"{hard_camera_sentence}"
f"{hard_row['positive_suffix']}."
)
@@ -4294,6 +4318,7 @@ def build_insta_of_pair(
"shared_cast_descriptors": cast_descriptors,
"softcore_partner_styling": soft_partner_styling,
"hardcore_clothing_state": hard_clothing_state,
"hardcore_detail_density": hard_detail_density,
"softcore_prompt": soft_prompt,
"hardcore_prompt": hard_prompt,
"softcore_negative_prompt": soft_negative,