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

820 lines
34 KiB
Python

from __future__ import annotations
import json
import re
from typing import Any
try:
from .krea_action_context import (
is_close_foreplay_text as _is_close_foreplay_text,
is_outercourse_text as _is_outercourse_text,
normalize_hardcore_detail_density as _normalize_hardcore_detail_density,
)
from .hardcore_text_cleanup import (
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
sanitize_hardcore_environment_anchors as _sanitize_hardcore_environment_anchors,
)
from .krea_cast import (
cast_prose as _cast_prose,
label_join as _label_join,
lowercase_for_inline_join as _lowercase_for_inline_join,
natural_label_text as _natural_label_text,
prompt_cast_descriptors as _prompt_cast_descriptors,
)
from .krea_clothing import natural_clothing_state as _natural_clothing_state
from .krea_action_positions import action_position_phrase as _action_position_phrase
from .krea_actions import hardcore_action_sentence as _hardcore_action_sentence
from .krea_pov import (
filter_pov_labeled_clauses as _filter_pov_labeled_clauses,
merge_labels as _merge_labels,
pov_camera_phrase as _pov_camera_phrase,
pov_composition_text as _pov_composition_text,
pov_labels_from_value as _pov_labels_from_value,
)
from .krea_pov_actions import pov_action_phrase as _pov_action_phrase
from .prompt_hygiene import sanitize_negative_text, sanitize_prose_text
except ImportError: # Allows local smoke tests with `python -c`.
from krea_action_context import (
is_close_foreplay_text as _is_close_foreplay_text,
is_outercourse_text as _is_outercourse_text,
normalize_hardcore_detail_density as _normalize_hardcore_detail_density,
)
from hardcore_text_cleanup import (
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
sanitize_hardcore_environment_anchors as _sanitize_hardcore_environment_anchors,
)
from krea_cast import (
cast_prose as _cast_prose,
label_join as _label_join,
lowercase_for_inline_join as _lowercase_for_inline_join,
natural_label_text as _natural_label_text,
prompt_cast_descriptors as _prompt_cast_descriptors,
)
from krea_clothing import natural_clothing_state as _natural_clothing_state
from krea_action_positions import action_position_phrase as _action_position_phrase
from krea_actions import hardcore_action_sentence as _hardcore_action_sentence
from krea_pov import (
filter_pov_labeled_clauses as _filter_pov_labeled_clauses,
merge_labels as _merge_labels,
pov_camera_phrase as _pov_camera_phrase,
pov_composition_text as _pov_composition_text,
pov_labels_from_value as _pov_labels_from_value,
)
from krea_pov_actions import pov_action_phrase as _pov_action_phrase
from prompt_hygiene import sanitize_negative_text, sanitize_prose_text
TRIGGER_CANDIDATES = (
"sxcpinup_coloredpencil",
"sxcppnl7",
)
PROMPT_FIELD_LABELS = (
"Ages",
"Body types",
"Cast",
"Cast descriptors",
"Characters",
"Scene",
"Setting",
"Pose",
"Sexual pose",
"Facial expression",
"Facial expressions",
"Clothing",
"Erotic outfit",
"Prop/detail",
"Composition",
"Role graph",
"Use",
"Avoid",
)
def _clean(value: Any) -> str:
text = "" if value is None else str(value)
text = text.replace("\n", " ")
text = re.sub(r"\s+", " ", text).strip()
text = re.sub(r"\s+([,.;:])", r"\1", text)
return text
def _is_false(value: Any) -> bool:
if isinstance(value, bool):
return value is False
if isinstance(value, str):
return value.strip().lower() in ("false", "0", "no", "off")
return False
def _expression_disabled(row: dict[str, Any]) -> bool:
return bool(row.get("expression_disabled")) or _is_false(row.get("expression_enabled", True))
def _sentence(text: str) -> str:
text = _clean(text).strip(" ,;")
if not text:
return ""
text = text[:1].upper() + text[1:]
if text[-1] not in ".!?":
text += "."
return text
def _paragraph(parts: list[str]) -> str:
return " ".join(part for part in (_sentence(part) for part in parts) if part)
def _with_indefinite_article(text: str) -> str:
text = _clean(text)
if not text or text.lower().startswith(("a ", "an ")):
return text
article = "an" if text[:1].lower() in "aeiou" else "a"
return f"{article} {text}"
def _maybe_json(text: str) -> dict[str, Any] | None:
text = _clean(text)
if not text.startswith("{"):
return None
try:
value = json.loads(text)
except json.JSONDecodeError:
return None
return value if isinstance(value, dict) else None
def _row_from_inputs(source_text: str, metadata_json: str, input_hint: str) -> tuple[dict[str, Any] | None, str]:
candidates: list[tuple[str, str]] = []
if input_hint in ("auto", "metadata_json"):
candidates.append((metadata_json, "metadata_json"))
candidates.append((source_text, "source_json"))
for text, method in candidates:
row = _maybe_json(text)
if row is not None:
return row, method
return None, "text"
def _strip_trigger(text: str, preserve_trigger: bool) -> str:
text = _clean(text)
if preserve_trigger:
return text
for trigger in TRIGGER_CANDIDATES:
if text.lower().startswith(trigger.lower() + ","):
return text[len(trigger) + 1 :].strip(" ,")
if text.lower().startswith(trigger.lower() + "."):
return text[len(trigger) + 1 :].strip(" ,")
return text
def _split_avoid(text: str) -> tuple[str, str]:
match = re.search(r"\bAvoid:\s*(.*)$", text)
if not match:
return text, ""
return text[: match.start()].strip(" ."), match.group(1).strip(" .")
def _prompt_field(text: str, label: str) -> str:
text = _clean(text)
if not text:
return ""
labels = "|".join(re.escape(name) for name in PROMPT_FIELD_LABELS)
pattern = rf"{re.escape(label)}:\s*(.*?)(?=\. (?:{labels}):|\. Use\b|\. Avoid\b|$)"
match = re.search(pattern, text)
if not match:
return ""
return _clean(match.group(1)).rstrip(".")
def _row_value(row: dict[str, Any], key: str, labels: tuple[str, ...] = ()) -> str:
value = _clean(row.get(key, ""))
if value:
return value
prompt = _clean(row.get("prompt", ""))
for label in labels:
value = _prompt_field(prompt, label)
if value:
return value
return ""
def _body_phrase(body: Any, figure_note: Any = "") -> str:
body = _clean(body)
figure_note = _clean(figure_note)
if not body:
return figure_note
if not figure_note:
return f"{body} figure"
if "figure" in figure_note.lower():
return f"{body} build and {figure_note}"
return f"{body} figure with {figure_note}"
def _single_caption_front(row: dict[str, Any]) -> dict[str, str]:
caption = _strip_trigger(_clean(row.get("caption")), False)
if not caption:
return {}
subject = _clean(row.get("primary_subject"))
age = _clean(row.get("age_band") or row.get("age"))
body = _clean(row.get("body_phrase"))
if not body:
body_type = _clean(row.get("body_type") or row.get("body"))
figure = _clean(row.get("figure"))
body = _body_phrase(body_type, figure)
front = f"{subject}, {age}, {body}, "
if subject in ("woman", "man") and age and body and caption.startswith(front):
try:
skin, hair, eyes, _rest = caption[len(front) :].split(", ", 3)
except ValueError:
return {}
return {"body_phrase": body, "skin": skin, "hair": hair, "eyes": eyes}
return {}
def _combine_negative(*parts: str) -> str:
cleaned = [_clean(part).strip(" ,.") for part in parts if _clean(part).strip(" ,.")]
return ", ".join(cleaned)
def _sanitize_scene_text_for_cast(text: Any, labels: list[str]) -> str:
text = _clean(text)
if not text:
return ""
if len(labels) < 3:
text = re.sub(r"\s*(?:while|as)\s+another partner watches\b", "", text, flags=re.IGNORECASE)
text = re.sub(r"\banother partner watches\b", "", text, flags=re.IGNORECASE)
text = re.sub(r",?\s*\bone partner held between two bodies\b", "", text, flags=re.IGNORECASE)
text = re.sub(r",?\s*\bthree bodies locked together\b", "", text, flags=re.IGNORECASE)
text = re.sub(r",?\s*\bthree bodies\b", "", text, flags=re.IGNORECASE)
text = re.sub(r"\bwith\s*,\s*", "with ", text, flags=re.IGNORECASE)
text = re.sub(r"\bwhile blowjob\b", "during a blowjob", text, flags=re.IGNORECASE)
text = re.sub(r"\bfeaturing blowjob\b", "featuring a blowjob", text, flags=re.IGNORECASE)
text = re.sub(r"\s+,", ",", text)
text = re.sub(r",\s*,", ",", text)
text = re.sub(r"\s{2,}", " ", text).strip(" ,")
return text
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)
if "first-person underview" in action_lower or "straddling the viewer's face" in action_lower:
if any(token in composition_lower for token in ("mirror-reflected", "oral scene", "face and body visible")):
return (
f"{prefix} close first-person underview with the woman's thighs framing the camera and the oral contact centered"
)
if "pov viewer" in action_lower and any(
token in action_lower
for token in ("ass raised", "seen from behind", "on all fours", "bent forward", "face-down")
):
return (
f"{prefix} first-person rear-view frame looking down at the woman's raised ass, with foreground hands and rear-entry contact readable"
)
oral_pose_tokens = (
"kneeling oral",
"side-lying oral",
"spread-leg oral",
"standing oral",
"edge-of-bed oral",
"face-sitting",
"sixty-nine",
"reclining cunnilingus",
"straddled oral",
"chair oral",
)
if "oral" in action_lower:
composition_oral_tokens = [token for token in oral_pose_tokens if token in composition_lower]
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 ""
if _is_outercourse_text(action_lower):
return f"{prefix} {composition}"
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 _is_close_foreplay_text(action_lower) and close_or_aftermath:
return f"{prefix} {composition}, in one continuous first-person close-body frame"
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}"
def _clean_age(age: Any) -> str:
return _clean(age)
def _age_detail_phrase(age: Any) -> str:
text = _clean(age)
text = re.sub(r"\s+adults?$", "", text).strip()
return text.replace("-year-old", " years old")
def _age_subject(row: dict[str, Any], fallback_subject: str = "adult person") -> str:
subject = _clean(row.get("subject_phrase") or row.get("primary_subject") or row.get("subject") or fallback_subject)
age = _clean_age(row.get("age_band") or row.get("age"))
if row.get("subject_type") == "configured_cast":
return _clean(row.get("subject_phrase") or subject)
if subject in ("woman", "man"):
if age:
return f"{age} {subject}" if "adult" in age.lower() else f"{age} adult {subject}"
return f"adult {subject}"
if age and "adult" not in subject.lower():
return f"{age} {subject}"
return subject or fallback_subject
def _appearance_phrase(row: dict[str, Any]) -> str:
front = _single_caption_front(row)
parts = [
_row_value(row, "body_phrase") or front.get("body_phrase"),
_row_value(row, "skin") or front.get("skin"),
_row_value(row, "hair") or front.get("hair"),
_row_value(row, "eyes") or front.get("eyes"),
]
return ", ".join(_clean(part) for part in parts if _clean(part))
def _expression_phrase(expression: Any) -> str:
expression = _clean(expression)
if not expression:
return ""
if ";" in expression or re.search(
r"\b(?:Woman|Man) [A-Z] has\b|\bthe (?:woman|man) has\b",
expression,
flags=re.IGNORECASE,
):
return f"Expressions: {expression}"
return f"with {expression}"
def _camera_phrase(row: dict[str, Any]) -> str:
directive = _clean(row.get("camera_directive"))
if directive:
return directive
config = row.get("camera_config")
if isinstance(config, dict):
detail = _clean(config.get("camera_detail"))
if detail == "off" or _clean(config.get("camera_mode")) == "disabled":
return ""
custom = _clean(config.get("custom_camera_prompt"))
if custom:
base = _clean(config.get("camera_mode")).replace("_", " ")
pieces = [piece for piece in (base, custom) if piece and piece != "standard"]
return "Camera: " + ", ".join(pieces)
mode = _clean(config.get("camera_mode")).replace("_", " ")
shot = _clean(config.get("shot_size")).replace("_", " ")
angle = _clean(config.get("angle")).replace("_", " ")
pieces = [piece for piece in (mode, shot, angle) if piece and piece != "auto" and piece != "standard"]
if pieces:
return "Camera: " + ", ".join(pieces)
return ""
def _camera_scene_phrase(row: dict[str, Any]) -> str:
return _clean(row.get("camera_scene_directive"))
def _camera_phrase_from_config(config: Any) -> str:
if not isinstance(config, dict):
return ""
detail = _clean(config.get("camera_detail"))
if detail == "off" or _clean(config.get("camera_mode")) == "disabled":
return ""
custom = _clean(config.get("custom_camera_prompt"))
if custom:
base = _clean(config.get("camera_mode")).replace("_", " ")
pieces = [piece for piece in (base, custom) if piece and piece != "standard"]
return "Camera: " + ", ".join(pieces)
values = [
_clean(config.get("camera_mode")).replace("_", " "),
_clean(config.get("shot_size")).replace("_", " "),
_clean(config.get("angle")).replace("_", " "),
_clean(config.get("lens")).replace("_", " "),
_clean(config.get("distance")).replace("_", " "),
_clean(config.get("orientation")).replace("_", " "),
_clean(config.get("phone_visibility")).replace("_", " "),
]
pieces = [value for value in values if value and value not in ("auto", "standard")]
if not pieces:
return ""
return "Camera: " + ", ".join(pieces)
def _pair_camera_phrase(directive: Any, config: Any, row: dict[str, Any]) -> str:
directive_text = _clean(directive)
if directive_text:
return directive_text
if isinstance(config, dict) and (
_clean(config.get("camera_detail")) == "off" or _clean(config.get("camera_mode")) == "disabled"
):
return ""
return _camera_phrase_from_config(config) or _camera_phrase(row)
def _style_phrase(row: dict[str, Any], style_mode: str) -> str:
if style_mode == "minimal":
return ""
if style_mode == "photographic":
return "realistic creator-shot photography with natural lighting, tactile skin and fabric detail, and clean social-media composition"
style = _clean(row.get("style"))
suffix = _clean(row.get("positive_suffix")) or _prompt_field(_clean(row.get("prompt")), "Use")
if style and suffix:
return f"{style}; {suffix}"
return style or suffix
def _couple_clothing_phrase(item: str) -> str:
item = _clean(item)
lower = item.lower()
partner_text = re.sub(r"\bPartner ([AB]) wears\b", r"Partner \1 wearing", item)
partner_text = re.sub(r"\bPartner ([AB]) has\b", r"Partner \1 with", partner_text)
if lower.startswith("partner a "):
return f"The outfits show {partner_text}"
if lower.startswith(("two ", "paired ", "coordinated ")):
return f"The outfits are {partner_text}"
return f"The couple wears {item}"
def _normal_row_to_krea(row: dict[str, Any], detail_level: str, style_mode: str) -> tuple[str, str]:
subject_type = _clean(row.get("subject_type"))
primary = _clean(row.get("primary_subject"))
item = _row_value(row, "item", ("Sexual pose", "Erotic outfit", "Clothing")) or _clean(row.get("custom_item"))
item = re.sub(r",?\s*(fashion editorial|resort) styling$", "", item, flags=re.IGNORECASE)
scene = _row_value(row, "scene_text", ("Setting", "Scene")) or _clean(row.get("scene"))
pose = _row_value(row, "pose", ("Sexual pose", "Pose"))
expression = ""
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)
camera_scene = _camera_scene_phrase(row)
style = _style_phrase(row, style_mode)
if subject_type == "configured_cast" or _clean(row.get("cast_summary")):
subject = _clean(row.get("subject_phrase") or primary or "adult sexual scene")
cast = _clean(row.get("cast_summary"))
try:
women_count = int(row.get("women_count") or 0)
men_count = int(row.get("men_count") or 0)
except (TypeError, ValueError):
women_count = men_count = 0
cast_descriptor_text = (
_clean(row.get("cast_descriptor_text"))
or _prompt_field(_clean(row.get("prompt")), "Characters")
or _prompt_field(_clean(row.get("prompt")), "Cast descriptors")
)
pov_labels = _pov_labels_from_value(row.get("pov_character_labels"))
if pov_labels:
camera = ""
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)
composition = _sanitize_hardcore_environment_anchors(composition)
source_composition = _sanitize_hardcore_environment_anchors(source_composition)
role_graph = _sanitize_scene_text_for_cast(
_sanitize_hardcore_environment_anchors(row.get("source_role_graph") or row.get("role_graph")),
cast_labels,
)
item = _sanitize_scene_text_for_cast(_sanitize_hardcore_environment_anchors(item), cast_labels)
role_graph = _natural_label_text(role_graph, cast_labels)
item = _natural_label_text(item, cast_labels)
axis_values = _sanitize_hardcore_axis_values(row.get("item_axis_values"))
detail_density = _normalize_hardcore_detail_density(row.get("hardcore_detail_density"))
action = _hardcore_action_sentence(
role_graph,
item,
source_composition,
axis_values,
detail_density,
row.get("action_family"),
)
action = _pov_action_phrase(action, pov_labels, role_graph, item, source_composition, axis_values, detail_density)
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 "",
camera_scene,
_expression_phrase(expression),
_composition_phrase(output_composition, action, "The image is framed as", detail_density),
camera,
style if detail_level != "concise" else "",
]
return _paragraph(parts), "metadata(configured_cast)"
if primary in ("woman", "man") or subject_type in ("woman", "man", "single_any"):
subject = _age_subject(row, "adult woman")
appearance = _appearance_phrase(row)
parts = [
_with_indefinite_article(subject),
f"with {appearance}" if appearance else "",
f"wearing {item}" if item else "",
f"{pose}" if pose else "",
f"with {expression}" if expression else "",
f"in {scene}" if scene else "",
camera_scene,
f"framed as {composition}" if composition else "",
camera,
style if detail_level != "concise" else "",
]
return _paragraph([", ".join(part for part in parts[:6] if part), *parts[6:]]), "metadata(single)"
if subject_type == "couple" or primary in ("two women", "two men", "a woman and a man"):
subject = _clean(row.get("subject_phrase") or primary or "adult couple")
if subject == "woman and man":
subject = "a woman and a man"
ages = _age_detail_phrase(_row_value(row, "age", ("Ages",)) or row.get("age_band"))
body = _row_value(row, "body", ("Body types",)) or _clean(row.get("body_type"))
parts = [
f"An adult couple: {subject}, all visibly adult",
f"Age detail: {ages}" if ages else "",
f"Body types: {body}" if body else "",
_couple_clothing_phrase(item) if item else "",
f"The pose is {pose}" if pose else "",
f"The setting is {scene}" if scene else "",
camera_scene,
f"Facial expressions are {expression}" if expression else "",
f"The image is framed as {composition}" if composition else "",
camera,
style if detail_level != "concise" else "",
]
return _paragraph(parts), "metadata(couple)"
subject = _age_subject(row, primary or "adult scene")
parts = [
f"{subject}",
f"featuring {item}" if item else "",
f"in {scene}" if scene else "",
camera_scene,
f"with {expression}" if expression else "",
f"framed as {composition}" if composition else "",
camera,
style if detail_level != "concise" else "",
]
return _paragraph(parts), "metadata(generic)"
def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str) -> tuple[str, str, str, str]:
descriptor = _clean(row.get("shared_descriptor"))
cast_descriptors = row.get("shared_cast_descriptors")
if isinstance(cast_descriptors, list):
cast_descriptor_text = "; ".join(_clean(item) for item in cast_descriptors if _clean(item))
else:
cast_descriptor_text = _clean(cast_descriptors)
cast_descriptor_text = _prompt_cast_descriptors(cast_descriptor_text)
soft = row.get("softcore_row") if isinstance(row.get("softcore_row"), dict) else {}
hard = row.get("hardcore_row") if isinstance(row.get("hardcore_row"), dict) else {}
soft_camera = _pair_camera_phrase(row.get("softcore_camera_directive"), row.get("softcore_camera_config"), soft)
hard_camera = _pair_camera_phrase(row.get("hardcore_camera_directive"), row.get("hardcore_camera_config"), hard)
soft_camera_scene = _camera_scene_phrase(soft) or _clean(row.get("softcore_camera_scene_directive"))
hard_camera_scene = _camera_scene_phrase(hard) or _clean(row.get("hardcore_camera_scene_directive"))
soft_style = _style_phrase(soft, style_mode)
hard_style = _style_phrase(hard, style_mode)
options = row.get("options") if isinstance(row.get("options"), dict) else {}
soft_level = _clean(options.get("softcore_level")).replace("_", " ")
hard_level = _clean(options.get("hardcore_level")).replace("_", " ")
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 = _sanitize_hardcore_environment_anchors(hard.get("composition"))
hard_source_composition = _sanitize_hardcore_environment_anchors(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")),
)
if pov_labels:
hard_camera = ""
if options.get("softcore_cast") == "same_as_hardcore":
soft_camera = ""
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,
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(_sanitize_hardcore_environment_anchors(hard.get("item")), hard_labels)
hard_role_graph = _sanitize_scene_text_for_cast(
_sanitize_hardcore_environment_anchors(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 = _sanitize_hardcore_axis_values(hard.get("item_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_source_composition,
hard_axis_values,
hard_detail_density,
hard.get("action_family") or row.get("action_family"),
)
hard_action = _pov_action_phrase(
hard_action,
pov_labels,
hard_role_graph,
hard_item,
hard_source_composition,
hard_axis_values,
hard_detail_density,
)
hard_output_composition = _pov_composition_text(hard_composition, pov_labels)
same_soft_cast = options.get("softcore_cast") == "same_as_hardcore"
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 soft creator-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)} share the frame in a soft creator-teaser pose"
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")
partner_outfit_text = "; ".join(_clean(item) for item in outfits if _clean(item)) if isinstance(outfits, list) else ""
partner_pose = _clean(partner_styling.get("pose"))
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_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_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,
)
soft_item = _clean(soft.get("item"))
soft_item_label = _clean(soft.get("softcore_item_prompt_label"))
soft_item_phrase = ""
if soft_item:
soft_item_phrase = f"body exposure: {soft_item}" if soft_item_label == "Body exposure" else f"wearing {soft_item}"
soft_parts = [
soft_cast_prose,
soft_cast_presence,
partner_outfit_text,
partner_pose,
_pov_camera_phrase(pov_labels, softcore=True) if same_soft_cast else "",
soft_item_phrase,
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 "",
soft_camera_scene,
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_label_text(
_filter_pov_labeled_clauses(_natural_clothing_state(row.get("hardcore_clothing_state"), hard_action), pov_labels),
hard_labels,
),
hard_cast_prose,
f"set in {hard_scene}" if hard_scene else "",
hard_camera_scene,
_expression_phrase(hard_expression),
_composition_phrase(hard_output_composition, hard_action, detail_density=hard_detail_density),
hard_camera,
hard_style if detail_level != "concise" else "",
]
return (
_paragraph(soft_parts),
_combine_negative(row.get("softcore_negative_prompt")),
_paragraph(hard_parts),
_combine_negative(row.get("hardcore_negative_prompt")),
)
def _fallback_text_to_krea(
source_text: str,
preserve_trigger: bool,
detail_level: str,
style_mode: str,
) -> tuple[str, str, str]:
positive, negative = _split_avoid(_strip_trigger(source_text, preserve_trigger))
positive = re.sub(r"\b(?:Scene|Setting):", "The setting is", positive)
positive = re.sub(r"\b(?:Pose|Sexual pose):", "The pose is", positive)
positive = re.sub(r"\bFacial expressions?:", "The facial expression is", positive)
positive = re.sub(r"\bComposition:", "The composition is", positive)
positive = re.sub(r"\bRole graph:", "The role choreography is", positive)
positive = re.sub(r"\bUse\b", "Use", positive)
positive = _clean(positive)
return _paragraph([positive]), negative, "text(fallback)"
def format_krea2_prompt(
source_text: str,
metadata_json: str = "",
negative_prompt: str = "",
input_hint: str = "auto",
target: str = "auto",
detail_level: str = "balanced",
style_mode: str = "preserve",
preserve_trigger: bool = False,
extra_positive: str = "",
extra_negative: str = "",
) -> dict[str, str]:
detail_level = detail_level if detail_level in ("concise", "balanced", "dense") else "balanced"
style_mode = style_mode if style_mode in ("preserve", "photographic", "minimal") else "preserve"
target = target if target in ("auto", "single", "softcore", "hardcore") else "auto"
row, method = _row_from_inputs(source_text, metadata_json, input_hint)
extracted_negative = ""
if row and row.get("mode") == "Insta/OF":
soft_prompt, soft_negative, hard_prompt, hard_negative = _insta_pair_to_krea(row, detail_level, style_mode)
if extra_positive.strip():
soft_prompt = f"{soft_prompt.rstrip()} {extra_positive.strip()}"
hard_prompt = f"{hard_prompt.rstrip()} {extra_positive.strip()}"
soft_prompt = sanitize_prose_text(soft_prompt, triggers=TRIGGER_CANDIDATES)
hard_prompt = sanitize_prose_text(hard_prompt, triggers=TRIGGER_CANDIDATES)
selected = hard_prompt if target == "hardcore" else soft_prompt if target == "softcore" else soft_prompt
selected_negative = hard_negative if target == "hardcore" else soft_negative
negative = sanitize_negative_text(_combine_negative(selected_negative, negative_prompt, extra_negative))
return {
"krea_prompt": selected,
"negative_prompt": negative,
"krea_softcore_prompt": soft_prompt,
"krea_hardcore_prompt": hard_prompt,
"softcore_negative_prompt": sanitize_negative_text(_combine_negative(soft_negative, extra_negative)),
"hardcore_negative_prompt": sanitize_negative_text(_combine_negative(hard_negative, extra_negative)),
"method": f"{method}:krea2(insta_of_pair)",
}
if row:
prompt, kind = _normal_row_to_krea(row, detail_level, style_mode)
extracted_negative = _clean(row.get("negative_prompt"))
method = f"{method}:krea2({kind})"
else:
prompt, extracted_negative, method = _fallback_text_to_krea(source_text, preserve_trigger, detail_level, style_mode)
if extra_positive.strip():
prompt = f"{prompt.rstrip()} {extra_positive.strip()}"
prompt = sanitize_prose_text(prompt, triggers=TRIGGER_CANDIDATES)
negative = sanitize_negative_text(_combine_negative(extracted_negative, negative_prompt, extra_negative))
return {
"krea_prompt": prompt,
"negative_prompt": negative,
"krea_softcore_prompt": "",
"krea_hardcore_prompt": "",
"softcore_negative_prompt": "",
"hardcore_negative_prompt": "",
"method": method,
}