from __future__ import annotations import json import re from typing import Any TRIGGER_CANDIDATES = ( "sxcpinup_coloredpencil", "sxcppnl7", ) PROMPT_FIELD_LABELS = ( "Ages", "Body types", "Cast", "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 _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 _clean_age(age: Any) -> str: return _clean(age) 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): 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 framing uses " + ", ".join(pieces) return "" 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 _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")) role_graph = _clean(row.get("role_graph")) parts = [ f"A consensual explicit adult scene with {subject}, all participants 21+ and visibly adult", f"The cast includes {cast}" if cast else "", role_graph, f"The sexual action is {item}" if item 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 = [ f"A {subject}" if not subject.lower().startswith(("a ", "an ")) else 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)" 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) 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 = _clean(row.get("softcore_camera_directive")) or _camera_phrase(soft) hard_camera = _clean(row.get("hardcore_camera_directive")) or _camera_phrase(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("_", " ") hard_cast = _clean(row.get("hardcore_women_count")) hard_men = _clean(row.get("hardcore_men_count")) hard_cast_text = _clean(hard.get("cast_summary")) or ( f"{hard_cast} adult women and {hard_men} adult men" if hard_cast or hard_men else "" ) 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 = soft.get("composition") if same_room and soft.get("composition") else hard.get("composition") soft_cast_descriptor_text = ( cast_descriptor_text if options.get("softcore_cast") == "same_as_hardcore" else f"Woman A / primary creator: {descriptor}" ) same_soft_cast = options.get("softcore_cast") == "same_as_hardcore" soft_cast_presence = ( "The same listed adult cast is present together in this softcore version in a non-explicit teaser pose, with no sex act or genital contact" if same_soft_cast else "The softcore version focuses on Woman A 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 = "" soft_parts = [ f"A visibly adult creator, {descriptor}", f"Shared cast descriptors: {soft_cast_descriptor_text}" if same_soft_cast and soft_cast_descriptor_text else "", f"Softcore primary creator descriptor: {soft_cast_descriptor_text}" if not same_soft_cast and soft_cast_descriptor_text else "", soft_cast_presence, f"Partner softcore styling: {partner_outfit_text}" if partner_outfit_text else "", f"The shared softcore cast pose is {partner_pose}" if partner_pose else "", f"shown in a {soft_level or 'softcore'} Insta/OF creator image", 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 = [ f"The same visibly adult creator, {descriptor}, is the visually central woman in a consensual explicit adult {hard_level or 'hardcore'} scene", f"{'Shared' if same_soft_cast else 'Hardcore'} cast descriptors: {cast_descriptor_text}" if cast_descriptor_text else "", f"all participants are 21+ and visibly adult; the cast includes {hard_cast_text}" if hard_cast_text else "all participants are 21+ and visibly adult", _clean(hard.get("role_graph")), f"The sexual action is {hard.get('item')}" if hard.get("item") else "", 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, }