Add prompt control and filter options

This commit is contained in:
2026-06-24 14:32:54 +02:00
parent 2b64499687
commit cb35e1881f
5 changed files with 592 additions and 99 deletions
+282 -27
View File
@@ -49,6 +49,35 @@ SEED_AXIS_ALIASES = {
"composition": ("composition_seed", "camera_seed", "composition"),
}
SEED_LOCK_AXES = (
"category",
"subcategory",
"content",
"person",
"scene",
"pose",
"role",
"expression",
"composition",
)
ETHNICITY_FILTER_CHOICES = [
"any",
"european",
"mediterranean_mena",
"latina",
"east_asian",
"southeast_asian",
"south_asian",
"black_african",
"indigenous",
"mixed",
"asian",
"white_asian",
]
CAMERA_DETAIL_CHOICES = ["off", "compact", "full"]
GENERIC_POSITIVE_SUFFIX = (
"Use crisp clean comic linework, detailed hatching, soft blended shading, "
"pastel skin tones, muted blues and pinks, warm sensual lighting, and tactile textured paper."
@@ -79,6 +108,7 @@ LAYOUT_TEMPLATE = (
)
CAMERA_MODE_PROMPTS = {
"disabled": "",
"standard": "",
"handheld_selfie": (
"Camera mode: handheld smartphone selfie, close arm-length framing, visible creator-shot perspective, "
@@ -109,6 +139,47 @@ CAMERA_MODE_PROMPTS = {
),
}
CAMERA_COMPACT_LABELS = {
"disabled": "",
"standard": "",
"handheld_selfie": "handheld smartphone selfie",
"mirror_selfie": "mirror selfie",
"phone_tripod": "phone tripod / ring-light setup",
"creator_pov": "creator-held POV",
"bed_selfie": "bed selfie",
"bathroom_mirror": "bathroom mirror selfie",
"phone_flash": "phone-flash selfie",
"action_cam": "handheld action-camera view",
"full_body": "full body",
"three_quarter": "three-quarter body",
"waist_up": "waist-up",
"close_up": "close-up",
"extreme_close_up": "extreme close-up",
"eye_level": "eye-level",
"high_angle": "high-angle",
"low_angle": "low-angle",
"overhead": "overhead",
"side_profile": "side-profile",
"rear_view": "rear-view",
"mirror_reflection": "mirror reflection",
"smartphone_wide": "smartphone wide-angle",
"ultra_wide": "ultra-wide",
"portrait_lens": "phone portrait lens",
"telephoto": "telephoto-style",
"macro_detail": "macro detail",
"arm_length": "arm-length",
"near_body": "near-body",
"bedside": "bedside phone",
"room_corner": "room-corner phone",
"vertical_story": "vertical 9:16",
"square_feed": "square feed",
"horizontal": "horizontal",
"phone_visible": "phone visible",
"phone_hidden": "phone hidden",
"screen_reflection": "screen reflection",
"ring_light_visible": "ring light visible",
}
CAMERA_SHOT_PROMPTS = {
"auto": "",
"full_body": "Shot size: full body visible, head-to-toe framing, no important body parts cropped out.",
@@ -917,13 +988,46 @@ def build_filter_config_json(
figure: str = "curvy",
no_plus_women: bool = False,
no_black: bool = False,
include_european: bool = True,
include_mediterranean_mena: bool = True,
include_latina: bool = True,
include_east_asian: bool = True,
include_southeast_asian: bool = True,
include_south_asian: bool = True,
include_black_african: bool = True,
include_indigenous: bool = True,
include_mixed: bool = True,
include_plus_size: bool = True,
) -> str:
include_flags = {
"european": include_european,
"mediterranean_mena": include_mediterranean_mena,
"latina": include_latina,
"east_asian": include_east_asian,
"southeast_asian": include_southeast_asian,
"south_asian": include_south_asian,
"black_african": include_black_african,
"indigenous": include_indigenous,
"mixed": include_mixed,
}
selected_ethnicities = [key for key, enabled in include_flags.items() if enabled]
disabled_ethnicities = [key for key, enabled in include_flags.items() if not enabled]
enabled_ethnicities = list(selected_ethnicities)
if enabled_ethnicities:
enabled_ethnicities.extend(f"exclude_{key}" for key in disabled_ethnicities)
if 0 < len(selected_ethnicities) < len(include_flags):
ethnicity = "+".join(enabled_ethnicities)
elif ethnicity not in ETHNICITY_FILTER_CHOICES:
ethnicity = "any"
return json.dumps(
{
"ethnicity": ethnicity if ethnicity in ("any", "asian", "white_asian") else "any",
"ethnicity": ethnicity,
"ethnicity_includes": selected_ethnicities,
"figure": figure if figure in ("curvy", "balanced", "bombshell") else "curvy",
"no_plus_women": bool(no_plus_women),
"no_black": bool(no_black),
"include_plus_size": bool(include_plus_size),
"include_black_african": bool(include_black_african),
"no_plus_women": not bool(include_plus_size) or bool(no_plus_women),
"no_black": not bool(include_black_african) or bool(no_black),
},
ensure_ascii=True,
sort_keys=True,
@@ -931,7 +1035,14 @@ def build_filter_config_json(
def _parse_filter_config(filter_config: str | dict[str, Any] | None) -> dict[str, Any]:
defaults = {"ethnicity": "any", "figure": "curvy", "no_plus_women": False, "no_black": False}
defaults = {
"ethnicity": "any",
"figure": "curvy",
"no_plus_women": False,
"no_black": False,
"include_plus_size": True,
"include_black_african": True,
}
if not filter_config:
return defaults
if isinstance(filter_config, dict):
@@ -944,8 +1055,11 @@ def _parse_filter_config(filter_config: str | dict[str, Any] | None) -> dict[str
if not isinstance(raw, dict):
raise ValueError("filter_config must be a JSON object")
parsed = {**defaults, **raw}
parsed["ethnicity"] = parsed["ethnicity"] if parsed.get("ethnicity") in ("any", "asian", "white_asian") else "any"
ethnicity = str(parsed.get("ethnicity") or "any")
parsed["ethnicity"] = ethnicity if ethnicity == "any" or ethnicity in ETHNICITY_FILTER_CHOICES or "+" in ethnicity else "any"
parsed["figure"] = parsed["figure"] if parsed.get("figure") in ("curvy", "balanced", "bombshell") else "curvy"
parsed["include_plus_size"] = bool(parsed.get("include_plus_size"))
parsed["include_black_african"] = bool(parsed.get("include_black_african"))
parsed["no_plus_women"] = bool(parsed.get("no_plus_women"))
parsed["no_black"] = bool(parsed.get("no_black"))
return parsed
@@ -997,6 +1111,34 @@ def build_seed_config_json(
)
def build_seed_lock_config_json(
base_seed: int = 20260614,
reroll_axis: str = "none",
reroll_seed: int = -1,
) -> str:
base_seed = int(base_seed)
reroll_seed = int(reroll_seed)
reroll_groups = {
"none": (),
"category": ("category",),
"subcategory": ("subcategory",),
"content": ("content",),
"person": ("person",),
"scene": ("scene",),
"pose": ("pose", "role"),
"role": ("role",),
"expression": ("expression",),
"composition": ("composition",),
"content_pose": ("content", "pose", "role"),
"scene_pose": ("scene", "pose", "role"),
}
reroll = set(reroll_groups.get(str(reroll_axis or "none"), ()))
config: dict[str, int] = {}
for axis in SEED_LOCK_AXES:
config[f"{axis}_seed"] = reroll_seed if axis in reroll else base_seed
return json.dumps(config, ensure_ascii=True, sort_keys=True)
def _parse_seed_config(seed_config: str | dict[str, Any] | None) -> dict[str, int]:
if not seed_config:
return {}
@@ -1075,6 +1217,14 @@ def camera_mode_choices() -> list[str]:
return list(CAMERA_MODE_PROMPTS)
def ethnicity_choices() -> list[str]:
return list(ETHNICITY_FILTER_CHOICES)
def camera_detail_choices() -> list[str]:
return list(CAMERA_DETAIL_CHOICES)
def camera_shot_choices() -> list[str]:
return list(CAMERA_SHOT_PROMPTS)
@@ -1112,6 +1262,7 @@ def build_camera_config_json(
orientation: str = "auto",
phone_visibility: str = "auto",
priority: str = "strong",
camera_detail: str = "compact",
) -> str:
return json.dumps(
{
@@ -1123,6 +1274,7 @@ def build_camera_config_json(
"orientation": orientation,
"phone_visibility": phone_visibility,
"priority": priority,
"camera_detail": camera_detail,
},
ensure_ascii=True,
sort_keys=True,
@@ -1144,6 +1296,7 @@ def _parse_camera_config(camera_config: str | dict[str, Any] | None) -> dict[str
"orientation": "auto",
"phone_visibility": "auto",
"priority": "strong",
"camera_detail": "compact",
}
if not camera_config:
return defaults
@@ -1166,6 +1319,9 @@ def _parse_camera_config(camera_config: str | dict[str, Any] | None) -> dict[str
"orientation": _choice(parsed.get("orientation"), CAMERA_ORIENTATION_PROMPTS, defaults["orientation"]),
"phone_visibility": _choice(parsed.get("phone_visibility"), CAMERA_PHONE_PROMPTS, defaults["phone_visibility"]),
"priority": _choice(parsed.get("priority"), CAMERA_PRIORITY_PROMPTS, defaults["priority"]),
"camera_detail": str(parsed.get("camera_detail") or defaults["camera_detail"])
if str(parsed.get("camera_detail") or defaults["camera_detail"]) in CAMERA_DETAIL_CHOICES
else defaults["camera_detail"],
}
@@ -1178,6 +1334,26 @@ def _camera_config_with_mode(camera_config: str | dict[str, Any] | None, camera_
def _camera_directive(camera_config: str | dict[str, Any] | None) -> tuple[str, dict[str, str]]:
parsed = _parse_camera_config(camera_config)
if parsed["camera_detail"] == "off" or parsed["camera_mode"] == "disabled":
return "", parsed
if parsed["camera_detail"] == "compact":
values = [
parsed["camera_mode"],
parsed["shot_size"],
parsed["angle"],
parsed["lens"],
parsed["distance"],
parsed["orientation"],
parsed["phone_visibility"],
]
labels = [CAMERA_COMPACT_LABELS.get(value, value.replace("_", " ")) for value in values]
labels = [label for value, label in zip(values, labels) if label and value != "auto"]
if not labels:
return "", parsed
directive = "Camera: " + ", ".join(labels) + "."
if parsed["priority"] == "locked":
directive += " Keep this camera framing."
return directive, parsed
parts = [
CAMERA_MODE_PROMPTS[parsed["camera_mode"]],
CAMERA_SHOT_PROMPTS[parsed["shot_size"]],
@@ -1679,7 +1855,7 @@ def _appearance_for_subject(
subject_type = "woman" if rng.random() < 0.82 else "man"
if subject_type == "man":
men_ethnicity = ethnicity if ethnicity == "asian" else "any"
men_ethnicity = ethnicity if ethnicity else "any"
subject, age, body, skin, hair, eyes = g.choose(rng, g.by_ethnicity(g.MEN, men_ethnicity))
return {
"subject_type": "man",
@@ -2452,7 +2628,7 @@ def build_prompt(
start_index = max(1, int(start_index))
seed = int(seed)
clothing = clothing if clothing in ("full", "minimal") else "full"
ethnicity = ethnicity if ethnicity in ("any", "asian", "white_asian") else "any"
ethnicity = ethnicity if ethnicity == "any" or ethnicity in ETHNICITY_FILTER_CHOICES or "+" in str(ethnicity) else "any"
poses = poses if poses in ("standard", "evocative") else "standard"
figure = figure if figure in ("curvy", "balanced", "bombshell") else "curvy"
minimal_ratio = _ratio_or_none(minimal_clothing_ratio)
@@ -2574,6 +2750,7 @@ INSTA_OF_SOFT_LEVELS = {
"lingerie_tease": "premium OF teaser set, lingerie-focused, sensual and intimate",
"implied_nude": "implied nude creator set, strategically covered body and intimate teaser framing",
"explicit_tease": "stronger adult teaser set with bolder nude-adjacent styling and solo-tease framing",
"explicit_nude": "explicit nude creator set with fully nude solo-tease framing",
}
INSTA_OF_HARDCORE_LEVELS = {
@@ -2587,6 +2764,14 @@ INSTA_OF_PLATFORM_STYLES = {
"onlyfans": "OnlyFans-inspired creator shoot, intimate subscriber-view camera and candid premium-content framing",
}
INSTA_OF_HARDCORE_CLOTHING_CONTINUITY = {
"none": "",
"same_outfit": "Woman A keeps the softcore outfit in the hardcore scene",
"partially_removed": "Woman A's softcore outfit is partially removed or pushed aside for the hardcore scene",
"implied_nude": "Woman A is nude-adjacent in the hardcore scene, with the softcore outfit slipping off or covering only part of the body",
"explicit_nude": "Woman A is fully nude in the hardcore scene, with the removed softcore outfit visible nearby",
}
INSTA_OF_NEGATIVE = (
"minors, childlike appearance, teen, underage, schoolgirl, non-consensual, coercion, rape, "
"violence, injury, blood, gore, incest, bestiality, watermark, logo, readable username, social media UI"
@@ -2603,6 +2788,7 @@ INSTA_OF_SOFTCORE_SUBCATEGORY_BY_LEVEL = {
"lingerie_tease": "Provocative erotic clothes / Provocative lingerie",
"implied_nude": "Provocative erotic clothes / Provocative lingerie",
"explicit_tease": "Provocative erotic clothes / Sheer exposed",
"explicit_nude": "Provocative erotic clothes / Nude accessories",
}
INSTA_OF_SOFTCORE_OUTFITS = {
@@ -2642,6 +2828,14 @@ INSTA_OF_SOFTCORE_OUTFITS = {
"bare-shoulder robe opened around covered lingerie, explicit adult tease without partnered contact",
"strappy lingerie set with covered cups and high-waisted bottoms, styled as a stronger solo teaser",
],
"explicit_nude": [
"fully nude creator styling with jewelry, heels, and direct adult selfie confidence",
"fully nude mirror-selfie styling with jewelry only and bold creator-shot framing",
"nude-on-sheets creator pose with lingerie discarded nearby and direct eye contact",
"fully nude vanity-mirror pose with heels, necklace, and premium adult teaser styling",
"nude shower-afterglow creator pose with wet hair, skin highlights, and phone-shot framing",
"fully nude bedroom creator pose with one hand holding the phone and lingerie visible nearby",
],
}
INSTA_OF_SOFTCORE_POSES = {
@@ -2677,6 +2871,14 @@ INSTA_OF_SOFTCORE_POSES = {
"sitting at the vanity in a bolder covered lingerie pose with direct eye contact",
"arching subtly in a solo adult tease while the styling keeps explicit anatomy obscured",
],
"explicit_nude": [
"taking a bold nude mirror selfie with direct eye contact and the body clearly framed",
"posing fully nude on the bed with jewelry and heels as the only styling",
"standing at the vanity fully nude in a premium creator-shot pose",
"reclining fully nude on soft sheets with the phone held close",
"turning slightly in a nude mirror pose with the body framed head-to-thigh",
"kneeling fully nude in a controlled adult teaser pose with direct phone-camera awareness",
],
}
INSTA_OF_SOFTCORE_PARTNER_WOMEN_OUTFITS = [
@@ -2711,8 +2913,10 @@ def build_insta_of_options_json(
hardcore_level: str = "hardcore",
platform_style: str = "hybrid",
continuity: str = "same_creator_same_room",
hardcore_clothing_continuity: str = "partially_removed",
softcore_camera_mode: str = "handheld_selfie",
hardcore_camera_mode: str = "same_as_softcore",
camera_detail: str = "compact",
softcore_expression_intensity: float = 0.45,
hardcore_expression_intensity: float = 0.85,
) -> str:
@@ -2726,8 +2930,10 @@ def build_insta_of_options_json(
"hardcore_level": hardcore_level,
"platform_style": platform_style,
"continuity": continuity,
"hardcore_clothing_continuity": hardcore_clothing_continuity,
"softcore_camera_mode": softcore_camera_mode,
"hardcore_camera_mode": hardcore_camera_mode,
"camera_detail": camera_detail,
"softcore_expression_intensity": _clamped_float(softcore_expression_intensity, 0.45),
"hardcore_expression_intensity": _clamped_float(hardcore_expression_intensity, 0.85),
},
@@ -2746,8 +2952,10 @@ def _parse_insta_of_options(options_json: str | dict[str, Any] | None) -> dict[s
"hardcore_level": "hardcore",
"platform_style": "hybrid",
"continuity": "same_creator_same_room",
"hardcore_clothing_continuity": "partially_removed",
"softcore_camera_mode": "handheld_selfie",
"hardcore_camera_mode": "same_as_softcore",
"camera_detail": "compact",
"softcore_expression_intensity": 0.45,
"hardcore_expression_intensity": 0.85,
}
@@ -2769,9 +2977,15 @@ def _parse_insta_of_options(options_json: str | dict[str, Any] | None) -> dict[s
parsed["hardcore_level"] = parsed["hardcore_level"] if parsed["hardcore_level"] in INSTA_OF_HARDCORE_LEVELS else defaults["hardcore_level"]
parsed["platform_style"] = parsed["platform_style"] if parsed["platform_style"] in INSTA_OF_PLATFORM_STYLES else defaults["platform_style"]
parsed["continuity"] = parsed["continuity"] if parsed["continuity"] in ("same_creator_same_room", "same_creator_new_scene") else defaults["continuity"]
parsed["hardcore_clothing_continuity"] = (
parsed["hardcore_clothing_continuity"]
if parsed["hardcore_clothing_continuity"] in INSTA_OF_HARDCORE_CLOTHING_CONTINUITY
else defaults["hardcore_clothing_continuity"]
)
parsed["softcore_camera_mode"] = parsed["softcore_camera_mode"] if parsed["softcore_camera_mode"] in CAMERA_MODE_PROMPTS else defaults["softcore_camera_mode"]
if parsed["hardcore_camera_mode"] not in CAMERA_MODE_PROMPTS and parsed["hardcore_camera_mode"] != "same_as_softcore":
parsed["hardcore_camera_mode"] = defaults["hardcore_camera_mode"]
parsed["camera_detail"] = parsed["camera_detail"] if parsed["camera_detail"] in CAMERA_DETAIL_CHOICES else defaults["camera_detail"]
parsed["softcore_expression_intensity"] = _clamped_float(
parsed.get("softcore_expression_intensity"),
defaults["softcore_expression_intensity"],
@@ -2865,13 +3079,17 @@ def _insta_of_cast_phrase(women_count: int, men_count: int) -> str:
return context["cast_summary"]
def _insta_of_prompt_cast_descriptors(text: str) -> str:
return str(text or "").replace("Woman A / primary creator:", "Woman A:")
SOFTCORE_CAST_POSES = [
"standing together for a mirror selfie with bodies close but no sexual contact",
"posing shoulder-to-shoulder in a creator-shot group teaser",
"leaning together on the bed in a non-explicit subscriber preview",
"sitting close together with hands kept above clothing",
"arranged around Woman A in a flirtatious non-explicit teaser pose",
"posing in the same room as a coordinated adult creator set",
"posing together as a coordinated adult creator set",
"standing near the phone tripod with relaxed teasing body language",
"framed together in a softcore cast reveal with no sex act",
]
@@ -2896,6 +3114,15 @@ def _insta_of_softcore_pose(rng: random.Random, level: str) -> str:
return g.choose(rng, pool)
def _insta_of_hardcore_clothing_state(mode: str, softcore_outfit: str) -> str:
mode = mode if mode in INSTA_OF_HARDCORE_CLOTHING_CONTINUITY else "none"
outfit = str(softcore_outfit or "").strip()
if mode == "none" or not outfit:
return ""
base = INSTA_OF_HARDCORE_CLOTHING_CONTINUITY[mode]
return f"Clothing state: {base}; softcore visual reference: {outfit}."
def _insta_of_partner_styling(
seed_config: dict[str, int],
seed: int,
@@ -2934,12 +3161,19 @@ def build_insta_of_pair(
prepend_trigger_to_prompt: bool,
seed_config: str | dict[str, Any] | None = None,
options_json: str | dict[str, Any] | None = None,
filter_config: str | dict[str, Any] | None = None,
camera_config: str | dict[str, Any] | None = None,
character_profile: str | dict[str, Any] | None = "",
extra_positive: str = "",
extra_negative: str = "",
) -> dict[str, Any]:
options = _parse_insta_of_options(options_json)
if filter_config:
filters = _parse_filter_config(filter_config)
ethnicity = filters["ethnicity"]
figure = filters["figure"]
no_plus_women = filters["no_plus_women"]
no_black = filters["no_black"]
hard_women_count, hard_men_count = _insta_of_hardcore_counts(options)
active_trigger = trigger.strip() or g.TRIGGER
parsed_seed_config = _parse_seed_config(seed_config)
@@ -3015,11 +3249,11 @@ def build_insta_of_pair(
hard_women_count,
hard_men_count,
)
cast_descriptor_text = "; ".join(cast_descriptors)
cast_descriptor_text = _insta_of_prompt_cast_descriptors("; ".join(cast_descriptors))
soft_cast_descriptor_text = (
cast_descriptor_text
if options["softcore_cast"] == "same_as_hardcore"
else f"Woman A / primary creator: {descriptor}"
else f"Woman A: {descriptor}"
)
soft_partner_styling = _insta_of_partner_styling(
parsed_seed_config,
@@ -3039,33 +3273,44 @@ def build_insta_of_pair(
hard_camera_mode = options["softcore_camera_mode"]
soft_camera_config = _camera_config_with_mode(camera_config, options["softcore_camera_mode"])
hard_camera_config = _camera_config_with_mode(camera_config, hard_camera_mode)
soft_camera_config["camera_detail"] = options["camera_detail"]
hard_camera_config["camera_detail"] = options["camera_detail"]
soft_camera_directive, soft_camera_config = _camera_directive(soft_camera_config)
hard_camera_directive, hard_camera_config = _camera_directive(hard_camera_config)
soft_camera_sentence = f"Camera control: {soft_camera_directive} " if soft_camera_directive else ""
hard_camera_sentence = f"Camera control: {hard_camera_directive} " if hard_camera_directive else ""
hard_scene = soft_row["scene_text"] if options["continuity"] == "same_creator_same_room" else hard_row["scene_text"]
hard_composition = soft_row["composition"] if options["continuity"] == "same_creator_same_room" else hard_row["composition"]
hard_composition = hard_row["composition"]
soft_cast = (
"solo creator setup; the primary creator is alone in the softcore version"
"solo creator setup with Woman A alone"
if options["softcore_cast"] == "solo"
else f"non-explicit teaser setup with the same adult cast as the hardcore version: {_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 = (
"Keep the same cast together in the softcore version in a non-explicit teaser pose with no sex act or genital contact. "
"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 = (
f"Partner softcore styling: {soft_partner_outfit_text}. Shared softcore cast pose: {soft_partner_styling['pose']}. "
f"Partner softcore styling: {soft_partner_outfit_text}. Cast pose: {soft_partner_styling['pose']}. "
if options["softcore_cast"] == "same_as_hardcore" and soft_partner_outfit_text
else ""
)
hard_cast = _insta_of_cast_phrase(hard_women_count, hard_men_count)
hard_clothing_state = _insta_of_hardcore_clothing_state(
options["hardcore_clothing_continuity"],
soft_row["item"],
)
soft_descriptor_sentence = (
f"Cast descriptors: {soft_cast_descriptor_text}. "
if options["softcore_cast"] == "same_as_hardcore"
else f"Woman A: {descriptor}. "
)
soft_prompt = (
f"Insta/OF softcore mode: {platform_style}. Shared primary creator descriptor: {descriptor}. "
f"Softcore setup: {soft_level}. Cast continuity: {soft_cast}. "
f"Shared cast descriptors: {soft_cast_descriptor_text}. "
f"Insta/OF softcore mode: {platform_style}. "
f"{soft_descriptor_sentence}"
f"Softcore setup: {soft_level}. Cast: {soft_cast}. "
f"{soft_cast_presence}"
f"{soft_cast_styling_sentence}"
f"Outfit: {soft_row['item']}. Pose: {soft_row['pose']}. Setting: {soft_row['scene_text']}. "
@@ -3075,10 +3320,11 @@ def build_insta_of_pair(
f"{soft_row['positive_suffix']}."
)
hard_prompt = (
f"Insta/OF hardcore mode: {platform_style}. Shared primary creator descriptor: {descriptor}. "
f"Insta/OF hardcore mode: {platform_style}. "
f"Hardcore setup: {hard_level}. Cast: {hard_cast}. "
f"Shared cast descriptors: {cast_descriptor_text}. "
"Apply the shared descriptor to the most visually central woman, keeping her continuous with the softcore version. "
f"Cast descriptors: {cast_descriptor_text}. "
"Keep Woman A visually central. "
f"{hard_clothing_state} "
f"Role graph: {hard_row['role_graph']} Sexual scene: {hard_row['item']}. "
f"Setting: {hard_scene}. Facial expressions: {hard_row['expression']}. Composition: {hard_composition}. "
f"{hard_camera_sentence}"
@@ -3103,20 +3349,29 @@ def build_insta_of_pair(
soft_partner_styling["pose"],
soft_row["scene_text"],
soft_row["composition"],
f"{soft_camera_config['camera_mode'].replace('_', ' ')} camera",
f"{soft_camera_config['camera_mode'].replace('_', ' ')} camera" if soft_camera_directive else "",
]
soft_caption = ", ".join(str(part).strip() for part in soft_caption_parts if str(part).strip())
hard_caption = (
f"{active_trigger}, Insta/OF hardcore mode, same primary creator descriptor, {descriptor}, "
f"{hard_cast}, {hard_row['role_graph']}, {hard_row['item']}, {hard_scene}, {hard_composition}, "
f"{hard_camera_config['camera_mode'].replace('_', ' ')} camera"
)
hard_caption_parts = [
active_trigger,
"Insta/OF hardcore mode",
"Woman A",
descriptor,
hard_cast,
hard_row["role_graph"],
hard_row["item"],
hard_scene,
hard_composition,
f"{hard_camera_config['camera_mode'].replace('_', ' ')} camera" if hard_camera_directive else "",
]
hard_caption = ", ".join(str(part).strip() for part in hard_caption_parts if str(part).strip())
metadata = {
"mode": "Insta/OF",
"options": options,
"shared_descriptor": descriptor,
"shared_cast_descriptors": cast_descriptors,
"softcore_partner_styling": soft_partner_styling,
"hardcore_clothing_state": hard_clothing_state,
"softcore_prompt": soft_prompt,
"hardcore_prompt": hard_prompt,
"softcore_negative_prompt": soft_negative,