674 lines
27 KiB
Python
674 lines
27 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
from typing import Any
|
|
|
|
|
|
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 _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 _prompt_cast_descriptors(text: str) -> str:
|
|
return _clean(text).replace("Woman A / primary creator:", "Woman A:")
|
|
|
|
|
|
def _cast_entries(text: str) -> list[tuple[str, str]]:
|
|
text = _prompt_cast_descriptors(text)
|
|
entries: list[tuple[str, str]] = []
|
|
for part in text.split(";"):
|
|
part = _clean(part)
|
|
match = re.match(r"^((?:Woman|Man) [A-Z]):\s*(.+)$", part)
|
|
if match:
|
|
entries.append((match.group(1), _clean(match.group(2))))
|
|
return entries
|
|
|
|
|
|
def _label_join(labels: list[str]) -> str:
|
|
labels = [_clean(label) for label in labels if _clean(label)]
|
|
if not labels:
|
|
return "the named adults"
|
|
if set(labels) == {"Woman A", "Man A"}:
|
|
return "the woman and man"
|
|
if len(labels) == 1:
|
|
if labels[0] == "Woman A":
|
|
return "the woman"
|
|
if labels[0] == "Man A":
|
|
return "the man"
|
|
return labels[0]
|
|
if len(labels) == 2:
|
|
return f"{labels[0]} and {labels[1]}"
|
|
return f"{', '.join(labels[:-1])}, and {labels[-1]}"
|
|
|
|
|
|
def _natural_label_text(text: Any, labels: list[str]) -> str:
|
|
text = _clean(text)
|
|
if not text:
|
|
return ""
|
|
if set(labels) == {"Woman A", "Man A"}:
|
|
text = re.sub(r"\bWoman A\b", "the woman", text)
|
|
text = re.sub(r"\bMan A\b", "the man", text)
|
|
elif labels == ["Woman A"]:
|
|
text = re.sub(r"\bWoman A\b", "the woman", text)
|
|
elif labels == ["Man A"]:
|
|
text = re.sub(r"\bMan A\b", "the man", text)
|
|
return text
|
|
|
|
|
|
def _cast_prose(text: str, central_label: str = "Woman A") -> tuple[str, list[str]]:
|
|
entries = _cast_entries(text)
|
|
if not entries:
|
|
return (f"{central_label} is {_clean(text)}" if _clean(text) else "", [])
|
|
labels = [label for label, _descriptor in entries]
|
|
if labels == ["Woman A"]:
|
|
return f"A {entries[0][1]}", labels
|
|
if labels == ["Man A"]:
|
|
return f"A {entries[0][1]}", labels
|
|
if set(labels) == {"Woman A", "Man A"} and len(labels) == 2:
|
|
by_label = {label: descriptor for label, descriptor in entries}
|
|
return f"A {by_label['Woman A']} alongside a {by_label['Man A']}", labels
|
|
sentences = []
|
|
for label, descriptor in entries:
|
|
sentences.append(f"{label} is {descriptor}.")
|
|
if central_label in labels:
|
|
sentences.append(f"{central_label} is the central subject.")
|
|
return " ".join(sentences), labels
|
|
|
|
|
|
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"\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{2,}", " ", text).strip(" ,")
|
|
return text
|
|
|
|
|
|
def _natural_clothing_state(text: Any) -> str:
|
|
text = _clean(text)
|
|
if not text:
|
|
return ""
|
|
text = re.sub(r"^Clothing state:\s*", "", text, flags=re.IGNORECASE)
|
|
match = re.match(
|
|
r"^(.*?)\b(?:softcore|teaser) outfit is (.*?)(?: for the (?:hardcore|sex) scene)?;\s*(?:softcore visual reference|teaser outfit detail):\s*(.*?)\.?$",
|
|
text,
|
|
flags=re.IGNORECASE,
|
|
)
|
|
if match:
|
|
owner = _natural_label_text(match.group(1).strip(" 's"), ["Woman A", "Man A"]).strip() or "the woman"
|
|
state = _clean(match.group(2)).lower()
|
|
outfit = _clean(match.group(3)).rstrip(".")
|
|
if "fully nude" in state:
|
|
return f"{owner.capitalize()} is fully nude, with the removed {outfit} visible nearby"
|
|
if "nude-adjacent" in state:
|
|
return f"{owner.capitalize()} is partly nude, with the {outfit} slipping off and no abstract clothing-reference wording"
|
|
if "partially removed" in state or "pushed aside" in state:
|
|
return f"{owner.capitalize()}'s {outfit} is pushed aside and partly removed, exposing the sexual contact clearly"
|
|
if "keeps" in state:
|
|
return f"{owner.capitalize()} keeps the {outfit} on while the sexual contact stays visible"
|
|
text = re.sub(r";\s*(?:softcore visual reference|teaser outfit detail):\s*", ". Visual clothing state: ", text, flags=re.IGNORECASE)
|
|
text = text.replace("softcore outfit", "outfit")
|
|
text = text.replace("teaser outfit", "outfit")
|
|
text = text.replace("hardcore scene", "sex scene")
|
|
return text
|
|
|
|
|
|
def _hardcore_action_sentence(role_graph: str, hard_item: str) -> str:
|
|
role_graph = _clean(role_graph).rstrip(".")
|
|
hard_item = _clean(hard_item).rstrip(".")
|
|
role_graph = re.sub(
|
|
r"\bthe man penetrates the woman while a toy adds a second point of contact\b",
|
|
"the man's penis thrusts into the woman while a toy adds a second penetration point",
|
|
role_graph,
|
|
flags=re.IGNORECASE,
|
|
)
|
|
role_graph = re.sub(
|
|
r"\bthe man penetrates the woman anally\b",
|
|
"the man's penis thrusts into the woman's ass",
|
|
role_graph,
|
|
flags=re.IGNORECASE,
|
|
)
|
|
role_graph = re.sub(
|
|
r"\bthe man penetrates the woman\b",
|
|
"the man's penis thrusts into the woman",
|
|
role_graph,
|
|
flags=re.IGNORECASE,
|
|
)
|
|
role_graph = re.sub(
|
|
r"\bthe woman and the man are in mutual oral contact with mouth-to-genital contact visible\b",
|
|
"the woman has the man's penis in her mouth while the man uses his mouth on her pussy",
|
|
role_graph,
|
|
flags=re.IGNORECASE,
|
|
)
|
|
role_graph = re.sub(
|
|
r"\bthe woman gives oral to the man\b",
|
|
"the woman takes the man's penis in her mouth",
|
|
role_graph,
|
|
flags=re.IGNORECASE,
|
|
)
|
|
if hard_item and role_graph:
|
|
return f"Explicit hardcore action: {role_graph}; {hard_item}"
|
|
if role_graph:
|
|
return f"Explicit hardcore action: {role_graph}"
|
|
if hard_item:
|
|
return f"Explicit hardcore action: {hard_item}"
|
|
return ""
|
|
|
|
|
|
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 _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 ""
|
|
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_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 ""
|
|
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 = _row_value(row, "expression", ("Facial expressions", "Facial expression"))
|
|
composition = re.sub(r"^vertical\s+", "", _row_value(row, "composition", ("Composition",)), flags=re.IGNORECASE)
|
|
camera = _camera_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")
|
|
)
|
|
cast_prose, cast_labels = _cast_prose(cast_descriptor_text)
|
|
if not cast_labels and women_count == 1 and men_count == 1:
|
|
cast_labels = ["Woman A", "Man A"]
|
|
role_graph = _natural_label_text(_clean(row.get("role_graph")), cast_labels)
|
|
item = _natural_label_text(item, cast_labels)
|
|
action = _hardcore_action_sentence(role_graph, item)
|
|
parts = [
|
|
action,
|
|
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 "",
|
|
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(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 "",
|
|
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 "",
|
|
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 "",
|
|
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_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 = hard.get("composition")
|
|
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)
|
|
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_item = _natural_label_text(hard_item, hard_labels)
|
|
hard_role_graph = _natural_label_text(hard_role_graph, hard_labels)
|
|
hard_action = _hardcore_action_sentence(hard_role_graph, hard_item)
|
|
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"
|
|
)
|
|
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 = _natural_label_text(partner_outfit_text, soft_labels)
|
|
|
|
soft_parts = [
|
|
soft_cast_prose,
|
|
soft_cast_presence,
|
|
partner_outfit_text,
|
|
partner_pose,
|
|
f"wearing {soft.get('item')}" if soft.get("item") else "",
|
|
f"{soft.get('pose')}" if soft.get("pose") else "",
|
|
f"with {soft.get('expression')}" if soft.get("expression") else "",
|
|
f"in {soft.get('scene_text')}" if soft.get("scene_text") else "",
|
|
f"framed as {soft.get('composition')}" if soft.get("composition") else "",
|
|
soft_camera,
|
|
soft_style if detail_level != "concise" else "",
|
|
]
|
|
hard_parts = [
|
|
hard_action,
|
|
_natural_clothing_state(row.get("hardcore_clothing_state")),
|
|
hard_cast_prose,
|
|
f"set in {hard_scene}" if hard_scene else "",
|
|
f"with {hard.get('expression')}" if hard.get("expression") else "",
|
|
f"framed as {hard_composition}" if hard_composition else "",
|
|
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)
|
|
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
|
|
if extra_positive.strip():
|
|
selected = f"{selected.rstrip()} {extra_positive.strip()}"
|
|
soft_prompt = f"{soft_prompt.rstrip()} {extra_positive.strip()}"
|
|
hard_prompt = f"{hard_prompt.rstrip()} {extra_positive.strip()}"
|
|
negative = _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": _combine_negative(soft_negative, extra_negative),
|
|
"hardcore_negative_prompt": _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()}"
|
|
negative = _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,
|
|
}
|