Add POV participant mode
This commit is contained in:
@@ -98,6 +98,13 @@ is emitted into named-cast descriptors:
|
|||||||
couple/group prompts without turning every partner into a fully detailed primary
|
couple/group prompts without turning every partner into a fully detailed primary
|
||||||
character. Set a man slot to `full` when the partner needs exact hair/eye detail.
|
character. Set a man slot to `full` when the partner needs exact hair/eye detail.
|
||||||
|
|
||||||
|
`SxCP Man Slot` also has `presence_mode`. Use `visible` for a normal rendered
|
||||||
|
partner. Use `pov` when that man is the first-person camera participant: he
|
||||||
|
remains part of the cast and role graph, but his appearance descriptor and
|
||||||
|
per-character expression text are omitted, and pose wording is rendered from
|
||||||
|
the POV participant's viewpoint. The generic `SxCP Character Slot` exposes the
|
||||||
|
same field, but it only has an effect when `subject_type=man`.
|
||||||
|
|
||||||
Slots also expose `expression_enabled` and `expression_intensity`. Disable
|
Slots also expose `expression_enabled` and `expression_intensity`. Disable
|
||||||
`expression_enabled` when that character should not receive a face/expression
|
`expression_enabled` when that character should not receive a face/expression
|
||||||
directive. Leave `expression_intensity` at `-1` to use the generator or Insta/OF
|
directive. Leave `expression_intensity` at `-1` to use the generator or Insta/OF
|
||||||
@@ -231,6 +238,9 @@ Important behavior:
|
|||||||
- Insta/OF cast metadata is rewritten as direct named-character prose such as
|
- Insta/OF cast metadata is rewritten as direct named-character prose such as
|
||||||
`Woman A is ...` and `Man A is ...`, so Krea2 does not have to interpret a
|
`Woman A is ...` and `Man A is ...`, so Krea2 does not have to interpret a
|
||||||
`Cast descriptors:` label.
|
`Cast descriptors:` label.
|
||||||
|
- Man slots set to `presence_mode=pov` are not emitted as visible cast prose.
|
||||||
|
The formatter keeps them in the role graph, rewrites the action from the
|
||||||
|
first-person viewer position, and adds a POV camera sentence.
|
||||||
|
|
||||||
It outputs:
|
It outputs:
|
||||||
|
|
||||||
@@ -261,6 +271,11 @@ the shared primary creator (`Woman A`) in both softcore and hardcore outputs;
|
|||||||
additional woman/man slots fill partner descriptors before random fallback
|
additional woman/man slots fill partner descriptors before random fallback
|
||||||
descriptors are used.
|
descriptors are used.
|
||||||
|
|
||||||
|
For POV pair prompts, set the relevant `SxCP Man Slot` to
|
||||||
|
`presence_mode=pov`. The softcore output frames the primary creator from that
|
||||||
|
participant's camera, while the hardcore output keeps the same cast and scene
|
||||||
|
but converts the role graph into first-person positioning.
|
||||||
|
|
||||||
It outputs:
|
It outputs:
|
||||||
|
|
||||||
- `softcore_prompt`
|
- `softcore_prompt`
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ try:
|
|||||||
character_figure_choices,
|
character_figure_choices,
|
||||||
character_label_choices,
|
character_label_choices,
|
||||||
character_man_body_choices,
|
character_man_body_choices,
|
||||||
|
character_presence_choices,
|
||||||
character_profile_choices,
|
character_profile_choices,
|
||||||
character_woman_body_choices,
|
character_woman_body_choices,
|
||||||
ethnicity_choices,
|
ethnicity_choices,
|
||||||
@@ -82,6 +83,7 @@ except ImportError:
|
|||||||
character_figure_choices,
|
character_figure_choices,
|
||||||
character_label_choices,
|
character_label_choices,
|
||||||
character_man_body_choices,
|
character_man_body_choices,
|
||||||
|
character_presence_choices,
|
||||||
character_profile_choices,
|
character_profile_choices,
|
||||||
character_woman_body_choices,
|
character_woman_body_choices,
|
||||||
ethnicity_choices,
|
ethnicity_choices,
|
||||||
@@ -611,6 +613,7 @@ class SxCPCharacterSlot:
|
|||||||
"descriptor_detail": (character_descriptor_detail_choices(), {"default": "auto"}),
|
"descriptor_detail": (character_descriptor_detail_choices(), {"default": "auto"}),
|
||||||
"expression_enabled": ("BOOLEAN", {"default": True}),
|
"expression_enabled": ("BOOLEAN", {"default": True}),
|
||||||
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
||||||
|
"presence_mode": (character_presence_choices(), {"default": "visible"}),
|
||||||
},
|
},
|
||||||
"optional": {
|
"optional": {
|
||||||
"character_cast": ("STRING", {"default": "", "multiline": True}),
|
"character_cast": ("STRING", {"default": "", "multiline": True}),
|
||||||
@@ -640,6 +643,7 @@ class SxCPCharacterSlot:
|
|||||||
descriptor_detail="auto",
|
descriptor_detail="auto",
|
||||||
expression_enabled=True,
|
expression_enabled=True,
|
||||||
expression_intensity=-1.0,
|
expression_intensity=-1.0,
|
||||||
|
presence_mode="visible",
|
||||||
character_cast="",
|
character_cast="",
|
||||||
):
|
):
|
||||||
result = build_character_slot_json(
|
result = build_character_slot_json(
|
||||||
@@ -658,6 +662,7 @@ class SxCPCharacterSlot:
|
|||||||
descriptor_detail=descriptor_detail,
|
descriptor_detail=descriptor_detail,
|
||||||
expression_enabled=expression_enabled,
|
expression_enabled=expression_enabled,
|
||||||
expression_intensity=expression_intensity,
|
expression_intensity=expression_intensity,
|
||||||
|
presence_mode=presence_mode,
|
||||||
enabled=enabled,
|
enabled=enabled,
|
||||||
character_cast=character_cast or "",
|
character_cast=character_cast or "",
|
||||||
)
|
)
|
||||||
@@ -755,6 +760,7 @@ class SxCPManSlot:
|
|||||||
"descriptor_detail": (character_descriptor_detail_choices(), {"default": "compact"}),
|
"descriptor_detail": (character_descriptor_detail_choices(), {"default": "compact"}),
|
||||||
"expression_enabled": ("BOOLEAN", {"default": True}),
|
"expression_enabled": ("BOOLEAN", {"default": True}),
|
||||||
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
|
||||||
|
"presence_mode": (character_presence_choices(), {"default": "visible"}),
|
||||||
},
|
},
|
||||||
"optional": {
|
"optional": {
|
||||||
"character_cast": ("STRING", {"default": "", "multiline": True}),
|
"character_cast": ("STRING", {"default": "", "multiline": True}),
|
||||||
@@ -782,6 +788,7 @@ class SxCPManSlot:
|
|||||||
descriptor_detail="compact",
|
descriptor_detail="compact",
|
||||||
expression_enabled=True,
|
expression_enabled=True,
|
||||||
expression_intensity=-1.0,
|
expression_intensity=-1.0,
|
||||||
|
presence_mode="visible",
|
||||||
character_cast="",
|
character_cast="",
|
||||||
):
|
):
|
||||||
result = build_character_slot_json(
|
result = build_character_slot_json(
|
||||||
@@ -800,6 +807,7 @@ class SxCPManSlot:
|
|||||||
descriptor_detail=descriptor_detail,
|
descriptor_detail=descriptor_detail,
|
||||||
expression_enabled=expression_enabled,
|
expression_enabled=expression_enabled,
|
||||||
expression_intensity=expression_intensity,
|
expression_intensity=expression_intensity,
|
||||||
|
presence_mode=presence_mode,
|
||||||
enabled=enabled,
|
enabled=enabled,
|
||||||
character_cast=character_cast or "",
|
character_cast=character_cast or "",
|
||||||
)
|
)
|
||||||
|
|||||||
+182
-20
@@ -230,8 +230,16 @@ def _natural_label_text(text: Any, labels: list[str]) -> str:
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
def _cast_prose(text: str, central_label: str = "Woman A") -> tuple[str, list[str]]:
|
def _cast_prose(
|
||||||
entries = _cast_entries(text)
|
text: str,
|
||||||
|
central_label: str = "Woman A",
|
||||||
|
omit_labels: list[str] | set[str] | tuple[str, ...] = (),
|
||||||
|
) -> tuple[str, list[str]]:
|
||||||
|
raw_entries = _cast_entries(text)
|
||||||
|
omitted = set(omit_labels or [])
|
||||||
|
entries = [(label, descriptor) for label, descriptor in raw_entries if label not in omitted]
|
||||||
|
if raw_entries and not entries:
|
||||||
|
return "", []
|
||||||
if not entries:
|
if not entries:
|
||||||
return (f"{central_label} is {_clean(text)}" if _clean(text) else "", [])
|
return (f"{central_label} is {_clean(text)}" if _clean(text) else "", [])
|
||||||
labels = [label for label, _descriptor in entries]
|
labels = [label for label, _descriptor in entries]
|
||||||
@@ -250,6 +258,87 @@ def _cast_prose(text: str, central_label: str = "Woman A") -> tuple[str, list[st
|
|||||||
return " ".join(sentences), labels
|
return " ".join(sentences), labels
|
||||||
|
|
||||||
|
|
||||||
|
def _pov_labels_from_value(value: Any) -> list[str]:
|
||||||
|
labels: list[str] = []
|
||||||
|
if isinstance(value, list):
|
||||||
|
candidates = value
|
||||||
|
else:
|
||||||
|
candidates = re.split(r"[,;]\s*", _clean(value)) if _clean(value) else []
|
||||||
|
for candidate in candidates:
|
||||||
|
label = _clean(candidate)
|
||||||
|
if re.match(r"^Man [A-Z]$", label) and label not in labels:
|
||||||
|
labels.append(label)
|
||||||
|
return labels
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_labels(*groups: list[str]) -> list[str]:
|
||||||
|
merged: list[str] = []
|
||||||
|
for group in groups:
|
||||||
|
for label in group:
|
||||||
|
if label and label not in merged:
|
||||||
|
merged.append(label)
|
||||||
|
return merged
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_pov_labeled_clauses(text: Any, pov_labels: list[str]) -> str:
|
||||||
|
rendered = _clean(text)
|
||||||
|
if not rendered or not pov_labels:
|
||||||
|
return rendered
|
||||||
|
clauses = [clause.strip() for clause in rendered.split(";") if clause.strip()]
|
||||||
|
filtered = [
|
||||||
|
clause
|
||||||
|
for clause in clauses
|
||||||
|
if not any(re.match(rf"^{re.escape(label)}\b", clause) for label in pov_labels)
|
||||||
|
]
|
||||||
|
return "; ".join(filtered)
|
||||||
|
|
||||||
|
|
||||||
|
def _pov_action_phrase(action: Any, pov_labels: list[str]) -> str:
|
||||||
|
rendered = _clean(action)
|
||||||
|
if not rendered or not pov_labels:
|
||||||
|
return rendered
|
||||||
|
for label in sorted(pov_labels, key=len, reverse=True):
|
||||||
|
escaped = re.escape(label)
|
||||||
|
rendered = re.sub(rf"\b{escaped}'s\b", "the POV viewer's", rendered)
|
||||||
|
rendered = re.sub(rf"\b{escaped}\b", "the POV viewer", rendered)
|
||||||
|
if "Man A" in pov_labels:
|
||||||
|
rendered = re.sub(r"\bthe man's\b", "the POV viewer's", rendered, flags=re.IGNORECASE)
|
||||||
|
rendered = re.sub(r"\bthe man\b", "the POV viewer", rendered, flags=re.IGNORECASE)
|
||||||
|
rendered = re.sub(r"\bhe\b", "the POV viewer", rendered, flags=re.IGNORECASE)
|
||||||
|
rendered = re.sub(r"\bhim\b", "the POV viewer", rendered, flags=re.IGNORECASE)
|
||||||
|
rendered = re.sub(r"\bhis\b", "the POV viewer's", rendered, flags=re.IGNORECASE)
|
||||||
|
rendered = re.sub(r"\bthe POV viewer is positioned\b", "the POV camera is positioned", rendered, flags=re.IGNORECASE)
|
||||||
|
return rendered
|
||||||
|
|
||||||
|
|
||||||
|
def _pov_camera_phrase(pov_labels: list[str], softcore: bool = False) -> str:
|
||||||
|
if not pov_labels:
|
||||||
|
return ""
|
||||||
|
if softcore:
|
||||||
|
return (
|
||||||
|
"first-person POV creator framing from the male participant; "
|
||||||
|
"the woman is the visible subject, and the participant is implied by camera position or foreground cues"
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
"first-person POV from the male participant; keep the visible partners readable from the viewer's position, "
|
||||||
|
"using only foreground hands, body position, or camera perspective cues for the POV participant"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _pov_composition_text(composition: Any, pov_labels: list[str]) -> str:
|
||||||
|
text = _clean(composition)
|
||||||
|
if not text or not pov_labels:
|
||||||
|
return text
|
||||||
|
text = re.sub(r"\ball participants visible\b", "visible partners readable", text, flags=re.IGNORECASE)
|
||||||
|
text = re.sub(r"\ball adult bodies visible\b", "visible partners readable", text, flags=re.IGNORECASE)
|
||||||
|
text = re.sub(r"\ball bodies visible\b", "visible partners readable", text, flags=re.IGNORECASE)
|
||||||
|
text = re.sub(r"\ball three bodies readable\b", "visible partner bodies readable", text, flags=re.IGNORECASE)
|
||||||
|
text = re.sub(r"\bwide group-sex composition\b", "first-person group-sex POV composition", text, flags=re.IGNORECASE)
|
||||||
|
if "pov" not in text.lower() and "first-person" not in text.lower():
|
||||||
|
text = f"{text}, adapted for first-person POV with the POV participant kept off-camera"
|
||||||
|
return _clean(text)
|
||||||
|
|
||||||
|
|
||||||
def _sanitize_scene_text_for_cast(text: Any, labels: list[str]) -> str:
|
def _sanitize_scene_text_for_cast(text: Any, labels: list[str]) -> str:
|
||||||
text = _clean(text)
|
text = _clean(text)
|
||||||
if not text:
|
if not text:
|
||||||
@@ -633,6 +722,34 @@ def _hardcore_pose_arrangement(anchor: str, role_graph: str, hard_item: str, com
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _arrangement_duplicates_role(arrangement: str, role_graph: str) -> bool:
|
||||||
|
arrangement_lower = _clean(arrangement).lower()
|
||||||
|
role_lower = _clean(role_graph).lower()
|
||||||
|
if not arrangement_lower or not role_lower:
|
||||||
|
return False
|
||||||
|
markers = (
|
||||||
|
"bed edge",
|
||||||
|
"on all fours",
|
||||||
|
"face-down",
|
||||||
|
"hips raised",
|
||||||
|
"bent forward",
|
||||||
|
"straddl",
|
||||||
|
"on her back",
|
||||||
|
"on their sides",
|
||||||
|
"on her side",
|
||||||
|
"seated in",
|
||||||
|
"sits in",
|
||||||
|
"lap",
|
||||||
|
"kneeling between",
|
||||||
|
"kneels between",
|
||||||
|
"kneeling in front",
|
||||||
|
"kneels in front",
|
||||||
|
"positioned behind",
|
||||||
|
"standing",
|
||||||
|
)
|
||||||
|
return any(marker in arrangement_lower and marker in role_lower for marker in markers)
|
||||||
|
|
||||||
|
|
||||||
def _hardcore_item_detail(hard_item: str) -> str:
|
def _hardcore_item_detail(hard_item: str) -> str:
|
||||||
text = _clean(hard_item).rstrip(".")
|
text = _clean(hard_item).rstrip(".")
|
||||||
if not text:
|
if not text:
|
||||||
@@ -1071,7 +1188,7 @@ def _hardcore_action_sentence(
|
|||||||
detail = _limit_detail_for_density(detail, detail_density, False)
|
detail = _limit_detail_for_density(detail, detail_density, False)
|
||||||
arrangement = _hardcore_pose_arrangement(anchor, role_graph, hard_item, composition, axis_values)
|
arrangement = _hardcore_pose_arrangement(anchor, role_graph, hard_item, composition, axis_values)
|
||||||
anchor_phrase = _with_indefinite_article(anchor) if anchor else ""
|
anchor_phrase = _with_indefinite_article(anchor) if anchor else ""
|
||||||
if arrangement and anchor_phrase:
|
if arrangement and anchor_phrase and not _arrangement_duplicates_role(arrangement, role_graph):
|
||||||
anchor_phrase = f"{anchor_phrase} {arrangement}"
|
anchor_phrase = f"{anchor_phrase} {arrangement}"
|
||||||
if role_graph and anchor_phrase:
|
if role_graph and anchor_phrase:
|
||||||
sentence = f"In {anchor_phrase}, {role_graph}"
|
sentence = f"In {anchor_phrase}, {role_graph}"
|
||||||
@@ -1258,6 +1375,12 @@ def _normal_row_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
|
|||||||
if not _expression_disabled(row):
|
if not _expression_disabled(row):
|
||||||
expression = _row_value(row, "character_expression_text") or _row_value(row, "expression", ("Facial expressions", "Facial expression"))
|
expression = _row_value(row, "character_expression_text") or _row_value(row, "expression", ("Facial expressions", "Facial expression"))
|
||||||
composition = re.sub(r"^vertical\s+", "", _row_value(row, "composition", ("Composition",)), flags=re.IGNORECASE)
|
composition = re.sub(r"^vertical\s+", "", _row_value(row, "composition", ("Composition",)), flags=re.IGNORECASE)
|
||||||
|
source_composition = re.sub(
|
||||||
|
r"^vertical\s+",
|
||||||
|
"",
|
||||||
|
_clean(row.get("source_composition")) or composition,
|
||||||
|
flags=re.IGNORECASE,
|
||||||
|
)
|
||||||
camera = _camera_phrase(row)
|
camera = _camera_phrase(row)
|
||||||
style = _style_phrase(row, style_mode)
|
style = _style_phrase(row, style_mode)
|
||||||
|
|
||||||
@@ -1274,25 +1397,31 @@ def _normal_row_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
|
|||||||
or _prompt_field(_clean(row.get("prompt")), "Characters")
|
or _prompt_field(_clean(row.get("prompt")), "Characters")
|
||||||
or _prompt_field(_clean(row.get("prompt")), "Cast descriptors")
|
or _prompt_field(_clean(row.get("prompt")), "Cast descriptors")
|
||||||
)
|
)
|
||||||
cast_prose, cast_labels = _cast_prose(cast_descriptor_text)
|
pov_labels = _pov_labels_from_value(row.get("pov_character_labels"))
|
||||||
|
cast_prose, cast_labels = _cast_prose(cast_descriptor_text, omit_labels=pov_labels)
|
||||||
if not cast_labels and women_count == 1 and men_count == 1:
|
if not cast_labels and women_count == 1 and men_count == 1:
|
||||||
cast_labels = ["Woman A", "Man A"]
|
cast_labels = ["Woman A", "Man A"]
|
||||||
|
cast_labels = _merge_labels(cast_labels, pov_labels)
|
||||||
|
expression = _filter_pov_labeled_clauses(expression, pov_labels)
|
||||||
expression = _natural_label_text(expression, cast_labels)
|
expression = _natural_label_text(expression, cast_labels)
|
||||||
role_graph = _sanitize_scene_text_for_cast(row.get("role_graph"), cast_labels)
|
role_graph = _sanitize_scene_text_for_cast(row.get("source_role_graph") or row.get("role_graph"), cast_labels)
|
||||||
item = _sanitize_scene_text_for_cast(item, cast_labels)
|
item = _sanitize_scene_text_for_cast(item, cast_labels)
|
||||||
role_graph = _natural_label_text(role_graph, cast_labels)
|
role_graph = _natural_label_text(role_graph, cast_labels)
|
||||||
item = _natural_label_text(item, 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 {}
|
axis_values = row.get("item_axis_values") if isinstance(row.get("item_axis_values"), dict) else {}
|
||||||
detail_density = _normalize_hardcore_detail_density(row.get("hardcore_detail_density"))
|
detail_density = _normalize_hardcore_detail_density(row.get("hardcore_detail_density"))
|
||||||
action = _hardcore_action_sentence(role_graph, item, composition, axis_values, detail_density)
|
action = _hardcore_action_sentence(role_graph, item, source_composition, axis_values, detail_density)
|
||||||
|
action = _pov_action_phrase(action, pov_labels)
|
||||||
|
output_composition = _pov_composition_text(composition, pov_labels)
|
||||||
parts = [
|
parts = [
|
||||||
action,
|
action,
|
||||||
|
_pov_camera_phrase(pov_labels),
|
||||||
cast_prose,
|
cast_prose,
|
||||||
f"A consensual explicit adult scene with {subject}" if not action else "",
|
f"A consensual explicit adult scene with {subject}" if not action else "",
|
||||||
f"The cast includes {cast}" if cast and not cast_prose and not (women_count == 1 and men_count == 1) else "",
|
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 "",
|
f"The setting is {scene}" if scene else "",
|
||||||
_expression_phrase(expression),
|
_expression_phrase(expression),
|
||||||
_composition_phrase(composition, action, "The image is framed as", detail_density),
|
_composition_phrase(output_composition, action, "The image is framed as", detail_density),
|
||||||
camera,
|
camera,
|
||||||
style if detail_level != "concise" else "",
|
style if detail_level != "concise" else "",
|
||||||
]
|
]
|
||||||
@@ -1367,15 +1496,26 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
|
|||||||
same_room = options.get("continuity") == "same_creator_same_room"
|
same_room = options.get("continuity") == "same_creator_same_room"
|
||||||
hard_scene = soft.get("scene_text") if same_room and soft.get("scene_text") else hard.get("scene_text")
|
hard_scene = soft.get("scene_text") if same_room and soft.get("scene_text") else hard.get("scene_text")
|
||||||
hard_composition = hard.get("composition")
|
hard_composition = hard.get("composition")
|
||||||
|
hard_source_composition = hard.get("source_composition") or hard_composition
|
||||||
|
pov_labels = _merge_labels(
|
||||||
|
_pov_labels_from_value(row.get("pov_character_labels")),
|
||||||
|
_pov_labels_from_value(soft.get("pov_character_labels")),
|
||||||
|
_pov_labels_from_value(hard.get("pov_character_labels")),
|
||||||
|
)
|
||||||
soft_cast_descriptor_text = (
|
soft_cast_descriptor_text = (
|
||||||
cast_descriptor_text
|
cast_descriptor_text
|
||||||
if options.get("softcore_cast") == "same_as_hardcore"
|
if options.get("softcore_cast") == "same_as_hardcore"
|
||||||
else f"Woman A: {descriptor}"
|
else f"Woman A: {descriptor}"
|
||||||
)
|
)
|
||||||
soft_cast_prose, soft_labels = _cast_prose(soft_cast_descriptor_text)
|
soft_cast_prose, soft_labels = _cast_prose(
|
||||||
hard_cast_prose, hard_labels = _cast_prose(cast_descriptor_text)
|
soft_cast_descriptor_text,
|
||||||
|
omit_labels=pov_labels if options.get("softcore_cast") == "same_as_hardcore" else [],
|
||||||
|
)
|
||||||
|
hard_cast_prose, hard_labels = _cast_prose(cast_descriptor_text, omit_labels=pov_labels)
|
||||||
|
soft_labels = _merge_labels(soft_labels, pov_labels if options.get("softcore_cast") == "same_as_hardcore" else [])
|
||||||
|
hard_labels = _merge_labels(hard_labels, pov_labels)
|
||||||
hard_item = _sanitize_scene_text_for_cast(hard.get("item"), hard_labels)
|
hard_item = _sanitize_scene_text_for_cast(hard.get("item"), hard_labels)
|
||||||
hard_role_graph = _sanitize_scene_text_for_cast(hard.get("role_graph"), hard_labels)
|
hard_role_graph = _sanitize_scene_text_for_cast(hard.get("source_role_graph") or hard.get("role_graph"), hard_labels)
|
||||||
hard_item = _natural_label_text(hard_item, hard_labels)
|
hard_item = _natural_label_text(hard_item, hard_labels)
|
||||||
hard_role_graph = _natural_label_text(hard_role_graph, 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_axis_values = hard.get("item_axis_values") if isinstance(hard.get("item_axis_values"), dict) else {}
|
||||||
@@ -1385,16 +1525,25 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
|
|||||||
hard_action = _hardcore_action_sentence(
|
hard_action = _hardcore_action_sentence(
|
||||||
hard_role_graph,
|
hard_role_graph,
|
||||||
hard_item,
|
hard_item,
|
||||||
hard_composition,
|
hard_source_composition,
|
||||||
hard_axis_values,
|
hard_axis_values,
|
||||||
hard_detail_density,
|
hard_detail_density,
|
||||||
)
|
)
|
||||||
|
hard_action = _pov_action_phrase(hard_action, pov_labels)
|
||||||
|
hard_output_composition = _pov_composition_text(hard_composition, pov_labels)
|
||||||
same_soft_cast = options.get("softcore_cast") == "same_as_hardcore"
|
same_soft_cast = options.get("softcore_cast") == "same_as_hardcore"
|
||||||
soft_cast_presence = (
|
soft_output_composition = _pov_composition_text(soft.get("composition"), pov_labels if same_soft_cast else [])
|
||||||
f"{_label_join(soft_labels)} are together in a non-explicit teaser pose, with no sex act or genital contact"
|
if same_soft_cast and pov_labels:
|
||||||
if same_soft_cast
|
soft_cast_presence = (
|
||||||
else "The image focuses on the woman alone"
|
"the woman is framed from the POV participant's first-person camera in a non-explicit teaser pose, "
|
||||||
)
|
"with the POV participant kept off-camera as the viewpoint and implied by camera position or foreground cues"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
soft_cast_presence = (
|
||||||
|
f"{_label_join(soft_labels)} are together in a non-explicit teaser pose, with no sex act or genital contact"
|
||||||
|
if same_soft_cast
|
||||||
|
else "The image focuses on the woman alone"
|
||||||
|
)
|
||||||
partner_styling = row.get("softcore_partner_styling")
|
partner_styling = row.get("softcore_partner_styling")
|
||||||
if isinstance(partner_styling, dict):
|
if isinstance(partner_styling, dict):
|
||||||
outfits = partner_styling.get("outfits")
|
outfits = partner_styling.get("outfits")
|
||||||
@@ -1403,18 +1552,29 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
|
|||||||
else:
|
else:
|
||||||
partner_outfit_text = ""
|
partner_outfit_text = ""
|
||||||
partner_pose = ""
|
partner_pose = ""
|
||||||
|
partner_outfit_text = _filter_pov_labeled_clauses(partner_outfit_text, pov_labels)
|
||||||
|
if pov_labels:
|
||||||
|
partner_pose = ""
|
||||||
partner_outfit_text = _natural_label_text(partner_outfit_text, soft_labels)
|
partner_outfit_text = _natural_label_text(partner_outfit_text, soft_labels)
|
||||||
|
|
||||||
soft_expression = ""
|
soft_expression = ""
|
||||||
if not _expression_disabled(soft):
|
if not _expression_disabled(soft):
|
||||||
soft_expression = _natural_label_text(
|
soft_expression_source = _filter_pov_labeled_clauses(
|
||||||
_clean(soft.get("character_expression_text")) or _clean(soft.get("expression")),
|
_clean(soft.get("character_expression_text")) or _clean(soft.get("expression")),
|
||||||
|
pov_labels,
|
||||||
|
)
|
||||||
|
soft_expression = _natural_label_text(
|
||||||
|
soft_expression_source,
|
||||||
soft_labels,
|
soft_labels,
|
||||||
)
|
)
|
||||||
hard_expression = ""
|
hard_expression = ""
|
||||||
if not _expression_disabled(hard):
|
if not _expression_disabled(hard):
|
||||||
hard_expression = _natural_label_text(
|
hard_expression_source = _filter_pov_labeled_clauses(
|
||||||
_clean(hard.get("character_expression_text")) or _clean(hard.get("expression")),
|
_clean(hard.get("character_expression_text")) or _clean(hard.get("expression")),
|
||||||
|
pov_labels,
|
||||||
|
)
|
||||||
|
hard_expression = _natural_label_text(
|
||||||
|
hard_expression_source,
|
||||||
hard_labels,
|
hard_labels,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1423,21 +1583,23 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
|
|||||||
soft_cast_presence,
|
soft_cast_presence,
|
||||||
partner_outfit_text,
|
partner_outfit_text,
|
||||||
partner_pose,
|
partner_pose,
|
||||||
|
_pov_camera_phrase(pov_labels, softcore=True) if same_soft_cast else "",
|
||||||
f"wearing {soft.get('item')}" if soft.get("item") else "",
|
f"wearing {soft.get('item')}" if soft.get("item") else "",
|
||||||
f"{soft.get('pose')}" if soft.get("pose") else "",
|
f"{soft.get('pose')}" if soft.get("pose") else "",
|
||||||
_expression_phrase(soft_expression),
|
_expression_phrase(soft_expression),
|
||||||
f"in {soft.get('scene_text')}" if soft.get("scene_text") else "",
|
f"in {soft.get('scene_text')}" if soft.get("scene_text") else "",
|
||||||
f"framed as {soft.get('composition')}" if soft.get("composition") else "",
|
f"framed as {soft_output_composition}" if soft_output_composition else "",
|
||||||
soft_camera,
|
soft_camera,
|
||||||
soft_style if detail_level != "concise" else "",
|
soft_style if detail_level != "concise" else "",
|
||||||
]
|
]
|
||||||
hard_parts = [
|
hard_parts = [
|
||||||
hard_action,
|
hard_action,
|
||||||
|
_pov_camera_phrase(pov_labels),
|
||||||
_natural_clothing_state(row.get("hardcore_clothing_state")),
|
_natural_clothing_state(row.get("hardcore_clothing_state")),
|
||||||
hard_cast_prose,
|
hard_cast_prose,
|
||||||
f"set in {hard_scene}" if hard_scene else "",
|
f"set in {hard_scene}" if hard_scene else "",
|
||||||
_expression_phrase(hard_expression),
|
_expression_phrase(hard_expression),
|
||||||
_composition_phrase(hard_composition, hard_action, detail_density=hard_detail_density),
|
_composition_phrase(hard_output_composition, hard_action, detail_density=hard_detail_density),
|
||||||
hard_camera,
|
hard_camera,
|
||||||
hard_style if detail_level != "concise" else "",
|
hard_style if detail_level != "concise" else "",
|
||||||
]
|
]
|
||||||
|
|||||||
+172
-7
@@ -175,6 +175,7 @@ CHARACTER_MAN_BODY_CHOICES = [
|
|||||||
"fat",
|
"fat",
|
||||||
]
|
]
|
||||||
CHARACTER_DESCRIPTOR_DETAIL_CHOICES = ["auto", "full", "medium", "compact", "minimal"]
|
CHARACTER_DESCRIPTOR_DETAIL_CHOICES = ["auto", "full", "medium", "compact", "minimal"]
|
||||||
|
CHARACTER_PRESENCE_CHOICES = ["visible", "pov"]
|
||||||
CHARACTER_RANDOM_TOKENS = {"", "random", "auto", "global", "from_global", "default"}
|
CHARACTER_RANDOM_TOKENS = {"", "random", "auto", "global", "from_global", "default"}
|
||||||
|
|
||||||
CAMERA_DETAIL_CHOICES = ["off", "compact", "full"]
|
CAMERA_DETAIL_CHOICES = ["off", "compact", "full"]
|
||||||
@@ -1429,6 +1430,10 @@ def character_descriptor_detail_choices() -> list[str]:
|
|||||||
return list(CHARACTER_DESCRIPTOR_DETAIL_CHOICES)
|
return list(CHARACTER_DESCRIPTOR_DETAIL_CHOICES)
|
||||||
|
|
||||||
|
|
||||||
|
def character_presence_choices() -> list[str]:
|
||||||
|
return list(CHARACTER_PRESENCE_CHOICES)
|
||||||
|
|
||||||
|
|
||||||
def character_ethnicity_choices() -> list[str]:
|
def character_ethnicity_choices() -> list[str]:
|
||||||
return ["random"] + list(ETHNICITY_FILTER_CHOICES)
|
return ["random"] + list(ETHNICITY_FILTER_CHOICES)
|
||||||
|
|
||||||
@@ -1863,6 +1868,21 @@ def _normalize_descriptor_detail(value: Any) -> str:
|
|||||||
return text if text in CHARACTER_DESCRIPTOR_DETAIL_CHOICES else "auto"
|
return text if text in CHARACTER_DESCRIPTOR_DETAIL_CHOICES else "auto"
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_presence_mode(value: Any, subject_type: str) -> str:
|
||||||
|
text = str(value or "visible").strip().lower()
|
||||||
|
if text not in CHARACTER_PRESENCE_CHOICES:
|
||||||
|
text = "visible"
|
||||||
|
if subject_type != "man":
|
||||||
|
return "visible"
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def _slot_is_pov(slot: dict[str, Any] | None) -> bool:
|
||||||
|
if not slot:
|
||||||
|
return False
|
||||||
|
return slot.get("subject_type") == "man" and slot.get("presence_mode") == "pov"
|
||||||
|
|
||||||
|
|
||||||
def _normalize_slot_expression_intensity(value: Any) -> float:
|
def _normalize_slot_expression_intensity(value: Any) -> float:
|
||||||
try:
|
try:
|
||||||
intensity = float(value)
|
intensity = float(value)
|
||||||
@@ -1907,6 +1927,8 @@ def _cast_expression_intensity_override(
|
|||||||
value_labels: list[str] = []
|
value_labels: list[str] = []
|
||||||
for label in labels:
|
for label in labels:
|
||||||
slot = label_map.get(label)
|
slot = label_map.get(label)
|
||||||
|
if _slot_is_pov(slot):
|
||||||
|
continue
|
||||||
if slot:
|
if slot:
|
||||||
matching_slots.append(slot)
|
matching_slots.append(slot)
|
||||||
value = _slot_expression_intensity(slot)
|
value = _slot_expression_intensity(slot)
|
||||||
@@ -1943,6 +1965,8 @@ def _character_expression_entries(
|
|||||||
slot = label_map.get(label)
|
slot = label_map.get(label)
|
||||||
if not slot:
|
if not slot:
|
||||||
continue
|
continue
|
||||||
|
if _slot_is_pov(slot):
|
||||||
|
continue
|
||||||
if not _slot_expression_enabled(slot):
|
if not _slot_expression_enabled(slot):
|
||||||
continue
|
continue
|
||||||
intensity = _slot_expression_intensity(slot)
|
intensity = _slot_expression_intensity(slot)
|
||||||
@@ -2050,6 +2074,7 @@ def _normalize_character_slot(slot: dict[str, Any]) -> dict[str, Any]:
|
|||||||
"hair": _slot_value(slot.get("hair")),
|
"hair": _slot_value(slot.get("hair")),
|
||||||
"eyes": _slot_value(slot.get("eyes")),
|
"eyes": _slot_value(slot.get("eyes")),
|
||||||
"descriptor_detail": _normalize_descriptor_detail(slot.get("descriptor_detail")),
|
"descriptor_detail": _normalize_descriptor_detail(slot.get("descriptor_detail")),
|
||||||
|
"presence_mode": _normalize_presence_mode(slot.get("presence_mode"), subject_type),
|
||||||
"expression_enabled": not _is_false(slot.get("expression_enabled", True)),
|
"expression_enabled": not _is_false(slot.get("expression_enabled", True)),
|
||||||
"expression_intensity": _normalize_slot_expression_intensity(slot.get("expression_intensity")),
|
"expression_intensity": _normalize_slot_expression_intensity(slot.get("expression_intensity")),
|
||||||
}
|
}
|
||||||
@@ -2096,6 +2121,8 @@ def _character_slot_summary(slot: dict[str, Any]) -> str:
|
|||||||
f"body={slot.get('body', 'random')}",
|
f"body={slot.get('body', 'random')}",
|
||||||
f"detail={slot.get('descriptor_detail', 'auto')}",
|
f"detail={slot.get('descriptor_detail', 'auto')}",
|
||||||
]
|
]
|
||||||
|
if _slot_is_pov(slot):
|
||||||
|
parts.append("presence=pov")
|
||||||
if not _slot_expression_enabled(slot):
|
if not _slot_expression_enabled(slot):
|
||||||
parts.append("expression=disabled")
|
parts.append("expression=disabled")
|
||||||
else:
|
else:
|
||||||
@@ -2127,6 +2154,7 @@ def build_character_slot_json(
|
|||||||
expression_intensity: float = -1.0,
|
expression_intensity: float = -1.0,
|
||||||
enabled: bool = True,
|
enabled: bool = True,
|
||||||
character_cast: str | dict[str, Any] | list[Any] | None = "",
|
character_cast: str | dict[str, Any] | list[Any] | None = "",
|
||||||
|
presence_mode: str = "visible",
|
||||||
) -> dict[str, str]:
|
) -> dict[str, str]:
|
||||||
existing_slots = _parse_character_cast(character_cast)
|
existing_slots = _parse_character_cast(character_cast)
|
||||||
slot = _normalize_character_slot(
|
slot = _normalize_character_slot(
|
||||||
@@ -2144,6 +2172,7 @@ def build_character_slot_json(
|
|||||||
"hair": hair,
|
"hair": hair,
|
||||||
"eyes": eyes,
|
"eyes": eyes,
|
||||||
"descriptor_detail": descriptor_detail,
|
"descriptor_detail": descriptor_detail,
|
||||||
|
"presence_mode": presence_mode,
|
||||||
"expression_enabled": expression_enabled,
|
"expression_enabled": expression_enabled,
|
||||||
"expression_intensity": expression_intensity,
|
"expression_intensity": expression_intensity,
|
||||||
}
|
}
|
||||||
@@ -2186,6 +2215,62 @@ def _character_slot_label_map(slots: list[dict[str, Any]]) -> dict[str, dict[str
|
|||||||
return label_map
|
return label_map
|
||||||
|
|
||||||
|
|
||||||
|
def _pov_character_labels(
|
||||||
|
label_map: dict[str, dict[str, Any]],
|
||||||
|
men_count: int | None = None,
|
||||||
|
) -> list[str]:
|
||||||
|
if men_count is None:
|
||||||
|
labels = sorted(label for label in label_map if label.startswith("Man "))
|
||||||
|
else:
|
||||||
|
labels = [f"Man {chr(ord('A') + index)}" for index in range(max(0, men_count))]
|
||||||
|
return [label for label in labels if _slot_is_pov(label_map.get(label))]
|
||||||
|
|
||||||
|
|
||||||
|
def _pov_text_with_viewer(text: Any, pov_labels: list[str]) -> str:
|
||||||
|
rendered = str(text or "").strip()
|
||||||
|
if not rendered or not pov_labels:
|
||||||
|
return rendered
|
||||||
|
for label in sorted(pov_labels, key=len, reverse=True):
|
||||||
|
escaped = re.escape(label)
|
||||||
|
rendered = re.sub(rf"\b{escaped}'s\b", "the POV viewer's", rendered)
|
||||||
|
rendered = re.sub(rf"\b{escaped}\b", "the POV viewer", rendered)
|
||||||
|
rendered = re.sub(r"\bthe POV viewer is positioned\b", "the POV camera is positioned", rendered, flags=re.IGNORECASE)
|
||||||
|
return _clean_prompt_punctuation(rendered)
|
||||||
|
|
||||||
|
|
||||||
|
def _pov_role_graph_prompt(role_graph: Any, pov_labels: list[str]) -> str:
|
||||||
|
role_graph_text = str(role_graph or "").strip()
|
||||||
|
if not role_graph_text or not pov_labels:
|
||||||
|
return role_graph_text
|
||||||
|
viewer_text = _pov_text_with_viewer(role_graph_text, pov_labels)
|
||||||
|
label_text = ", ".join(pov_labels)
|
||||||
|
return f"First-person POV from {label_text}; {viewer_text}"
|
||||||
|
|
||||||
|
|
||||||
|
def _pov_prompt_directive(pov_labels: list[str]) -> str:
|
||||||
|
if not pov_labels:
|
||||||
|
return ""
|
||||||
|
label_text = ", ".join(pov_labels)
|
||||||
|
return (
|
||||||
|
f"POV participant: {label_text} is the first-person camera viewpoint; "
|
||||||
|
"he remains the off-camera viewpoint, represented by foreground hands, body position, or camera perspective cues when needed."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _pov_composition_prompt(composition: Any, pov_labels: list[str]) -> str:
|
||||||
|
text = str(composition or "").strip()
|
||||||
|
if not text or not pov_labels:
|
||||||
|
return text
|
||||||
|
text = re.sub(r"\ball participants visible\b", "visible partners readable", text, flags=re.IGNORECASE)
|
||||||
|
text = re.sub(r"\ball adult bodies visible\b", "visible partners readable", text, flags=re.IGNORECASE)
|
||||||
|
text = re.sub(r"\ball bodies visible\b", "visible partners readable", text, flags=re.IGNORECASE)
|
||||||
|
text = re.sub(r"\ball three bodies readable\b", "visible partner bodies readable", text, flags=re.IGNORECASE)
|
||||||
|
text = re.sub(r"\bwide group-sex composition\b", "first-person group-sex POV composition", text, flags=re.IGNORECASE)
|
||||||
|
if "pov" not in text.lower() and "first-person" not in text.lower():
|
||||||
|
text = f"{text}, adapted for first-person POV with the POV participant kept off-camera"
|
||||||
|
return _clean_prompt_punctuation(text)
|
||||||
|
|
||||||
|
|
||||||
def _context_from_character_slot(
|
def _context_from_character_slot(
|
||||||
rng: random.Random,
|
rng: random.Random,
|
||||||
slot: dict[str, Any],
|
slot: dict[str, Any],
|
||||||
@@ -2228,6 +2313,7 @@ def _context_from_character_slot(
|
|||||||
if value:
|
if value:
|
||||||
context[key] = value
|
context[key] = value
|
||||||
context["descriptor_detail"] = _normalize_descriptor_detail(slot.get("descriptor_detail"))
|
context["descriptor_detail"] = _normalize_descriptor_detail(slot.get("descriptor_detail"))
|
||||||
|
context["presence_mode"] = _normalize_presence_mode(slot.get("presence_mode"), subject_type)
|
||||||
context["expression_enabled"] = _slot_expression_enabled(slot)
|
context["expression_enabled"] = _slot_expression_enabled(slot)
|
||||||
expression_intensity = _slot_expression_intensity(slot)
|
expression_intensity = _slot_expression_intensity(slot)
|
||||||
if expression_intensity is not None:
|
if expression_intensity is not None:
|
||||||
@@ -2267,6 +2353,7 @@ def _apply_character_context_to_row(row: dict[str, Any], context: dict[str, Any]
|
|||||||
"eyes",
|
"eyes",
|
||||||
"figure",
|
"figure",
|
||||||
"descriptor_detail",
|
"descriptor_detail",
|
||||||
|
"presence_mode",
|
||||||
"expression_enabled",
|
"expression_enabled",
|
||||||
"expression_intensity",
|
"expression_intensity",
|
||||||
):
|
):
|
||||||
@@ -2304,6 +2391,8 @@ def _cast_descriptor_entries(
|
|||||||
descriptors.append(f"{label}: {_insta_of_descriptor_from_context(context)}")
|
descriptors.append(f"{label}: {_insta_of_descriptor_from_context(context)}")
|
||||||
for index in range(max(0, men_count)):
|
for index in range(max(0, men_count)):
|
||||||
label = f"Man {chr(ord('A') + index)}"
|
label = f"Man {chr(ord('A') + index)}"
|
||||||
|
if _slot_is_pov(label_map.get(label)):
|
||||||
|
continue
|
||||||
context, _slot = _character_context_for_label(label, label_map, rng, ethnicity, figure, no_plus_women, no_black)
|
context, _slot = _character_context_for_label(label, label_map, rng, ethnicity, figure, no_plus_women, no_black)
|
||||||
descriptors.append(f"{label}: {_insta_of_descriptor_from_context(context)}")
|
descriptors.append(f"{label}: {_insta_of_descriptor_from_context(context)}")
|
||||||
return descriptors, slots
|
return descriptors, slots
|
||||||
@@ -2786,6 +2875,34 @@ def _role_graph(
|
|||||||
return f"{woman} kneels in front of {man} at hip height while {man} ejaculates semen onto her face, lips, and chest."
|
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."
|
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 while {man} is above her and {man}'s penis thrusts into her."
|
||||||
|
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."
|
||||||
|
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."
|
||||||
|
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."
|
||||||
|
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."
|
||||||
|
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."
|
||||||
|
if "edge-of-bed" in text or "edge of bed" in text or "bed edge" in text:
|
||||||
|
return f"{woman} lies at the bed edge with hips near the edge while {man} kneels between her legs and {man}'s penis thrusts into her."
|
||||||
|
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."
|
||||||
|
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."
|
||||||
|
return f"{woman} lies on her back with thighs open while {man} kneels between her legs and {man}'s penis thrusts into her."
|
||||||
|
|
||||||
if people_count == 1:
|
if people_count == 1:
|
||||||
solo = people[0]
|
solo = people[0]
|
||||||
if women_count == 1:
|
if women_count == 1:
|
||||||
@@ -2870,7 +2987,7 @@ def _role_graph(
|
|||||||
elif "cumshot" in slug or "climax" in slug:
|
elif "cumshot" in slug or "climax" in slug:
|
||||||
graph = climax_position_graph(woman, man, third)
|
graph = climax_position_graph(woman, man, third)
|
||||||
else:
|
else:
|
||||||
graph = f"{man}'s penis thrusts into {woman} with penetration and body contact visible."
|
graph = penetration_position_graph(woman, man)
|
||||||
return graph + support_sentence({woman, man, third} if third else {woman, man})
|
return graph + support_sentence({woman, man, third} if third else {woman, man})
|
||||||
|
|
||||||
|
|
||||||
@@ -3246,7 +3363,13 @@ def _build_custom_row(
|
|||||||
else:
|
else:
|
||||||
context, applied_profile, profile_status = _apply_character_profile_to_context(context, character_profile)
|
context, applied_profile, profile_status = _apply_character_profile_to_context(context, character_profile)
|
||||||
subject_type = context["subject_type"]
|
subject_type = context["subject_type"]
|
||||||
role_graph = _role_graph(role_rng, subcategory, context, item_axis_values)
|
pov_character_labels = (
|
||||||
|
_pov_character_labels(character_slot_map, men_count)
|
||||||
|
if subject_type == "configured_cast"
|
||||||
|
else []
|
||||||
|
)
|
||||||
|
source_role_graph = _role_graph(role_rng, subcategory, context, item_axis_values)
|
||||||
|
role_graph = _pov_role_graph_prompt(source_role_graph, pov_character_labels)
|
||||||
cast_descriptors: list[str] = []
|
cast_descriptors: list[str] = []
|
||||||
cast_descriptor_text = ""
|
cast_descriptor_text = ""
|
||||||
expression_intensity_source = "input"
|
expression_intensity_source = "input"
|
||||||
@@ -3320,10 +3443,11 @@ def _build_custom_row(
|
|||||||
character_expression_text = "; ".join(character_expressions)
|
character_expression_text = "; ".join(character_expressions)
|
||||||
if character_expression_text:
|
if character_expression_text:
|
||||||
expression = character_expression_text
|
expression = character_expression_text
|
||||||
composition = _choose_text(
|
source_composition = _choose_text(
|
||||||
composition_rng,
|
composition_rng,
|
||||||
_compatible_entries(_composition_pool(category, subcategory, item, subject_type), women_count, men_count),
|
_compatible_entries(_composition_pool(category, subcategory, item, subject_type), women_count, men_count),
|
||||||
)
|
)
|
||||||
|
composition = _pov_composition_prompt(source_composition, pov_character_labels)
|
||||||
|
|
||||||
negative_prompt = str(_merged_field(category, subcategory, item, "negative_prompt", g.NEGATIVE_PROMPT))
|
negative_prompt = str(_merged_field(category, subcategory, item, "negative_prompt", g.NEGATIVE_PROMPT))
|
||||||
positive_suffix = str(_merged_field(category, subcategory, item, "positive_suffix", GENERIC_POSITIVE_SUFFIX))
|
positive_suffix = str(_merged_field(category, subcategory, item, "positive_suffix", GENERIC_POSITIVE_SUFFIX))
|
||||||
@@ -3360,8 +3484,12 @@ def _build_custom_row(
|
|||||||
"expression_intensity": expression_intensity,
|
"expression_intensity": expression_intensity,
|
||||||
"expression_intensity_source": expression_intensity_source,
|
"expression_intensity_source": expression_intensity_source,
|
||||||
"composition": composition,
|
"composition": composition,
|
||||||
|
"source_composition": source_composition,
|
||||||
"composition_prompt": _composition_prompt(composition),
|
"composition_prompt": _composition_prompt(composition),
|
||||||
"role_graph": role_graph,
|
"role_graph": role_graph,
|
||||||
|
"source_role_graph": source_role_graph,
|
||||||
|
"pov_character_labels": pov_character_labels,
|
||||||
|
"pov_prompt_directive": _pov_prompt_directive(pov_character_labels),
|
||||||
"cast_descriptors": cast_descriptor_text,
|
"cast_descriptors": cast_descriptor_text,
|
||||||
"positive_suffix": positive_suffix,
|
"positive_suffix": positive_suffix,
|
||||||
"negative_prompt": negative_prompt,
|
"negative_prompt": negative_prompt,
|
||||||
@@ -3392,6 +3520,8 @@ def _build_custom_row(
|
|||||||
prompt = _format(template, context)
|
prompt = _format(template, context)
|
||||||
if subject_type == "configured_cast" and cast_descriptor_text and "{cast_descriptors}" not in template:
|
if subject_type == "configured_cast" and cast_descriptor_text and "{cast_descriptors}" not in template:
|
||||||
prompt = _insert_positive_directive(prompt, f"Characters: {cast_descriptor_text}.")
|
prompt = _insert_positive_directive(prompt, f"Characters: {cast_descriptor_text}.")
|
||||||
|
if subject_type == "configured_cast" and pov_character_labels:
|
||||||
|
prompt = _insert_positive_directive(prompt, _pov_prompt_directive(pov_character_labels))
|
||||||
caption = _format(caption_template, context)
|
caption = _format(caption_template, context)
|
||||||
if subject_type == "configured_cast" and cast_descriptor_text and "{cast_descriptors}" not in caption_template:
|
if subject_type == "configured_cast" and cast_descriptor_text and "{cast_descriptors}" not in caption_template:
|
||||||
caption = f"{caption.rstrip()}, {cast_descriptor_text}"
|
caption = f"{caption.rstrip()}, {cast_descriptor_text}"
|
||||||
@@ -3425,6 +3555,10 @@ def _build_custom_row(
|
|||||||
"seed_config": seed_config,
|
"seed_config": seed_config,
|
||||||
"content_seed_axis": content_axis,
|
"content_seed_axis": content_axis,
|
||||||
"role_graph": role_graph,
|
"role_graph": role_graph,
|
||||||
|
"source_role_graph": source_role_graph,
|
||||||
|
"source_composition": source_composition,
|
||||||
|
"pov_character_labels": pov_character_labels,
|
||||||
|
"pov_prompt_directive": _pov_prompt_directive(pov_character_labels),
|
||||||
"shared_expression": shared_expression,
|
"shared_expression": shared_expression,
|
||||||
"character_expressions": character_expressions,
|
"character_expressions": character_expressions,
|
||||||
"character_expression_text": character_expression_text,
|
"character_expression_text": character_expression_text,
|
||||||
@@ -4020,15 +4154,19 @@ def _insta_of_partner_styling(
|
|||||||
row_number: int,
|
row_number: int,
|
||||||
women_count: int,
|
women_count: int,
|
||||||
men_count: int,
|
men_count: int,
|
||||||
|
pov_labels: list[str] | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
content_rng = _axis_rng(seed_config, "content", seed, row_number + 421)
|
content_rng = _axis_rng(seed_config, "content", seed, row_number + 421)
|
||||||
pose_rng = _axis_rng(seed_config, "pose", seed, row_number + 421)
|
pose_rng = _axis_rng(seed_config, "pose", seed, row_number + 421)
|
||||||
|
pov_set = set(pov_labels or [])
|
||||||
outfits: list[str] = []
|
outfits: list[str] = []
|
||||||
for index in range(max(0, women_count - 1)):
|
for index in range(max(0, women_count - 1)):
|
||||||
label = chr(ord("B") + index)
|
label = chr(ord("B") + index)
|
||||||
outfits.append(f"Woman {label} wears {g.choose(content_rng, INSTA_OF_SOFTCORE_PARTNER_WOMEN_OUTFITS)}")
|
outfits.append(f"Woman {label} wears {g.choose(content_rng, INSTA_OF_SOFTCORE_PARTNER_WOMEN_OUTFITS)}")
|
||||||
for index in range(max(0, men_count)):
|
for index in range(max(0, men_count)):
|
||||||
label = chr(ord("A") + index)
|
label = chr(ord("A") + index)
|
||||||
|
if f"Man {label}" in pov_set:
|
||||||
|
continue
|
||||||
outfits.append(f"Man {label} wears {g.choose(content_rng, INSTA_OF_SOFTCORE_PARTNER_MEN_OUTFITS)}")
|
outfits.append(f"Man {label} wears {g.choose(content_rng, INSTA_OF_SOFTCORE_PARTNER_MEN_OUTFITS)}")
|
||||||
return {
|
return {
|
||||||
"outfits": outfits,
|
"outfits": outfits,
|
||||||
@@ -4071,6 +4209,7 @@ def build_insta_of_pair(
|
|||||||
parsed_seed_config = _parse_seed_config(seed_config)
|
parsed_seed_config = _parse_seed_config(seed_config)
|
||||||
character_slots = _parse_character_cast(character_cast)
|
character_slots = _parse_character_cast(character_cast)
|
||||||
character_slot_map = _character_slot_label_map(character_slots)
|
character_slot_map = _character_slot_label_map(character_slots)
|
||||||
|
pov_character_labels = _pov_character_labels(character_slot_map, hard_men_count)
|
||||||
softcore_level_key = str(options["softcore_level"])
|
softcore_level_key = str(options["softcore_level"])
|
||||||
soft_category, soft_subcategory = _insta_of_softcore_category(softcore_level_key)
|
soft_category, soft_subcategory = _insta_of_softcore_category(softcore_level_key)
|
||||||
soft_content_rng = _axis_rng(parsed_seed_config, "content", seed, row_number + 311)
|
soft_content_rng = _axis_rng(parsed_seed_config, "content", seed, row_number + 311)
|
||||||
@@ -4143,6 +4282,18 @@ def build_insta_of_pair(
|
|||||||
soft_row["item_label"] = "Insta/OF softcore outfit"
|
soft_row["item_label"] = "Insta/OF softcore outfit"
|
||||||
soft_row["custom_item"] = "insta_of_softcore_outfit"
|
soft_row["custom_item"] = "insta_of_softcore_outfit"
|
||||||
soft_row["softcore_outfit_policy"] = "insta_of_safe_softcore"
|
soft_row["softcore_outfit_policy"] = "insta_of_safe_softcore"
|
||||||
|
soft_row["pov_character_labels"] = (
|
||||||
|
pov_character_labels
|
||||||
|
if options["softcore_cast"] == "same_as_hardcore"
|
||||||
|
else []
|
||||||
|
)
|
||||||
|
soft_row["pov_prompt_directive"] = _pov_prompt_directive(soft_row["pov_character_labels"])
|
||||||
|
if soft_row["pov_character_labels"]:
|
||||||
|
soft_row["source_composition"] = soft_row.get("source_composition") or soft_row.get("composition", "")
|
||||||
|
soft_row["composition"] = _pov_composition_prompt(
|
||||||
|
soft_row["source_composition"],
|
||||||
|
soft_row["pov_character_labels"],
|
||||||
|
)
|
||||||
hard_row = build_prompt(
|
hard_row = build_prompt(
|
||||||
category="Hardcore sexual poses",
|
category="Hardcore sexual poses",
|
||||||
subcategory=RANDOM_SUBCATEGORY,
|
subcategory=RANDOM_SUBCATEGORY,
|
||||||
@@ -4170,6 +4321,8 @@ def build_insta_of_pair(
|
|||||||
character_cast=character_cast or "",
|
character_cast=character_cast or "",
|
||||||
)
|
)
|
||||||
hard_row["hardcore_detail_density"] = options["hardcore_detail_density"]
|
hard_row["hardcore_detail_density"] = options["hardcore_detail_density"]
|
||||||
|
hard_row["pov_character_labels"] = pov_character_labels
|
||||||
|
hard_row["pov_prompt_directive"] = _pov_prompt_directive(pov_character_labels)
|
||||||
|
|
||||||
descriptor = _insta_of_descriptor(soft_row)
|
descriptor = _insta_of_descriptor(soft_row)
|
||||||
cast_descriptors = _insta_of_cast_descriptors(
|
cast_descriptors = _insta_of_cast_descriptors(
|
||||||
@@ -4197,6 +4350,7 @@ def build_insta_of_pair(
|
|||||||
row_number,
|
row_number,
|
||||||
hard_women_count if options["softcore_cast"] == "same_as_hardcore" else 1,
|
hard_women_count if options["softcore_cast"] == "same_as_hardcore" else 1,
|
||||||
hard_men_count if options["softcore_cast"] == "same_as_hardcore" else 0,
|
hard_men_count if options["softcore_cast"] == "same_as_hardcore" else 0,
|
||||||
|
pov_character_labels if options["softcore_cast"] == "same_as_hardcore" else [],
|
||||||
)
|
)
|
||||||
if options["softcore_cast"] != "same_as_hardcore":
|
if options["softcore_cast"] != "same_as_hardcore":
|
||||||
soft_partner_styling = {"outfits": [], "pose": ""}
|
soft_partner_styling = {"outfits": [], "pose": ""}
|
||||||
@@ -4223,9 +4377,16 @@ def build_insta_of_pair(
|
|||||||
else f"non-explicit teaser setup with {_insta_of_cast_phrase(hard_women_count, hard_men_count)}"
|
else f"non-explicit teaser setup with {_insta_of_cast_phrase(hard_women_count, hard_men_count)}"
|
||||||
)
|
)
|
||||||
soft_cast_presence = (
|
soft_cast_presence = (
|
||||||
"Place Woman A and the listed partners together in a non-explicit teaser pose with no sex act or genital contact. "
|
(
|
||||||
if options["softcore_cast"] == "same_as_hardcore"
|
"Frame Woman A from the POV participant's first-person camera in a non-explicit teaser setup; "
|
||||||
else "Keep the softcore version focused on Woman A alone. "
|
"keep the POV participant off-camera as the viewpoint and implied by camera perspective or foreground cues. "
|
||||||
|
)
|
||||||
|
if options["softcore_cast"] == "same_as_hardcore" and pov_character_labels
|
||||||
|
else (
|
||||||
|
"Place Woman A and the listed partners together in a non-explicit teaser pose with no sex act or genital contact. "
|
||||||
|
if options["softcore_cast"] == "same_as_hardcore"
|
||||||
|
else "Keep the softcore version focused on Woman A alone. "
|
||||||
|
)
|
||||||
)
|
)
|
||||||
soft_cast_styling_sentence = (
|
soft_cast_styling_sentence = (
|
||||||
f"Partner softcore styling: {soft_partner_outfit_text}. Cast pose: {soft_partner_styling['pose']}. "
|
f"Partner softcore styling: {soft_partner_outfit_text}. Cast pose: {soft_partner_styling['pose']}. "
|
||||||
@@ -4243,6 +4404,7 @@ def build_insta_of_pair(
|
|||||||
"balanced": "",
|
"balanced": "",
|
||||||
"dense": "Use dense but coherent motion, contact, and aftermath detail while keeping one readable body position. ",
|
"dense": "Use dense but coherent motion, contact, and aftermath detail while keeping one readable body position. ",
|
||||||
}[hard_detail_density]
|
}[hard_detail_density]
|
||||||
|
pov_directive = _pov_prompt_directive(pov_character_labels)
|
||||||
soft_descriptor_sentence = (
|
soft_descriptor_sentence = (
|
||||||
f"Cast descriptors: {soft_cast_descriptor_text}. "
|
f"Cast descriptors: {soft_cast_descriptor_text}. "
|
||||||
if options["softcore_cast"] == "same_as_hardcore"
|
if options["softcore_cast"] == "same_as_hardcore"
|
||||||
@@ -4266,7 +4428,8 @@ def build_insta_of_pair(
|
|||||||
f"Insta/OF hardcore mode: {platform_style}. "
|
f"Insta/OF hardcore mode: {platform_style}. "
|
||||||
f"Hardcore setup: {hard_level}. Cast: {hard_cast}. "
|
f"Hardcore setup: {hard_level}. Cast: {hard_cast}. "
|
||||||
f"Cast descriptors: {cast_descriptor_text}. "
|
f"Cast descriptors: {cast_descriptor_text}. "
|
||||||
"Keep Woman A visually central. "
|
f"{pov_directive + ' ' if pov_directive else ''}"
|
||||||
|
f"{'Keep Woman A visually central from the POV camera. ' if pov_character_labels else 'Keep Woman A visually central. '}"
|
||||||
f"{hard_clothing_state} "
|
f"{hard_clothing_state} "
|
||||||
f"Role graph: {hard_row['role_graph']} Sexual scene: {hard_row['item']}. "
|
f"Role graph: {hard_row['role_graph']} Sexual scene: {hard_row['item']}. "
|
||||||
f"Setting: {hard_scene}. "
|
f"Setting: {hard_scene}. "
|
||||||
@@ -4316,6 +4479,8 @@ def build_insta_of_pair(
|
|||||||
"options": options,
|
"options": options,
|
||||||
"shared_descriptor": descriptor,
|
"shared_descriptor": descriptor,
|
||||||
"shared_cast_descriptors": cast_descriptors,
|
"shared_cast_descriptors": cast_descriptors,
|
||||||
|
"pov_character_labels": pov_character_labels,
|
||||||
|
"pov_prompt_directive": pov_directive,
|
||||||
"softcore_partner_styling": soft_partner_styling,
|
"softcore_partner_styling": soft_partner_styling,
|
||||||
"hardcore_clothing_state": hard_clothing_state,
|
"hardcore_clothing_state": hard_clothing_state,
|
||||||
"hardcore_detail_density": hard_detail_density,
|
"hardcore_detail_density": hard_detail_density,
|
||||||
|
|||||||
Reference in New Issue
Block a user