Add POV participant mode
This commit is contained in:
+182
-20
@@ -230,8 +230,16 @@ def _natural_label_text(text: Any, labels: list[str]) -> str:
|
||||
return text
|
||||
|
||||
|
||||
def _cast_prose(text: str, central_label: str = "Woman A") -> tuple[str, list[str]]:
|
||||
entries = _cast_entries(text)
|
||||
def _cast_prose(
|
||||
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:
|
||||
return (f"{central_label} is {_clean(text)}" if _clean(text) else "", [])
|
||||
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
|
||||
|
||||
|
||||
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:
|
||||
text = _clean(text)
|
||||
if not text:
|
||||
@@ -633,6 +722,34 @@ def _hardcore_pose_arrangement(anchor: str, role_graph: str, hard_item: str, com
|
||||
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:
|
||||
text = _clean(hard_item).rstrip(".")
|
||||
if not text:
|
||||
@@ -1071,7 +1188,7 @@ def _hardcore_action_sentence(
|
||||
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:
|
||||
if arrangement and anchor_phrase and not _arrangement_duplicates_role(arrangement, role_graph):
|
||||
anchor_phrase = f"{anchor_phrase} {arrangement}"
|
||||
if role_graph and anchor_phrase:
|
||||
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):
|
||||
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)
|
||||
source_composition = re.sub(
|
||||
r"^vertical\s+",
|
||||
"",
|
||||
_clean(row.get("source_composition")) or composition,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
camera = _camera_phrase(row)
|
||||
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")), "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:
|
||||
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)
|
||||
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)
|
||||
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 {}
|
||||
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 = [
|
||||
action,
|
||||
_pov_camera_phrase(pov_labels),
|
||||
cast_prose,
|
||||
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 setting is {scene}" if scene else "",
|
||||
_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,
|
||||
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"
|
||||
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_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 = (
|
||||
cast_descriptor_text
|
||||
if options.get("softcore_cast") == "same_as_hardcore"
|
||||
else f"Woman A: {descriptor}"
|
||||
)
|
||||
soft_cast_prose, soft_labels = _cast_prose(soft_cast_descriptor_text)
|
||||
hard_cast_prose, hard_labels = _cast_prose(cast_descriptor_text)
|
||||
soft_cast_prose, soft_labels = _cast_prose(
|
||||
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_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_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 {}
|
||||
@@ -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_role_graph,
|
||||
hard_item,
|
||||
hard_composition,
|
||||
hard_source_composition,
|
||||
hard_axis_values,
|
||||
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"
|
||||
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"
|
||||
)
|
||||
soft_output_composition = _pov_composition_text(soft.get("composition"), pov_labels if same_soft_cast else [])
|
||||
if same_soft_cast and pov_labels:
|
||||
soft_cast_presence = (
|
||||
"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")
|
||||
if isinstance(partner_styling, dict):
|
||||
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:
|
||||
partner_outfit_text = ""
|
||||
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)
|
||||
|
||||
soft_expression = ""
|
||||
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")),
|
||||
pov_labels,
|
||||
)
|
||||
soft_expression = _natural_label_text(
|
||||
soft_expression_source,
|
||||
soft_labels,
|
||||
)
|
||||
hard_expression = ""
|
||||
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")),
|
||||
pov_labels,
|
||||
)
|
||||
hard_expression = _natural_label_text(
|
||||
hard_expression_source,
|
||||
hard_labels,
|
||||
)
|
||||
|
||||
@@ -1423,21 +1583,23 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
|
||||
soft_cast_presence,
|
||||
partner_outfit_text,
|
||||
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"{soft.get('pose')}" if soft.get("pose") else "",
|
||||
_expression_phrase(soft_expression),
|
||||
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_style if detail_level != "concise" else "",
|
||||
]
|
||||
hard_parts = [
|
||||
hard_action,
|
||||
_pov_camera_phrase(pov_labels),
|
||||
_natural_clothing_state(row.get("hardcore_clothing_state")),
|
||||
hard_cast_prose,
|
||||
f"set in {hard_scene}" if hard_scene else "",
|
||||
_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_style if detail_level != "concise" else "",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user