from __future__ import annotations import json import random from pathlib import Path from string import Formatter from typing import Any try: from . import generate_prompt_batches as g except ImportError: # Allows local smoke tests with `python -c`. import generate_prompt_batches as g ROOT_DIR = Path(__file__).resolve().parent CATEGORY_DIR = ROOT_DIR / "categories" BUILTIN_CATEGORIES = [ "auto_weighted", "woman", "man", "couple", "group_or_layout", "custom_random", ] RANDOM_SUBCATEGORY = "random" SEED_AXIS_SALTS = { "category": 31, "subcategory": 37, "content": 41, "person": 43, "scene": 47, "pose": 53, "role": 57, "expression": 59, "composition": 61, } SEED_AXIS_ALIASES = { "category": ("category_seed", "category"), "subcategory": ("subcategory_seed", "subcategory"), "content": ("content_seed", "item_seed", "outfit_seed", "sexual_pose_seed", "content"), "person": ("person_seed", "appearance_seed", "cast_seed", "person"), "scene": ("scene_seed", "scene"), "pose": ("pose_seed", "sexual_pose_seed", "pose"), "role": ("role_seed", "role", "pose_seed", "sexual_pose_seed"), "expression": ("expression_seed", "face_seed", "expression"), "composition": ("composition_seed", "camera_seed", "composition"), } 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." ) SINGLE_TEMPLATE = ( "A {subject}: {style}, {age}, {body_phrase}, {skin}, {hair}, {eyes}. " "{item_label}: {item}. Scene: {scene}. Pose: {pose}. Facial expression: {expression}. " "Composition: vertical {composition}. {positive_suffix} Avoid: {negative_prompt}." ) COUPLE_TEMPLATE = ( "{subject_phrase}: {style}. Ages: {age}. Body types: {body}. {item_label}: {item}. " "Scene: {scene}. Pose: {pose}. Facial expressions: {expression}. " "Composition: vertical {composition}. {positive_suffix} Avoid: {negative_prompt}." ) GROUP_TEMPLATE = ( "{subject_phrase}: {style}, ages {age}, diverse adult body types. {item_label}: {item}. " "Scene: {scene}. Facial expressions: {expression}. Composition: vertical {composition}. " "{positive_suffix} Avoid: {negative_prompt}." ) LAYOUT_TEMPLATE = ( "{item}: {style}, adults only, clean designed composition. Scene: {scene}. " "Facial expression: {expression}. Composition: {composition}. {positive_suffix} " "Avoid: {negative_prompt}. Use no readable text unless the layout naturally needs small decorative placeholder marks." ) CAMERA_MODE_PROMPTS = { "standard": "", "handheld_selfie": ( "Camera mode: handheld smartphone selfie, close arm-length framing, visible creator-shot perspective, " "slight wide-angle intimacy, direct eye contact, natural phone-camera composition." ), "mirror_selfie": ( "Camera mode: mirror selfie with the phone visible in one hand, reflective framing, creator looking at the screen, " "body and environment visible through the mirror." ), "phone_tripod": ( "Camera mode: phone on tripod or ring-light stand, creator-facing social-video framing, stable vertical composition, " "hands-free self-recorded setup." ), "creator_pov": ( "Camera mode: creator-held POV, intimate subscriber-view angle, the creator controls the camera, close foreground body framing." ), "bed_selfie": ( "Camera mode: bed selfie shot from a phone held above or beside the body, intimate close framing, sheets visible around the subject." ), "bathroom_mirror": ( "Camera mode: bathroom mirror selfie, phone visible, tiled private room, close vertical framing, candid creator-shot energy." ), "phone_flash": ( "Camera mode: direct phone-flash selfie, crisp flash highlights, candid night-post feeling, hard-edged smartphone shadows." ), "action_cam": ( "Camera mode: body-mounted or handheld action-camera intimacy, very close wide-angle perspective, dynamic creator-shot framing." ), } CAMERA_SHOT_PROMPTS = { "auto": "", "full_body": "Shot size: full body visible, head-to-toe framing, no important body parts cropped out.", "three_quarter": "Shot size: three-quarter body framing, face, torso, hips, and thighs clearly visible.", "waist_up": "Shot size: waist-up creator framing with face and upper body as the focus.", "close_up": "Shot size: close-up framing with face, expression, hands, and body contact emphasized.", "extreme_close_up": "Shot size: extreme close-up detail shot, tightly framed and intimate.", } CAMERA_ANGLE_PROMPTS = { "auto": "", "eye_level": "Angle: eye-level camera angle with direct creator eye contact.", "high_angle": "Angle: high-angle selfie looking down toward the body.", "low_angle": "Angle: low-angle phone camera looking upward from near the body.", "overhead": "Angle: overhead phone shot looking down at the full pose.", "side_profile": "Angle: side-profile camera view emphasizing body silhouette and contact points.", "rear_view": "Angle: rear-view camera framing with the body turned away from the lens.", "mirror_reflection": "Angle: mirror-reflection composition with the phone and reflected body placement readable.", } CAMERA_LENS_PROMPTS = { "auto": "", "smartphone_wide": "Lens: smartphone wide-angle lens with slight edge distortion and close personal scale.", "ultra_wide": "Lens: ultra-wide phone lens, exaggerated near-camera perspective, environmental context visible.", "portrait_lens": "Lens: phone portrait mode, shallow depth of field, crisp subject separation.", "telephoto": "Lens: compressed telephoto-style framing, flatter proportions, less distortion.", "macro_detail": "Lens: macro-detail phone shot focused on texture, skin, fabric, and contact detail.", } CAMERA_DISTANCE_PROMPTS = { "auto": "", "arm_length": "Camera distance: arm-length selfie distance, close enough to feel handheld.", "near_body": "Camera distance: near-body camera placement with intimate foreground framing.", "bedside": "Camera distance: phone placed beside the body on the bed or floor.", "room_corner": "Camera distance: phone set across the room, self-recorded but wider and more observational.", } CAMERA_ORIENTATION_PROMPTS = { "auto": "", "vertical_story": "Orientation: vertical 9:16 story/reel framing.", "square_feed": "Orientation: square social-feed crop.", "horizontal": "Orientation: horizontal phone-video crop.", } CAMERA_PHONE_PROMPTS = { "auto": "", "phone_visible": "Phone visibility: phone visible in hand or mirror, clearly creator-shot.", "phone_hidden": "Phone visibility: phone is implied but not visible, preserving the selfie/creator-shot perspective.", "screen_reflection": "Phone visibility: screen glow or reflection visible in the scene.", "ring_light_visible": "Phone visibility: ring light or tripod visible enough to read as self-recorded content.", } CAMERA_PRIORITY_PROMPTS = { "soft_hint": "Camera priority: treat the camera notes as style guidance.", "strong": "Camera priority: strongly preserve the selected camera, lens, angle, crop, and phone-shot perspective.", "locked": "Camera priority: locked camera constraint; do not replace this with a studio, third-person, cinematic, or unrelated camera view.", } _EXTENSIONS_APPLIED = False class SafeFormatDict(dict): def __missing__(self, key: str) -> str: return "{" + key + "}" def _json_files() -> list[Path]: if not CATEGORY_DIR.exists(): return [] return sorted(path for path in CATEGORY_DIR.glob("*.json") if path.is_file()) def _read_json(path: Path) -> dict[str, Any]: try: data = json.loads(path.read_text(encoding="utf-8")) except json.JSONDecodeError as exc: raise ValueError(f"Invalid JSON in {path}: {exc}") from exc if not isinstance(data, dict): raise ValueError(f"{path} must contain a JSON object") return data def _slug(value: str) -> str: return g.slugify(value) or "custom" def _list_from(value: Any) -> list[Any]: if value is None: return [] if isinstance(value, list): return value return [value] 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 _unique_extend(target: list[Any], additions: list[Any]) -> None: seen = set() for item in target: try: seen.add(json.dumps(item, sort_keys=True)) except TypeError: seen.add(repr(item)) for item in additions: try: marker = json.dumps(item, sort_keys=True) except TypeError: marker = repr(item) if marker not in seen: target.append(item) seen.add(marker) def _pair_from(value: Any) -> tuple[str, str]: if isinstance(value, dict): text = str( value.get("prompt") or value.get("description") or value.get("text") or value.get("name") or "" ).strip() slug = str(value.get("slug") or _slug(str(value.get("name") or text))).strip() if not text: raise ValueError(f"Pair extension is missing prompt text: {value!r}") return slug, text if isinstance(value, (list, tuple)) and len(value) == 2: return str(value[0]), str(value[1]) text = str(value).strip() if not text: raise ValueError("Pair extension cannot be empty") return _slug(text), text def _weighted_choice(rng: random.Random, items: list[Any]) -> Any: if not items: raise ValueError("Cannot choose from an empty list") weights: list[float] = [] for item in items: weight = item.get("weight", 1.0) if isinstance(item, dict) else 1.0 try: weights.append(max(0.0, float(weight))) except (TypeError, ValueError): weights.append(1.0) total = sum(weights) if total <= 0: return items[rng.randrange(len(items))] pick = rng.random() * total running = 0.0 for item, weight in zip(items, weights): running += weight if pick <= running: return item return items[-1] def _entry_text(item: Any) -> str: if isinstance(item, dict): return str( item.get("template") or item.get("prompt") or item.get("text") or item.get("description") or item.get("name") or "" ).strip() return str(item).strip() def _item_text(item: Any) -> str: return _entry_text(item) def _item_name(item: Any) -> str: if isinstance(item, dict): return str(item.get("name") or _item_text(item)).strip() return _item_text(item) def _template_list(category: dict[str, Any], subcategory: dict[str, Any], item: Any, key: str) -> list[Any]: if isinstance(item, dict) and key in item: return _list_from(item[key]) if key in subcategory: return _list_from(subcategory[key]) if key in category: return _list_from(category[key]) return [] def _constraint_int(entry: dict[str, Any], key: str) -> int | None: if key not in entry: return None try: return int(entry[key]) except (TypeError, ValueError): return None def _cast_requirement_matches(requirement: str, women_count: int, men_count: int) -> bool: total = women_count + men_count requirement = requirement.strip().lower() if requirement in ("", "any"): return True if requirement == "women_only": return women_count > 0 and men_count == 0 if requirement == "men_only": return men_count > 0 and women_count == 0 if requirement == "mixed": return women_count > 0 and men_count > 0 if requirement == "has_women": return women_count > 0 if requirement == "has_men": return men_count > 0 if requirement == "solo": return total == 1 if requirement == "couple": return total == 2 if requirement == "threesome": return total == 3 if requirement == "group": return total >= 4 return True def _heuristic_cast_compatible(text: str, women_count: int, men_count: int) -> bool: text = text.lower() if not text: return True total = women_count + men_count if total == 1: solo_blocked_terms = ( "partner", "partners", "two bodies", "three bodies", "bodies still pressed", "bodies pressed", "bodies tangled", "wet bodies", "chests heaving together", "straddling a partner", "shared climax", "between two", "from both sides", "front-and-back", "body contact", ) if any(term in text for term in solo_blocked_terms): return False solo_toy_terms = ("toy", "dildo", "finger", "fingers", "self") if "penetration" in text and not any(term in text for term in solo_toy_terms): return False if total < 3 and "threesome" in text: return False if total != 3 and ("centered threesome" in text or "three-way" in text): return False if total < 3 and ("three bodies" in text or "center partner" in text or "center body" in text): return False if total < 4 and ("orgy" in text or "group sex" in text or "group-sex" in text or "group pile" in text): return False if total < 3 and ( "double penetration" in text or "two partners penetrating" in text or "front-and-back penetration" in text or "one cock in pussy and one cock in ass" in text or "pussy and ass filled" in text or "vaginal and anal penetration at the same time" in text or "front-and-back double penetration" in text or "hardcore double penetration" in text or "kneeling double penetration" in text or "standing supported double penetration" in text or "deep double penetration" in text or "between two partners" in text or "from both sides" in text ): toy_terms = ("strap-on", "strap on", "dildo", "toy", "finger") if not any(term in text for term in toy_terms): return False if men_count == 0: toy_terms = ("strap-on", "strap on", "dildo", "toy", "finger", "fingers") penetration_terms = ( "vaginal penetration", "deep vaginal sex", "penetrative sex", "pussy penetration", "pussy stretched", "vaginal thrusting", "full-body penetrative", "close-contact vaginal", "penetration clearly visible", "explicit penetrative contact", ) if any(term in text for term in penetration_terms) and not any(term in text for term in toy_terms): return False male_terms = ( " cock", "cock ", "cocks", "cum", "creampie", "facial", "blowjob", "fellatio", "deepthroat", "ejaculation", "semen", ) if any(term in text for term in male_terms) and not any(term in text for term in toy_terms): return False elif men_count < 2 and "cocks" in text: return False if women_count == 0: if "penetrative sex" in text and not any(term in text for term in ("anal", "ass", "male/male", "men")): return False female_terms = ( "pussy", "vaginal", "vagina", "cunnilingus", "clit", "clitoris", "breasts", "breast ", "nipples", "nipple", "underboob", ) if any(term in text for term in female_terms): return False return True def _compatible_entry(entry: Any, women_count: int, men_count: int) -> bool: if not isinstance(entry, dict): return _heuristic_cast_compatible(_entry_text(entry), women_count, men_count) total = women_count + men_count for key, value in ( ("min_women", women_count), ("min_men", men_count), ("min_people", total), ): minimum = _constraint_int(entry, key) if minimum is not None and value < minimum: return False for key, value in ( ("max_women", women_count), ("max_men", men_count), ("max_people", total), ): maximum = _constraint_int(entry, key) if maximum is not None and value > maximum: return False requirements = _list_from(entry.get("cast", [])) + _list_from(entry.get("requires", [])) if requirements and not all(_cast_requirement_matches(str(req), women_count, men_count) for req in requirements): return False if any(key in entry for key in ("subcategories", "item_templates", "item_axes")): return True return _heuristic_cast_compatible(_entry_text(entry), women_count, men_count) def _compatible_entries(entries: list[Any], women_count: int, men_count: int) -> list[Any]: filtered = [entry for entry in entries if _compatible_entry(entry, women_count, men_count)] return filtered or entries def _merged_axes(category: dict[str, Any], subcategory: dict[str, Any], item: Any) -> dict[str, list[Any]]: axes: dict[str, list[Any]] = {} for source in (category, subcategory, item if isinstance(item, dict) else None): if not isinstance(source, dict): continue raw_axes = source.get("item_axes", {}) if raw_axes is None: continue if not isinstance(raw_axes, dict): raise ValueError("item_axes must be a JSON object") for key, values in raw_axes.items(): axes[str(key)] = _list_from(values) return axes def _compose_item( rng: random.Random, category: dict[str, Any], subcategory: dict[str, Any], item: Any, women_count: int = 1, men_count: int = 1, ) -> tuple[str, str, dict[str, str]]: templates = _template_list(category, subcategory, item, "item_templates") axes = _merged_axes(category, subcategory, item) if templates and axes: template = _entry_text(_weighted_choice(rng, _compatible_entries(templates, women_count, men_count))) fields = {key for _, key, _, _ in Formatter().parse(template) if key} axis_values = { name: _entry_text(_weighted_choice(rng, _compatible_entries(axes[name], women_count, men_count))) for name in fields if name in axes and axes[name] } item_text = _format(template, axis_values).strip() item_name = _item_name(item) or subcategory["name"] return item_text, item_name, axis_values return _item_text(item), _item_name(item), {} def _choose_text(rng: random.Random, items: list[Any]) -> str: item = _weighted_choice(rng, items) return _item_text(item) def _choose_pair(rng: random.Random, items: list[Any]) -> tuple[str, str]: return _pair_from(_weighted_choice(rng, items)) def _normalize_subcategories(category: dict[str, Any]) -> list[dict[str, Any]]: raw = category.get("subcategories", []) if isinstance(raw, dict): raw = [ {"name": name, **(value if isinstance(value, dict) else {"items": value})} for name, value in raw.items() ] subcategories: list[dict[str, Any]] = [] for entry in _list_from(raw): if isinstance(entry, str): sub = {"name": entry, "items": [entry]} elif isinstance(entry, dict): sub = dict(entry) else: raise ValueError(f"Subcategory must be an object or string: {entry!r}") name = str(sub.get("name") or sub.get("slug") or "General").strip() sub["name"] = name sub["slug"] = str(sub.get("slug") or _slug(name)) if "items" not in sub and "prompts" in sub: sub["items"] = sub["prompts"] if "items" not in sub: sub["items"] = [name] subcategories.append(sub) if not subcategories: name = str(category.get("name") or "General") subcategories.append({"name": "General", "slug": "general", "items": [name]}) return subcategories def _normalize_categories(raw_categories: Any) -> list[dict[str, Any]]: if isinstance(raw_categories, dict): iterable = [ {"name": name, **(value if isinstance(value, dict) else {"subcategories": value})} for name, value in raw_categories.items() ] else: iterable = _list_from(raw_categories) categories: list[dict[str, Any]] = [] for entry in iterable: if not isinstance(entry, dict): raise ValueError(f"Category must be an object: {entry!r}") category = dict(entry) name = str(category.get("name") or category.get("slug") or "Custom").strip() category["name"] = name category["slug"] = str(category.get("slug") or _slug(name)) category["subcategories"] = _normalize_subcategories(category) categories.append(category) return categories def load_category_library() -> list[dict[str, Any]]: categories: list[dict[str, Any]] = [] for path in _json_files(): data = _read_json(path) categories.extend(_normalize_categories(data.get("categories", []))) return categories def _load_named_pool_library(key: str) -> dict[str, list[Any]]: pools: dict[str, list[Any]] = {} for path in _json_files(): data = _read_json(path) raw_pools = data.get(key, {}) if not raw_pools: continue if not isinstance(raw_pools, dict): raise ValueError(f"{key} in {path} must be an object") for name, entries in raw_pools.items(): pool_name = str(name).strip() if not pool_name: continue pools.setdefault(pool_name, []) _unique_extend(pools[pool_name], _list_from(entries)) return pools def load_scene_pool_library() -> dict[str, list[Any]]: return _load_named_pool_library("scene_pools") def load_expression_pool_library() -> dict[str, list[Any]]: return _load_named_pool_library("expression_pools") def load_composition_pool_library() -> dict[str, list[Any]]: return _load_named_pool_library("composition_pools") def _extension_targets() -> dict[str, tuple[list[Any], bool]]: return { "women_clothes": (g.WOMEN_CLOTHES, False), "women_clothes_minimal": (g.WOMEN_CLOTHES_MINIMAL, False), "men_clothes": (g.MEN_CLOTHES, False), "men_clothes_minimal": (g.MEN_CLOTHES_MINIMAL, False), "couple_outfits": (g.COUPLE_OUTFITS, False), "couple_outfits_minimal": (g.COUPLE_OUTFITS_MINIMAL, False), "poses": (g.POSES, False), "evocative_poses": (g.EVOCATIVE_POSES, False), "backside_poses": (g.BACKSIDE_POSES, False), "expressions": (g.EXPRESSIONS, False), "compositions": (g.COMPOSITIONS, False), "props": (g.PROPS, False), "figure_curvy": (g.FIGURE_CURVY, False), "figure_athletic": (g.FIGURE_ATHLETIC, False), "figure_bombshell": (g.FIGURE_BOMBSHELL, False), "scenes": (g.SCENES, True), "group_scenes": (g.GROUP_SCENES, True), "layouts_full": (g.LAYOUTS_FULL, True), "layouts_minimal": (g.LAYOUTS_MINIMAL, True), "group_compositions": (g.GROUP_COMPOSITIONS, False), "group_ages": (g.GROUP_AGES, False), } def apply_pool_extensions() -> None: global _EXTENSIONS_APPLIED if _EXTENSIONS_APPLIED: return targets = _extension_targets() for path in _json_files(): data = _read_json(path) extensions = data.get("pool_extensions", {}) if not isinstance(extensions, dict): raise ValueError(f"pool_extensions in {path} must be an object") for target_name, additions in extensions.items(): if target_name not in targets: known = ", ".join(sorted(targets)) raise ValueError(f"Unknown pool extension '{target_name}' in {path}. Known: {known}") target, expects_pair = targets[target_name] normalized = [_pair_from(item) for item in _list_from(additions)] if expects_pair else [ _item_text(item) for item in _list_from(additions) ] _unique_extend(target, normalized) g.EVOCATIVE_ALL = g.EVOCATIVE_POSES + g.BACKSIDE_POSES _EXTENSIONS_APPLIED = True def category_choices() -> list[str]: apply_pool_extensions() custom = [category["name"] for category in load_category_library()] return BUILTIN_CATEGORIES + [name for name in custom if name not in BUILTIN_CATEGORIES] def subcategory_choices() -> list[str]: apply_pool_extensions() choices = [RANDOM_SUBCATEGORY] for category in load_category_library(): for subcategory in category["subcategories"]: choices.append(f"{category['name']} / {subcategory['name']}") return choices def _ratio_or_none(value: float) -> float | None: try: ratio = float(value) except (TypeError, ValueError): return None if ratio < 0: return None return max(0.0, min(1.0, ratio)) def _clamped_float(value: Any, default: float = 0.5, min_value: float = 0.0, max_value: float = 1.0) -> float: try: number = float(value) except (TypeError, ValueError): return default return max(min_value, min(max_value, number)) def build_seed_config_json( category_seed: int = -1, subcategory_seed: int = -1, content_seed: int = -1, person_seed: int = -1, scene_seed: int = -1, pose_seed: int = -1, role_seed: int = -1, expression_seed: int = -1, composition_seed: int = -1, ) -> str: return json.dumps( { "category_seed": int(category_seed), "subcategory_seed": int(subcategory_seed), "content_seed": int(content_seed), "person_seed": int(person_seed), "scene_seed": int(scene_seed), "pose_seed": int(pose_seed), "role_seed": int(role_seed), "expression_seed": int(expression_seed), "composition_seed": int(composition_seed), }, 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 {} if isinstance(seed_config, dict): raw = seed_config else: try: raw = json.loads(str(seed_config)) except json.JSONDecodeError as exc: raise ValueError(f"Invalid seed_config JSON: {exc}") from exc if not isinstance(raw, dict): raise ValueError("seed_config must be a JSON object") parsed: dict[str, int] = {} for key, value in raw.items(): try: parsed[str(key)] = int(value) except (TypeError, ValueError): continue return parsed def _configured_axis_seed(seed_config: dict[str, int], axis: str) -> int | None: for key in SEED_AXIS_ALIASES.get(axis, (axis,)): value = seed_config.get(key) if value is not None and value >= 0: return value return None def _axis_rng(seed_config: dict[str, int], axis: str, base_seed: int, row_number: int) -> random.Random: configured = _configured_axis_seed(seed_config, axis) salt = SEED_AXIS_SALTS.get(axis, 0) if configured is None: return random.Random(_row_seed(base_seed, row_number, salt)) return random.Random(_row_seed(configured, row_number, salt)) def _is_pose_content_category(category: dict[str, Any], subcategory: dict[str, Any]) -> bool: haystack = " ".join( str(value) for value in ( category.get("name", ""), category.get("slug", ""), category.get("item_label", ""), subcategory.get("name", ""), subcategory.get("slug", ""), subcategory.get("item_label", ""), ) ).lower() return "pose" in haystack or "sex" in haystack def _format(template: str, context: dict[str, Any]) -> str: fields = {key for _, key, _, _ in Formatter().parse(template) if key} safe_context = SafeFormatDict({key: str(value) for key, value in context.items()}) for field in fields: safe_context.setdefault(field, "{" + field + "}") return template.format_map(safe_context) def _prepend_trigger(prompt: str, trigger: str, enabled: bool) -> str: trigger = trigger.strip() if not enabled or not trigger: return prompt if prompt.lower().startswith(trigger.lower()): return prompt return f"{trigger}, {prompt}" def _combined_negative(base: str, extra: str) -> str: parts = [part.strip() for part in (base, extra) if part and part.strip()] return ", ".join(parts) def camera_mode_choices() -> list[str]: return list(CAMERA_MODE_PROMPTS) def camera_shot_choices() -> list[str]: return list(CAMERA_SHOT_PROMPTS) def camera_angle_choices() -> list[str]: return list(CAMERA_ANGLE_PROMPTS) def camera_lens_choices() -> list[str]: return list(CAMERA_LENS_PROMPTS) def camera_distance_choices() -> list[str]: return list(CAMERA_DISTANCE_PROMPTS) def camera_orientation_choices() -> list[str]: return list(CAMERA_ORIENTATION_PROMPTS) def camera_phone_choices() -> list[str]: return list(CAMERA_PHONE_PROMPTS) def camera_priority_choices() -> list[str]: return list(CAMERA_PRIORITY_PROMPTS) def build_camera_config_json( camera_mode: str = "standard", shot_size: str = "auto", angle: str = "auto", lens: str = "auto", distance: str = "auto", orientation: str = "auto", phone_visibility: str = "auto", priority: str = "strong", ) -> str: return json.dumps( { "camera_mode": camera_mode, "shot_size": shot_size, "angle": angle, "lens": lens, "distance": distance, "orientation": orientation, "phone_visibility": phone_visibility, "priority": priority, }, ensure_ascii=True, sort_keys=True, ) def _choice(value: Any, choices: dict[str, str], default: str) -> str: value = str(value or default) return value if value in choices else default def _parse_camera_config(camera_config: str | dict[str, Any] | None) -> dict[str, str]: defaults = { "camera_mode": "standard", "shot_size": "auto", "angle": "auto", "lens": "auto", "distance": "auto", "orientation": "auto", "phone_visibility": "auto", "priority": "strong", } if not camera_config: return defaults if isinstance(camera_config, dict): raw = camera_config else: try: raw = json.loads(str(camera_config)) except json.JSONDecodeError as exc: raise ValueError(f"Invalid camera_config JSON: {exc}") from exc if not isinstance(raw, dict): raise ValueError("camera_config must be a JSON object") parsed = {**defaults, **raw} return { "camera_mode": _choice(parsed.get("camera_mode"), CAMERA_MODE_PROMPTS, defaults["camera_mode"]), "shot_size": _choice(parsed.get("shot_size"), CAMERA_SHOT_PROMPTS, defaults["shot_size"]), "angle": _choice(parsed.get("angle"), CAMERA_ANGLE_PROMPTS, defaults["angle"]), "lens": _choice(parsed.get("lens"), CAMERA_LENS_PROMPTS, defaults["lens"]), "distance": _choice(parsed.get("distance"), CAMERA_DISTANCE_PROMPTS, defaults["distance"]), "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"]), } def _camera_config_with_mode(camera_config: str | dict[str, Any] | None, camera_mode: str) -> dict[str, str]: parsed = _parse_camera_config(camera_config) if camera_mode and camera_mode != "from_camera_config": parsed["camera_mode"] = _choice(camera_mode, CAMERA_MODE_PROMPTS, parsed["camera_mode"]) return parsed def _camera_directive(camera_config: str | dict[str, Any] | None) -> tuple[str, dict[str, str]]: parsed = _parse_camera_config(camera_config) parts = [ CAMERA_MODE_PROMPTS[parsed["camera_mode"]], CAMERA_SHOT_PROMPTS[parsed["shot_size"]], CAMERA_ANGLE_PROMPTS[parsed["angle"]], CAMERA_LENS_PROMPTS[parsed["lens"]], CAMERA_DISTANCE_PROMPTS[parsed["distance"]], CAMERA_ORIENTATION_PROMPTS[parsed["orientation"]], CAMERA_PHONE_PROMPTS[parsed["phone_visibility"]], ] parts = [part for part in parts if part] if not parts: return "", parsed parts.append(CAMERA_PRIORITY_PROMPTS[parsed["priority"]]) return " ".join(parts), parsed def _insert_positive_directive(prompt: str, directive: str) -> str: marker = " Avoid:" if marker in prompt: before, after = prompt.split(marker, 1) return f"{before.rstrip()} {directive}{marker}{after}" return f"{prompt.rstrip()} {directive}" def _apply_camera_config(row: dict[str, Any], camera_config: str | dict[str, Any] | None) -> dict[str, Any]: directive, parsed = _camera_directive(camera_config) row["camera_config"] = parsed row["camera_directive"] = directive if not directive: return row row["prompt"] = _insert_positive_directive(row["prompt"], directive) row["caption"] = f"{row.get('caption', '').rstrip()}, {parsed['camera_mode'].replace('_', ' ')} camera framing" return row def _row_seed(seed: int, row_number: int, salt: int = 0) -> int: return int(seed) + int(row_number) * 1009 + salt * 9176 def _pick_clothing_mode(rng: random.Random, clothing: str, minimal_ratio: float | None) -> str: if minimal_ratio is None: return clothing return "minimal" if rng.random() < minimal_ratio else "full" def _pick_pose_mode(rng: random.Random, poses: str, standard_ratio: float | None) -> str: if standard_ratio is None: return poses return "standard" if rng.random() < standard_ratio else "evocative" def _build_auto_weighted_row( row_number: int, start_index: int, clothing: str, ethnicity: str, poses: str, backside_bias: float, figure: str, no_plus_women: bool, no_black: bool, minimal_clothing_ratio: float | None, standard_pose_ratio: float | None, seed: int, ) -> dict[str, Any]: batch_number = max(1, ((row_number - 1) // g.BATCH_SIZE) + 1) rows = g.build_rows( batch_number * g.BATCH_SIZE, start_index, clothing, ethnicity, poses, backside_bias, figure, no_plus_women, no_black, minimal_clothing_ratio, standard_pose_ratio, seed, g.EXPRESSION_SEED + seed, ) row = rows[row_number - 1] row["main_category"] = "auto_weighted" row["subcategory"] = row.get("primary_subject", "auto") row["source"] = "built_in_generator" return row def _build_direct_builtin_row( category: str, row_number: int, start_index: int, clothing: str, ethnicity: str, poses: str, backside_bias: float, figure: str, no_plus_women: bool, no_black: bool, minimal_clothing_ratio: float | None, standard_pose_ratio: float | None, seed: int, ) -> dict[str, Any]: rng = random.Random(_row_seed(seed, row_number)) expr_deck = g.ExpressionDeck(g.EXPRESSIONS, random.Random(_row_seed(g.EXPRESSION_SEED + seed, row_number))) batch = max(1, ((row_number - 1) // g.BATCH_SIZE) + 1) index = start_index + row_number - 1 row_clothing = _pick_clothing_mode(rng, clothing, minimal_clothing_ratio) row_poses = _pick_pose_mode(rng, poses, standard_pose_ratio) if category == "woman": row = g.make_single( index, batch, rng, "woman", expr_deck, row_clothing, ethnicity, row_poses, backside_bias, figure, no_plus_women, no_black, ) elif category == "man": row = g.make_single(index, batch, rng, "man", expr_deck, row_clothing, ethnicity, row_poses, backside_bias, figure) elif category == "couple": row = g.make_couple(index, batch, rng, expr_deck, row_clothing, ethnicity, no_plus_women) elif category == "group_or_layout": row = g.make_group_or_layout(index, batch, rng, expr_deck, row_clothing, ethnicity, no_plus_women) else: raise ValueError(f"Unknown built-in category: {category}") row["main_category"] = category row["subcategory"] = row.get("pose_mode", category) row["source"] = "built_in_generator" return row def _find_category(categories: list[dict[str, Any]], name_or_slug: str) -> dict[str, Any] | None: wanted = name_or_slug.strip().lower() for category in categories: if category["name"].lower() == wanted or category["slug"].lower() == wanted: return category return None def _base_cast_counts(women_count: int, men_count: int) -> tuple[int, int]: women_count = max(0, int(women_count)) men_count = max(0, int(men_count)) if women_count + men_count == 0: women_count = 1 return women_count, men_count def _counts_for_exact_subcategory( subcategory: dict[str, Any], women_count: int, men_count: int, ) -> tuple[int, int]: women_count, men_count = _base_cast_counts(women_count, men_count) min_women = _constraint_int(subcategory, "min_women") if min_women is not None and women_count < min_women: women_count = min_women min_men = _constraint_int(subcategory, "min_men") if min_men is not None and men_count < min_men: men_count = min_men min_people = _constraint_int(subcategory, "min_people") if min_people is not None: missing = min_people - (women_count + men_count) if missing > 0: if women_count > 0 or men_count == 0: women_count += missing else: men_count += missing return women_count, men_count def _find_subcategory( categories: list[dict[str, Any]], category_choice: str, subcategory_choice: str, category_rng: random.Random, subcategory_rng: random.Random, women_count: int = 1, men_count: int = 1, ) -> tuple[dict[str, Any], dict[str, Any], int, int]: women_count, men_count = _base_cast_counts(women_count, men_count) if subcategory_choice and subcategory_choice != RANDOM_SUBCATEGORY and " / " in subcategory_choice: category_name, subcategory_name = subcategory_choice.split(" / ", 1) category = _find_category(categories, category_name) if not category: raise ValueError(f"Unknown category in subcategory picker: {category_name}") wanted = subcategory_name.strip().lower() for subcategory in category["subcategories"]: if subcategory["name"].lower() == wanted or subcategory["slug"].lower() == wanted: adjusted_women_count, adjusted_men_count = _counts_for_exact_subcategory( subcategory, women_count, men_count, ) if not _compatible_entry(subcategory, adjusted_women_count, adjusted_men_count): raise ValueError( f"Subcategory '{subcategory['name']}' is not compatible with " f"women_count={women_count}, men_count={men_count}" ) return category, subcategory, adjusted_women_count, adjusted_men_count raise ValueError(f"Unknown subcategory '{subcategory_name}' for category '{category_name}'") if category_choice == "custom_random": if not categories: raise ValueError("No custom categories found in categories/*.json") category = _weighted_choice(category_rng, categories) else: category = _find_category(categories, category_choice) if not category: raise ValueError(f"Unknown custom category: {category_choice}") subcategories = _compatible_entries(category["subcategories"], women_count, men_count) subcategory = _weighted_choice(subcategory_rng, subcategories) return category, subcategory, women_count, men_count def _merged_field(category: dict[str, Any], subcategory: dict[str, Any], item: Any, key: str, default: Any = None) -> Any: if isinstance(item, dict) and key in item: return item[key] if key in subcategory: return subcategory[key] if key in category: return category[key] return default def _body_phrase(body: Any, figure_note: Any = "") -> str: body = str(body or "").strip() figure_note = str(figure_note or "").strip() 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 _appearance_for_subject( rng: random.Random, subject_type: str, ethnicity: str, figure: str, no_plus_women: bool, no_black: bool, ) -> dict[str, str]: if subject_type == "single_any": subject_type = "woman" if rng.random() < 0.82 else "man" if subject_type == "man": men_ethnicity = ethnicity if ethnicity == "asian" else "any" subject, age, body, skin, hair, eyes = g.choose(rng, g.by_ethnicity(g.MEN, men_ethnicity)) return { "subject_type": "man", "subject": subject, "subject_phrase": subject, "age": age, "body": body, "skin": skin, "hair": hair, "eyes": eyes, "body_phrase": f"{body} figure", } subject, age, body, skin, hair, eyes = g.choose_woman(rng, ethnicity, no_plus_women, no_black) figure_note = g.choose(rng, g.figure_pool(figure)) return { "subject_type": "woman", "subject": subject, "subject_phrase": subject, "age": age, "body": body, "skin": skin, "hair": hair, "eyes": eyes, "body_phrase": _body_phrase(body, figure_note), "figure": figure_note, } def _count_phrase(count: int, singular: str, plural: str) -> str: words = { 0: "no", 1: "one", 2: "two", 3: "three", 4: "four", 5: "five", 6: "six", 7: "seven", 8: "eight", 9: "nine", 10: "ten", 11: "eleven", 12: "twelve", } label = singular if count == 1 else plural return f"{words.get(count, str(count))} {label}" def _configured_cast_context(women_count: int, men_count: int) -> dict[str, str]: women_count = max(0, int(women_count)) men_count = max(0, int(men_count)) if women_count + men_count == 0: women_count = 1 parts = [] if women_count: parts.append(_count_phrase(women_count, "adult woman", "adult women")) if men_count: parts.append(_count_phrase(men_count, "adult man", "adult men")) if len(parts) == 1: subject_phrase = parts[0] else: subject_phrase = f"{parts[0]} and {parts[1]}" person_count = women_count + men_count if person_count == 1: scene_kind = "solo adult sexual pose" elif person_count == 2: scene_kind = "adult couple sex scene" elif person_count == 3: scene_kind = "adult threesome sex scene" else: scene_kind = "adult group sex scene" women_label = "woman" if women_count == 1 else "women" men_label = "man" if men_count == 1 else "men" cast_summary = f"{women_count} {women_label}, {men_count} {men_label}, {person_count} total adults" return { "subject_type": "configured_cast", "subject": f"{women_count}w_{men_count}m_sex_scene", "subject_phrase": subject_phrase, "age": "21+ adults", "body": "varied", "skin": "", "hair": "", "eyes": "", "body_phrase": "varied adult bodies", "women_count": str(women_count), "men_count": str(men_count), "person_count": str(person_count), "cast_summary": cast_summary, "scene_kind": scene_kind, } def _lettered(prefix: str, count: int) -> list[str]: letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" return [f"{prefix.capitalize()} {letters[index]}" for index in range(max(0, count))] def _pick_distinct(rng: random.Random, items: list[str], count: int) -> list[str]: if not items: return [] if len(items) >= count: return rng.sample(items, count) picked = list(items) while len(picked) < count: picked.append(items[rng.randrange(len(items))]) return picked def _participant_context(women_count: int, men_count: int) -> dict[str, list[str]]: women = _lettered("woman", women_count) men = _lettered("man", men_count) return {"women": women, "men": men, "people": women + men} def _role_graph( rng: random.Random, subcategory: dict[str, Any], context: dict[str, str], item_axis_values: dict[str, str] | None = None, ) -> str: if context.get("subject_type") != "configured_cast": return "" women_count = int(context.get("women_count") or 0) men_count = int(context.get("men_count") or 0) people_count = women_count + men_count if people_count <= 0: return "" participants = _participant_context(women_count, men_count) women = participants["women"] men = participants["men"] people = participants["people"] slug = str(subcategory.get("slug") or subcategory.get("name") or "").lower() item_text = " ".join((item_axis_values or {}).values()).lower() def any_person(exclude: set[str] | None = None) -> str: exclude = exclude or set() pool = [person for person in people if person not in exclude] or people return rng.choice(pool) def any_woman(exclude: set[str] | None = None) -> str: exclude = exclude or set() pool = [person for person in women if person not in exclude] or [person for person in people if person not in exclude] or people return rng.choice(pool) def any_man(exclude: set[str] | None = None) -> str: exclude = exclude or set() pool = [person for person in men if person not in exclude] or [person for person in people if person not in exclude] or people return rng.choice(pool) def support_sentence(exclude: set[str]) -> str: extras = [person for person in people if person not in exclude] if not extras: return "" extra = rng.choice(extras) actions = [ "kisses and grips the nearest body", "holds hips open for the camera", "touches breasts, thighs, and stomach", "keeps one hand on a partner's ass", "watches close and joins the body contact", "presses in from the side with hands on skin", ] return f" {extra} {rng.choice(actions)}." if people_count == 1: solo = people[0] if women_count == 1: if "cumshot" in slug or "climax" in slug: return f"{solo} is shown in a solo explicit climax pose with thighs open, one hand on her body, and visible arousal on skin and sheets." return f"{solo} is shown in a solo explicit adult pose with self-touch, open body framing, and direct camera awareness." if "cumshot" in slug or "climax" in slug: return f"{solo} is shown in a solo explicit climax pose with one hand on his cock, body angled toward the camera, and visible ejaculation detail." return f"{solo} is shown in a solo explicit adult pose with direct camera awareness and clear body framing." if women_count > 0 and men_count == 0: a, b = _pick_distinct(rng, women, 2) c = any_woman({a, b}) if len(women) >= 3 else "" used = {a, b} if "oral" in slug: graph = f"{a} kneels between {b}'s spread thighs and uses tongue and fingers on her pussy." elif "anal" in slug or "double" in slug: graph = f"{a} uses a strap-on or toy on {b} while keeping her hips held open." elif "threesome" in slug or "group" in slug or "orgy" in slug: helper = c or any_woman({a}) graph = f"{a} uses a strap-on on {b} while {helper} gives oral contact and touches both bodies." used.add(helper) elif "cumshot" in slug or "climax" in slug: graph = f"{a} brings {b} to orgasm with mouth and fingers while wetness is visible on thighs and sheets." else: graph = f"{a} uses a strap-on or toy on {b} while their bodies stay pressed together." return graph + support_sentence(used) if men_count > 0 and women_count == 0: a, b = _pick_distinct(rng, men, 2) c = any_man({a, b}) if len(men) >= 3 else "" used = {a, b} if "oral" in slug: graph = f"{a} kneels and takes {b}'s cock in his mouth while holding his hips." elif "anal" in slug or "double" in slug or "penetrative" in slug: graph = f"{a} penetrates {b} anally while {b}'s hips are held open." elif "threesome" in slug or "group" in slug or "orgy" in slug: helper = c or any_man({a}) graph = f"{a} penetrates {b} anally while {helper} gives oral contact from the front." used.add(helper) elif "cumshot" in slug or "climax" in slug: graph = f"{a} climaxes over {b}'s body while {b} keeps eye contact and one hand on his cock." else: graph = f"{a} and {b} keep explicit cock and anal contact visible." return graph + support_sentence(used) # Mixed cast. woman = any_woman() man = any_man() third = any_person({woman, man}) if people_count >= 3 else "" if "oral" in slug: graph = f"{woman} gives oral to {man} while {man} holds her hair and hips." elif "anal" in slug or "double" in slug: if "double" in item_text or "toy" in item_text: if people_count >= 3: graph = f"{man} penetrates {woman} while {third} adds a second point of contact from the front." else: graph = f"{man} penetrates {woman} while a toy adds a second point of contact." elif people_count >= 3: graph = f"{man} penetrates {woman} while {third} gives oral contact from the front." else: graph = f"{man} penetrates {woman} anally while keeping her hips held open." elif "threesome" in slug: graph = f"{man} penetrates {woman} while {third or any_person({woman, man})} uses mouth or hands on the exposed body." elif "group" in slug or "orgy" in slug: graph = f"{man} penetrates {woman} while surrounding partners give oral contact and keep hands on hips, breasts, and thighs." elif "cumshot" in slug or "climax" in slug: graph = f"{man} climaxes on {woman}'s body while {woman} stays posed with thighs open and direct eye contact." else: graph = f"{man} and {woman} keep penetration and body contact visible." return graph + support_sentence({woman, man, third} if third else {woman, man}) def _subject_context( rng: random.Random, subject_type: str, ethnicity: str, figure: str, no_plus_women: bool, no_black: bool, women_count: int = 1, men_count: int = 1, ) -> dict[str, str]: if subject_type in ("woman", "man", "single_any"): return _appearance_for_subject(rng, subject_type, ethnicity, figure, no_plus_women, no_black) if subject_type == "configured_cast": return _configured_cast_context(women_count, men_count) if subject_type == "couple": primary_subject, subject_phrase, pose = g.choose(rng, g.COUPLE_TYPES) return { "subject_type": "couple", "subject": primary_subject, "subject_phrase": subject_phrase, "age": g.choose(rng, g.COUPLE_AGES), "body": g.choose(rng, ["slim and average", "curvy and broad", "stocky and curvy", "average and athletic"]), "skin": "", "hair": "", "eyes": "", "body_phrase": "", "fallback_pose": pose, } if subject_type == "group": eth = "Asian " if ethnicity == "asian" else "" return { "subject_type": "group", "subject": f"mixed {eth}adult group", "subject_phrase": f"A mixed {eth}adult group of women and men", "age": g.choose(rng, g.GROUP_AGES), "body": "diverse", "skin": "", "hair": "", "eyes": "", "body_phrase": "diverse adult body types", } return { "subject_type": subject_type, "subject": "layout scene", "subject_phrase": "Adult layout scene", "age": "adult", "body": "varied", "skin": "", "hair": "", "eyes": "", "body_phrase": "varied adult figures", } def _scene_pool(category: dict[str, Any], subcategory: dict[str, Any], item: Any, subject_type: str) -> list[Any]: fallback = g.GROUP_SCENES if subject_type in ("group", "configured_cast") else g.SCENES scene_entries: list[Any] = [] scene_pools = load_scene_pool_library() item_source = item if isinstance(item, dict) else None if item_source is not None and _is_false(item_source.get("inherit_scenes")): sources = (item_source,) elif _is_false(subcategory.get("inherit_scenes")): sources = (subcategory, item_source) else: sources = (category, subcategory, item_source) for source in sources: if not isinstance(source, dict): continue if "scenes" in source: _unique_extend(scene_entries, _list_from(source["scenes"])) refs = _list_from(source.get("scene_pool")) + _list_from(source.get("scene_pools")) for ref in refs: ref_name = str(ref).strip() if ref_name not in scene_pools: raise ValueError(f"Unknown scene pool '{ref_name}'") _unique_extend(scene_entries, scene_pools[ref_name]) return scene_entries or fallback def _sources_with_inheritance( category: dict[str, Any], subcategory: dict[str, Any], item: Any, inherit_key: str, ) -> tuple[Any, ...]: item_source = item if isinstance(item, dict) else None if item_source is not None and _is_false(item_source.get(inherit_key)): return (item_source,) if _is_false(subcategory.get(inherit_key)): return (subcategory, item_source) return (category, subcategory, item_source) def _configured_pool( category: dict[str, Any], subcategory: dict[str, Any], item: Any, direct_key: str, pool_key: str, pool_library: dict[str, list[Any]], inherit_key: str, ) -> list[Any]: entries: list[Any] = [] singular_pool_key = pool_key[:-1] if pool_key.endswith("s") else pool_key for source in _sources_with_inheritance(category, subcategory, item, inherit_key): if not isinstance(source, dict): continue if direct_key in source: _unique_extend(entries, _list_from(source[direct_key])) refs = _list_from(source.get(singular_pool_key)) + _list_from(source.get(pool_key)) for ref in refs: ref_name = str(ref).strip() if ref_name not in pool_library: raise ValueError(f"Unknown {singular_pool_key} '{ref_name}'") _unique_extend(entries, pool_library[ref_name]) return entries def _expression_pool(category: dict[str, Any], subcategory: dict[str, Any], item: Any) -> list[Any]: return _configured_pool( category, subcategory, item, "expressions", "expression_pools", load_expression_pool_library(), "inherit_expressions", ) or g.EXPRESSIONS def _expression_intensity_hint(entry: Any) -> float: if isinstance(entry, dict): for key in ("expression_intensity", "intensity"): if key in entry: return _clamped_float(entry[key], 0.5) text = _entry_text(entry).lower() high_terms = ( "ahegao", "orgasm", "climax", "drool", "drooling", "tongue out", "eyes rolled", "fucked-out", "cum-smeared", "saliva", "gagging", "slack jaw", "jaw slack", "slack-jawed", "sex-drunk", "overwhelmed", "strained", "messy", "panting", "trembling", "shaking", "wide open mouth", "raw ", "wild ", "dazed", "spent", ) if any(term in text for term in high_terms): return 0.9 medium_terms = ( "seductive", "teasing", "lustful", "aroused", "bedroom", "dominant", "predatory", "control", "stern", "strict", "smirk", "parted lips", "open-mouthed", "heated", "hungry", "inviting", "sensual", "fetish", "commanding", "flushed", "moan", ) if any(term in text for term in medium_terms): return 0.62 low_terms = ( "neutral", "quiet", "calm", "reserved", "relaxed", "candid", "closed-mouth", "thoughtful", "controlled", "focused", "steady", "bitten-lip", "braced", "held breath", "concentrated", "aloof", "bored", "tired", "unfocused", "contented", "fashion", "soft", "sleepy", "fresh-faced", ) if any(term in text for term in low_terms): return 0.25 return 0.5 def _expression_entries_for_intensity(entries: list[Any], expression_intensity: float) -> list[Any]: target = _clamped_float(expression_intensity, 0.5) weighted: list[Any] = [] for entry in entries: entry_intensity = _expression_intensity_hint(entry) distance = abs(target - entry_intensity) if distance <= 0.18: intensity_weight = 4.0 elif distance <= 0.35: intensity_weight = 1.4 elif distance <= 0.55: intensity_weight = 0.35 else: intensity_weight = 0.05 if isinstance(entry, dict): adjusted = dict(entry) try: base_weight = float(adjusted.get("weight", 1.0)) except (TypeError, ValueError): base_weight = 1.0 adjusted["weight"] = max(0.0, base_weight) * intensity_weight weighted.append(adjusted) else: weighted.append({"text": _entry_text(entry), "weight": intensity_weight}) return weighted or entries def _pose_pool(category: dict[str, Any], subcategory: dict[str, Any], item: Any, subject_type: str, poses: str) -> list[Any]: configured = _merged_field(category, subcategory, item, "poses") if configured: return _list_from(configured) if subject_type == "couple": return [entry[2] for entry in g.COUPLE_TYPES] if subject_type in ("layout", "scene"): return ["clean designed layout"] return g.EVOCATIVE_ALL if poses == "evocative" else g.POSES def _composition_pool(category: dict[str, Any], subcategory: dict[str, Any], item: Any, subject_type: str) -> list[Any]: configured = _configured_pool( category, subcategory, item, "compositions", "composition_pools", load_composition_pool_library(), "inherit_compositions", ) if configured: return configured if subject_type in ("group", "configured_cast"): return g.GROUP_COMPOSITIONS if subject_type in ("layout", "scene"): return ["designed illustration layout"] return g.COMPOSITIONS def _build_custom_row( category_choice: str, subcategory_choice: str, row_number: int, start_index: int, ethnicity: str, poses: str, figure: str, no_plus_women: bool, no_black: bool, women_count: int, men_count: int, seed: int, seed_config: dict[str, int], expression_intensity: float, ) -> dict[str, Any]: categories = load_category_library() category_rng = _axis_rng(seed_config, "category", seed, row_number) subcategory_rng = _axis_rng(seed_config, "subcategory", seed, row_number) person_rng = _axis_rng(seed_config, "person", seed, row_number) scene_rng = _axis_rng(seed_config, "scene", seed, row_number) pose_rng = _axis_rng(seed_config, "pose", seed, row_number) role_rng = _axis_rng(seed_config, "role", seed, row_number) expression_rng = _axis_rng(seed_config, "expression", seed, row_number) composition_rng = _axis_rng(seed_config, "composition", seed, row_number) requested_women_count = women_count requested_men_count = men_count category, subcategory, women_count, men_count = _find_subcategory( categories, category_choice, subcategory_choice, category_rng, subcategory_rng, women_count, men_count, ) count_adjustment = {} if women_count != requested_women_count or men_count != requested_men_count: count_adjustment = { "requested_women_count": requested_women_count, "requested_men_count": requested_men_count, "effective_women_count": women_count, "effective_men_count": men_count, } content_axis = "pose" if _is_pose_content_category(category, subcategory) else "content" content_rng = _axis_rng(seed_config, content_axis, seed, row_number) items = _list_from(subcategory.get("items", [subcategory["name"]])) item = _weighted_choice(content_rng, items) item_text, item_name, item_axis_values = _compose_item(content_rng, category, subcategory, item, women_count, men_count) subject_type = str(_merged_field(category, subcategory, item, "subject_type", "single_any")) context = _subject_context(person_rng, subject_type, ethnicity, figure, no_plus_women, no_black, women_count, men_count) subject_type = context["subject_type"] role_graph = _role_graph(role_rng, subcategory, context, item_axis_values) scene_slug, scene = _choose_pair(scene_rng, _compatible_entries(_scene_pool(category, subcategory, item, subject_type), women_count, men_count)) pose = str(_merged_field(category, subcategory, item, "pose", "") or context.get("fallback_pose") or _choose_text( pose_rng, _compatible_entries(_pose_pool(category, subcategory, item, subject_type, poses), women_count, men_count) )) expression_entries = _compatible_entries( _expression_entries_for_intensity(_expression_pool(category, subcategory, item), expression_intensity), women_count, men_count, ) expression = _choose_text(expression_rng, expression_entries) if subject_type in ("couple", "group") and ";" not in expression: expression = f"{expression}; {_choose_text(expression_rng, expression_entries)}" composition = _choose_text( composition_rng, _compatible_entries(_composition_pool(category, subcategory, item, subject_type), women_count, men_count), ) negative_prompt = str(_merged_field(category, subcategory, item, "negative_prompt", g.NEGATIVE_PROMPT)) positive_suffix = str(_merged_field(category, subcategory, item, "positive_suffix", GENERIC_POSITIVE_SUFFIX)) style = str( _merged_field( category, subcategory, item, "style", "sexy but tasteful adult pin-up coloured-pencil comic illustration", ) ) item_label = str(_merged_field(category, subcategory, item, "item_label", category["name"])) context.update( { "trigger": g.TRIGGER, "main_category": category["name"], "subcategory": subcategory["name"], "category": category["name"], "item": item_text, "item_name": item_name, "item_label": item_label, "style": style, "scene": scene, "scene_slug": scene_slug, "pose": pose, "expression": expression, "expression_intensity": expression_intensity, "composition": composition, "role_graph": role_graph, "positive_suffix": positive_suffix, "negative_prompt": negative_prompt, } ) if isinstance(item, dict) and "prompt_template" in item: template = str(item["prompt_template"]) else: template = str(subcategory.get("prompt_template") or category.get("prompt_template") or "") if not template: if subject_type in ("woman", "man"): template = SINGLE_TEMPLATE elif subject_type == "couple": template = COUPLE_TEMPLATE elif subject_type == "group": template = GROUP_TEMPLATE else: template = LAYOUT_TEMPLATE caption_template = str( (item.get("caption_template") if isinstance(item, dict) else None) or subcategory.get("caption_template") or category.get("caption_template") or "{trigger}, {subject_phrase}, {age}, {item}, {scene}, {composition}, coloured pencil comic illustration" ) prompt = _format(template, context) caption = _format(caption_template, context) batch = max(1, ((row_number - 1) // g.BATCH_SIZE) + 1) index = start_index + row_number - 1 row = g.row_base(index, batch, context["subject"], context["age"], context["body"], scene_slug, composition) row.update( { "prompt": prompt, "caption": caption, "negative_prompt": negative_prompt, "expression": expression, "main_category": category["name"], "subcategory": subcategory["name"], "category_slug": category["slug"], "subcategory_slug": subcategory["slug"], "subject_type": subject_type, "subject_phrase": context.get("subject_phrase", ""), "body_phrase": context.get("body_phrase", ""), "skin": context.get("skin", ""), "hair": context.get("hair", ""), "eyes": context.get("eyes", ""), "style": style, "item": item_text, "item_label": item_label, "positive_suffix": positive_suffix, "custom_item": item_name, "item_axis_values": item_axis_values, "scene_text": scene, "pose": pose, "seed_config": seed_config, "content_seed_axis": content_axis, "role_graph": role_graph, "cast_summary": context.get("cast_summary", ""), "scene_kind": context.get("scene_kind", ""), "women_count": context.get("women_count", ""), "men_count": context.get("men_count", ""), "person_count": context.get("person_count", ""), "cast_count_adjustment": count_adjustment if subject_type == "configured_cast" else {}, "source": "json_category", } ) if context.get("figure"): row["figure"] = context["figure"] return row def build_prompt( category: str, subcategory: str, row_number: int, start_index: int, seed: int, clothing: str, ethnicity: str, poses: str, backside_bias: float, figure: str, no_plus_women: bool, no_black: bool, minimal_clothing_ratio: float, standard_pose_ratio: float, trigger: str, prepend_trigger_to_prompt: bool, extra_positive: str, extra_negative: str, seed_config: str | dict[str, Any] | None = None, women_count: int = 1, men_count: int = 1, camera_config: str | dict[str, Any] | None = None, expression_intensity: float = 0.5, ) -> dict[str, Any]: apply_pool_extensions() row_number = max(1, int(row_number)) 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" 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) pose_ratio = _ratio_or_none(standard_pose_ratio) expression_intensity = _clamped_float(expression_intensity, 0.5) parsed_seed_config = _parse_seed_config(seed_config) exact_custom_subcategory = bool(subcategory and subcategory != RANDOM_SUBCATEGORY and " / " in subcategory) if category == "auto_weighted" and not exact_custom_subcategory: row = _build_auto_weighted_row( row_number, start_index, clothing, ethnicity, poses, float(backside_bias), figure, bool(no_plus_women), bool(no_black), minimal_ratio, pose_ratio, seed, ) elif category in ("woman", "man", "couple", "group_or_layout") and not exact_custom_subcategory: row = _build_direct_builtin_row( category, row_number, start_index, clothing, ethnicity, poses, float(backside_bias), figure, bool(no_plus_women), bool(no_black), minimal_ratio, pose_ratio, seed, ) else: row = _build_custom_row( category, subcategory, row_number, start_index, ethnicity, poses, figure, bool(no_plus_women), bool(no_black), int(women_count), int(men_count), seed, parsed_seed_config, expression_intensity, ) if extra_positive.strip(): row["prompt"] = f"{row['prompt'].rstrip()} {extra_positive.strip()}" row = _apply_camera_config(row, camera_config) active_trigger = trigger.strip() or g.TRIGGER row["prompt"] = _prepend_trigger(row["prompt"], active_trigger, bool(prepend_trigger_to_prompt)) row["negative_prompt"] = _combined_negative(row.get("negative_prompt", g.NEGATIVE_PROMPT), extra_negative) row["trigger"] = active_trigger row["expression_intensity"] = expression_intensity return row INSTA_OF_SOFT_LEVELS = { "social_tease": "Instagram-style thirst-trap post, suggestive but non-explicit, polished social feed energy", "lingerie_tease": "premium OF teaser set, lingerie-focused, sensual and intimate but without explicit sex", "implied_nude": "implied nude creator set, strategically covered body, sensual but no visible sex act", "explicit_tease": "explicit adult tease, nudity can be visible, but no penetration or partnered sex act", } INSTA_OF_HARDCORE_LEVELS = { "explicit": "explicit adult creator content with clear sexual contact and adult-only framing", "hardcore": "hardcore adult creator content with anatomically clear sexual contact and intense body language", } INSTA_OF_PLATFORM_STYLES = { "hybrid": "hybrid Instagram-to-OF creator shoot, polished social-media framing with intimate subscriber-content energy", "instagram": "Instagram-inspired creator shoot, polished mirror-selfie and feed-post aesthetics", "onlyfans": "OnlyFans-inspired creator shoot, intimate subscriber-view camera and candid premium-content framing", } 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" ) INSTA_OF_SOFT_NEGATIVE = ( INSTA_OF_NEGATIVE + ", explicit intercourse, penetration, oral sex, cumshot, genital contact, group sex" ) def build_insta_of_options_json( softcore_cast: str = "solo", hardcore_cast: str = "use_counts", hardcore_women_count: int = 1, hardcore_men_count: int = 1, softcore_level: str = "lingerie_tease", hardcore_level: str = "hardcore", platform_style: str = "hybrid", continuity: str = "same_creator_same_room", softcore_camera_mode: str = "handheld_selfie", hardcore_camera_mode: str = "same_as_softcore", softcore_expression_intensity: float = 0.45, hardcore_expression_intensity: float = 0.85, ) -> str: return json.dumps( { "softcore_cast": softcore_cast, "hardcore_cast": hardcore_cast, "hardcore_women_count": int(hardcore_women_count), "hardcore_men_count": int(hardcore_men_count), "softcore_level": softcore_level, "hardcore_level": hardcore_level, "platform_style": platform_style, "continuity": continuity, "softcore_camera_mode": softcore_camera_mode, "hardcore_camera_mode": hardcore_camera_mode, "softcore_expression_intensity": _clamped_float(softcore_expression_intensity, 0.45), "hardcore_expression_intensity": _clamped_float(hardcore_expression_intensity, 0.85), }, ensure_ascii=True, sort_keys=True, ) def _parse_insta_of_options(options_json: str | dict[str, Any] | None) -> dict[str, Any]: defaults = { "softcore_cast": "solo", "hardcore_cast": "use_counts", "hardcore_women_count": 1, "hardcore_men_count": 1, "softcore_level": "lingerie_tease", "hardcore_level": "hardcore", "platform_style": "hybrid", "continuity": "same_creator_same_room", "softcore_camera_mode": "handheld_selfie", "hardcore_camera_mode": "same_as_softcore", "softcore_expression_intensity": 0.45, "hardcore_expression_intensity": 0.85, } if not options_json: return defaults if isinstance(options_json, dict): raw = options_json else: try: raw = json.loads(str(options_json)) except json.JSONDecodeError as exc: raise ValueError(f"Invalid Insta/OF options JSON: {exc}") from exc if not isinstance(raw, dict): raise ValueError("Insta/OF options must be a JSON object") parsed = {**defaults, **raw} parsed["softcore_cast"] = parsed["softcore_cast"] if parsed["softcore_cast"] in ("solo", "same_as_hardcore") else defaults["softcore_cast"] parsed["hardcore_cast"] = parsed["hardcore_cast"] if parsed["hardcore_cast"] in ("use_counts", "couple", "threesome", "group") else defaults["hardcore_cast"] parsed["softcore_level"] = parsed["softcore_level"] if parsed["softcore_level"] in INSTA_OF_SOFT_LEVELS else defaults["softcore_level"] 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["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["softcore_expression_intensity"] = _clamped_float( parsed.get("softcore_expression_intensity"), defaults["softcore_expression_intensity"], ) parsed["hardcore_expression_intensity"] = _clamped_float( parsed.get("hardcore_expression_intensity"), defaults["hardcore_expression_intensity"], ) for key in ("hardcore_women_count", "hardcore_men_count"): try: parsed[key] = max(0, min(12, int(parsed[key]))) except (TypeError, ValueError): parsed[key] = defaults[key] return parsed def _insta_of_hardcore_counts(options: dict[str, Any]) -> tuple[int, int]: policy = str(options.get("hardcore_cast", "use_counts")) if policy == "couple": women_count, men_count = 1, 1 elif policy == "threesome": women_count, men_count = 2, 1 elif policy == "group": women_count, men_count = 3, 2 else: women_count = int(options.get("hardcore_women_count") or 0) men_count = int(options.get("hardcore_men_count") or 0) women_count = max(1, min(12, women_count)) men_count = max(0, min(12, men_count)) if women_count + men_count < 2: men_count = 1 return women_count, men_count def _insta_of_descriptor(row: dict[str, Any]) -> str: age = str(row.get("age_band") or row.get("age") or "").strip() age = " ".join(age.split()) age = age.removesuffix(" adults").removesuffix(" adult").strip() pieces = [ f"{age} adult woman" if age else "adult woman", row.get("body_phrase"), row.get("skin"), row.get("hair"), row.get("eyes"), ] return ", ".join(str(piece).strip() for piece in pieces if piece and str(piece).strip()) def _insta_of_descriptor_from_context(context: dict[str, Any]) -> str: age = str(context.get("age") or "").strip() age = " ".join(age.split()) age = age.removesuffix(" adults").removesuffix(" adult").strip() subject = str(context.get("subject") or context.get("subject_type") or "person").strip() pieces = [ f"{age} adult {subject}".strip(), context.get("body_phrase"), context.get("skin"), context.get("hair"), context.get("eyes"), ] return ", ".join(str(piece).strip() for piece in pieces if piece and str(piece).strip()) def _insta_of_cast_descriptors( primary_descriptor: str, seed_config: dict[str, int], seed: int, row_number: int, ethnicity: str, figure: str, no_plus_women: bool, no_black: bool, women_count: int, men_count: int, ) -> list[str]: descriptors = [f"Woman A / primary creator: {primary_descriptor}"] rng = _axis_rng(seed_config, "person", seed, row_number + 997) for index in range(max(0, women_count - 1)): label = chr(ord("B") + index) context = _appearance_for_subject(rng, "woman", ethnicity, figure, no_plus_women, no_black) descriptors.append(f"Woman {label}: {_insta_of_descriptor_from_context(context)}") for index in range(max(0, men_count)): label = chr(ord("A") + index) context = _appearance_for_subject(rng, "man", ethnicity, figure, no_plus_women, no_black) descriptors.append(f"Man {label}: {_insta_of_descriptor_from_context(context)}") return descriptors def _insta_of_cast_phrase(women_count: int, men_count: int) -> str: context = _configured_cast_context(women_count, men_count) return context["cast_summary"] 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", "standing near the phone tripod with relaxed teasing body language", "framed together in a softcore cast reveal with no sex act", ] def _insta_of_partner_styling( seed_config: dict[str, int], seed: int, row_number: int, women_count: int, men_count: int, ) -> dict[str, Any]: content_rng = _axis_rng(seed_config, "content", seed, row_number + 421) pose_rng = _axis_rng(seed_config, "pose", seed, row_number + 421) outfits: list[str] = [] for index in range(max(0, women_count - 1)): label = chr(ord("B") + index) outfits.append(f"Woman {label} wears {g.choose(content_rng, g.WOMEN_CLOTHES_MINIMAL)}") for index in range(max(0, men_count)): label = chr(ord("A") + index) outfits.append(f"Man {label} wears {g.choose(content_rng, g.MEN_CLOTHES_MINIMAL)}") return { "outfits": outfits, "pose": g.choose(pose_rng, SOFTCORE_CAST_POSES), } def _insta_of_active_trigger(prompt: str, trigger: str, enabled: bool) -> str: return _prepend_trigger(prompt, trigger, enabled) def build_insta_of_pair( row_number: int, start_index: int, seed: int, ethnicity: str, figure: str, no_plus_women: bool, no_black: bool, trigger: str, prepend_trigger_to_prompt: bool, seed_config: str | dict[str, Any] | None = None, options_json: str | dict[str, Any] | None = None, camera_config: str | dict[str, Any] | None = None, extra_positive: str = "", extra_negative: str = "", ) -> dict[str, Any]: options = _parse_insta_of_options(options_json) 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) soft_row = build_prompt( category="Provocative erotic clothes", subcategory=RANDOM_SUBCATEGORY, row_number=row_number, start_index=start_index, seed=seed, clothing="minimal", ethnicity=ethnicity, poses="evocative", backside_bias=0.0, figure=figure, no_plus_women=no_plus_women, no_black=no_black, minimal_clothing_ratio=-1, standard_pose_ratio=-1, trigger=active_trigger, prepend_trigger_to_prompt=False, extra_positive="", extra_negative="", seed_config=parsed_seed_config, women_count=1, men_count=0, expression_intensity=options["softcore_expression_intensity"], ) hard_row = build_prompt( category="Hardcore sexual poses", subcategory=RANDOM_SUBCATEGORY, row_number=row_number, start_index=start_index, seed=seed, clothing="minimal", ethnicity=ethnicity, poses="evocative", backside_bias=0.0, figure=figure, no_plus_women=no_plus_women, no_black=no_black, minimal_clothing_ratio=-1, standard_pose_ratio=-1, trigger=active_trigger, prepend_trigger_to_prompt=False, extra_positive="", extra_negative="", seed_config=parsed_seed_config, women_count=hard_women_count, men_count=hard_men_count, expression_intensity=options["hardcore_expression_intensity"], ) descriptor = _insta_of_descriptor(soft_row) cast_descriptors = _insta_of_cast_descriptors( descriptor, parsed_seed_config, seed, row_number, ethnicity, figure, no_plus_women, no_black, hard_women_count, hard_men_count, ) cast_descriptor_text = "; ".join(cast_descriptors) soft_cast_descriptor_text = ( cast_descriptor_text if options["softcore_cast"] == "same_as_hardcore" else f"Woman A / primary creator: {descriptor}" ) soft_partner_styling = _insta_of_partner_styling( parsed_seed_config, seed, row_number, hard_women_count if options["softcore_cast"] == "same_as_hardcore" else 1, hard_men_count if options["softcore_cast"] == "same_as_hardcore" else 0, ) if options["softcore_cast"] != "same_as_hardcore": soft_partner_styling = {"outfits": [], "pose": ""} soft_partner_outfit_text = "; ".join(soft_partner_styling["outfits"]) platform_style = INSTA_OF_PLATFORM_STYLES[options["platform_style"]] soft_level = INSTA_OF_SOFT_LEVELS[options["softcore_level"]] hard_level = INSTA_OF_HARDCORE_LEVELS[options["hardcore_level"]] hard_camera_mode = options["hardcore_camera_mode"] if hard_camera_mode == "same_as_softcore": 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_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"] soft_cast = ( "solo creator setup; the primary creator is alone in the softcore version" 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)}" ) soft_cast_presence = ( "Show the same listed adult cast together in the softcore version, present in the same location 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']}. " 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) 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"{soft_cast_presence}" f"{soft_cast_styling_sentence}" f"Outfit: {soft_row['item']}. Pose: {soft_row['pose']}. Setting: {soft_row['scene_text']}. " f"Facial expression: {soft_row['expression']}. Composition: {soft_row['composition']}. " f"{soft_camera_sentence}" "Keep the softcore version adult-only, consensual, seductive, creator-shot, and non-explicit. " f"{soft_row['positive_suffix']} Avoid: {INSTA_OF_SOFT_NEGATIVE}." ) hard_prompt = ( f"Insta/OF hardcore mode: {platform_style}. Shared primary creator descriptor: {descriptor}. " 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"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}" "All participants are consenting adults 21+. " f"{hard_row['positive_suffix']} Avoid: {INSTA_OF_NEGATIVE}." ) if extra_positive.strip(): soft_prompt = f"{soft_prompt.rstrip()} {extra_positive.strip()}" hard_prompt = f"{hard_prompt.rstrip()} {extra_positive.strip()}" soft_prompt = _insta_of_active_trigger(soft_prompt, active_trigger, bool(prepend_trigger_to_prompt)) hard_prompt = _insta_of_active_trigger(hard_prompt, active_trigger, bool(prepend_trigger_to_prompt)) soft_negative = _combined_negative(INSTA_OF_SOFT_NEGATIVE, extra_negative) hard_negative = _combined_negative(INSTA_OF_NEGATIVE, extra_negative) soft_caption_parts = [ active_trigger, "Insta/OF softcore mode", descriptor, soft_level, soft_row["item"], soft_row["pose"], soft_partner_outfit_text, soft_partner_styling["pose"], soft_row["scene_text"], soft_row["composition"], f"{soft_camera_config['camera_mode'].replace('_', ' ')} camera", ] 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" ) metadata = { "mode": "Insta/OF", "options": options, "shared_descriptor": descriptor, "shared_cast_descriptors": cast_descriptors, "softcore_partner_styling": soft_partner_styling, "softcore_prompt": soft_prompt, "hardcore_prompt": hard_prompt, "softcore_negative_prompt": soft_negative, "hardcore_negative_prompt": hard_negative, "softcore_caption": soft_caption, "hardcore_caption": hard_caption, "softcore_row": soft_row, "hardcore_row": hard_row, "hardcore_women_count": hard_women_count, "hardcore_men_count": hard_men_count, "softcore_camera_config": soft_camera_config, "hardcore_camera_config": hard_camera_config, "softcore_camera_directive": soft_camera_directive, "hardcore_camera_directive": hard_camera_directive, } return metadata