from __future__ import annotations from typing import Any, Callable try: from . import cast_context as cast_context_policy from . import character_profile as character_profile_policy from . import pair_clothing from . import pair_options from . import softcore_text_policy except ImportError: # Allows local smoke tests with top-level imports. import cast_context as cast_context_policy import character_profile as character_profile_policy import pair_clothing import pair_options import softcore_text_policy AxisRng = Callable[[dict[str, int], str, int, int], Any] Choose = Callable[[Any, list[str]], str] CharacterContextForLabel = Callable[ [str, dict[str, dict[str, Any]], Any, str, str, bool, bool], tuple[dict[str, Any], dict[str, Any] | None], ] CharacterSlotLabelMap = Callable[[list[dict[str, Any]]], dict[str, dict[str, Any]]] ParseCharacterCast = Callable[[str | dict[str, Any] | list[Any] | None], list[dict[str, Any]]] SlotIsPov = Callable[[dict[str, Any] | None], bool] SlotSoftcoreOutfit = Callable[[dict[str, Any] | None, Any], str] def cast_summary_phrase(women_count: int, men_count: int) -> str: return cast_context_policy.cast_summary_phrase(women_count, men_count) def insta_descriptor_from_row(row: dict[str, Any]) -> str: return character_profile_policy.descriptor_from_parts( "woman", row.get("age_band") or row.get("age"), row.get("body_phrase"), row.get("skin"), row.get("hair"), row.get("eyes"), row.get("descriptor_detail"), ) def insta_descriptor_from_context(context: dict[str, Any]) -> str: subject = str(context.get("subject") or context.get("subject_type") or "person").strip() return character_profile_policy.descriptor_from_parts( subject, context.get("age"), context.get("body_phrase"), context.get("skin"), context.get("hair"), context.get("eyes"), context.get("descriptor_detail"), ) def prompt_cast_descriptors(text: str) -> str: return str(text or "").replace("Woman A / primary creator:", "Woman A:") def cast_descriptor_entries_from_slots( *, 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, character_slots: list[dict[str, Any]], character_slot_map: dict[str, dict[str, Any]], primary_descriptor: str = "", axis_rng: AxisRng, character_context_for_label: CharacterContextForLabel, slot_is_pov: SlotIsPov, ) -> tuple[list[str], list[dict[str, Any]]]: rng = axis_rng(seed_config, "person", seed, row_number + 997) descriptors: list[str] = [] for index in range(max(0, women_count)): label = f"Woman {chr(ord('A') + index)}" if index == 0 and primary_descriptor: descriptors.append(f"Woman A / primary creator: {primary_descriptor}") continue context, _slot = character_context_for_label( label, character_slot_map, rng, ethnicity, figure, no_plus_women, no_black, ) descriptors.append(f"{label}: {insta_descriptor_from_context(context)}") for index in range(max(0, men_count)): label = f"Man {chr(ord('A') + index)}" if slot_is_pov(character_slot_map.get(label)): continue context, _slot = character_context_for_label( label, character_slot_map, rng, ethnicity, figure, no_plus_women, no_black, ) descriptors.append(f"{label}: {insta_descriptor_from_context(context)}") return descriptors, character_slots def cast_descriptor_entries( *, 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, character_cast: str | dict[str, Any] | list[Any] | None = "", primary_descriptor: str = "", parse_character_cast: ParseCharacterCast, character_slot_label_map: CharacterSlotLabelMap, axis_rng: AxisRng, character_context_for_label: CharacterContextForLabel, slot_is_pov: SlotIsPov, ) -> tuple[list[str], list[dict[str, Any]]]: slots = parse_character_cast(character_cast) label_map = character_slot_label_map(slots) return cast_descriptor_entries_from_slots( seed_config=seed_config, seed=seed, row_number=row_number, ethnicity=ethnicity, figure=figure, no_plus_women=no_plus_women, no_black=no_black, women_count=women_count, men_count=men_count, character_slots=slots, character_slot_map=label_map, primary_descriptor=primary_descriptor, axis_rng=axis_rng, character_context_for_label=character_context_for_label, slot_is_pov=slot_is_pov, ) def softcore_partner_styling( *, seed_config: dict[str, int], seed: int, row_number: int, women_count: int, men_count: int, pov_labels: list[str] | None, label_map: dict[str, dict[str, Any]] | None, axis_rng: AxisRng, choose: Choose, slot_softcore_outfit: SlotSoftcoreOutfit, ) -> dict[str, Any]: clothing_rng = axis_rng(seed_config, "clothing", seed, row_number + 421) pose_rng = axis_rng(seed_config, "pose", seed, row_number + 421) pov_set = set(pov_labels or []) outfits: list[str] = [] for index in range(max(0, women_count - 1)): label = chr(ord("B") + index) full_label = f"Woman {label}" outfit = slot_softcore_outfit((label_map or {}).get(full_label), clothing_rng) or choose( clothing_rng, pair_options.INSTA_OF_SOFTCORE_PARTNER_WOMEN_OUTFITS, ) sentence = pair_clothing.softcore_outfit_sentence(full_label, outfit) if sentence: outfits.append(sentence) for index in range(max(0, men_count)): label = chr(ord("A") + index) full_label = f"Man {label}" if full_label in pov_set: continue outfit = slot_softcore_outfit((label_map or {}).get(full_label), clothing_rng) or choose( clothing_rng, pair_options.INSTA_OF_SOFTCORE_PARTNER_MEN_OUTFITS, ) sentence = pair_clothing.softcore_outfit_sentence(full_label, outfit) if sentence: outfits.append(sentence) return { "outfits": outfits, "pose": choose(pose_rng, pair_options.SOFTCORE_CAST_POSES), } def resolve_insta_pair_cast_context( *, soft_row: dict[str, Any], options: dict[str, Any], parsed_seed_config: dict[str, int], seed: int, row_number: int, ethnicity: str, figure: str, no_plus_women: bool, no_black: bool, hard_women_count: int, hard_men_count: int, character_slots: list[dict[str, Any]], character_slot_map: dict[str, dict[str, Any]], pov_character_labels: list[str], platform_styles: dict[str, str], soft_levels: dict[str, str], hardcore_levels: dict[str, str], axis_rng: AxisRng, character_context_for_label: CharacterContextForLabel, slot_is_pov: SlotIsPov, choose: Choose, slot_softcore_outfit: SlotSoftcoreOutfit, parsed_softcore_seed_config: dict[str, int] | None = None, ) -> dict[str, Any]: soft_seed_config = parsed_softcore_seed_config or parsed_seed_config descriptor = insta_descriptor_from_row(soft_row) cast_descriptors, _descriptor_slots = cast_descriptor_entries_from_slots( seed_config=parsed_seed_config, seed=seed, row_number=row_number, ethnicity=ethnicity, figure=figure, no_plus_women=no_plus_women, no_black=no_black, women_count=hard_women_count, men_count=hard_men_count, character_slots=character_slots, character_slot_map=character_slot_map, primary_descriptor=descriptor, axis_rng=axis_rng, character_context_for_label=character_context_for_label, slot_is_pov=slot_is_pov, ) cast_descriptor_text = prompt_cast_descriptors("; ".join(cast_descriptors)) same_softcore_cast = options["softcore_cast"] == "same_as_hardcore" soft_cast_descriptor_text = cast_descriptor_text if same_softcore_cast else f"Woman A: {descriptor}" soft_partner_styling = softcore_partner_styling( seed_config=soft_seed_config, seed=seed, row_number=row_number, women_count=hard_women_count if same_softcore_cast else 1, men_count=hard_men_count if same_softcore_cast else 0, pov_labels=pov_character_labels if same_softcore_cast else [], label_map=character_slot_map, axis_rng=axis_rng, choose=choose, slot_softcore_outfit=slot_softcore_outfit, ) if not same_softcore_cast: soft_partner_styling = {"outfits": [], "pose": ""} soft_partner_outfit_text = "; ".join(soft_partner_styling["outfits"]) soft_cast = ( "solo creator setup with Woman A alone" if options["softcore_cast"] == "solo" else f"soft creator-teaser setup with {cast_summary_phrase(hard_women_count, hard_men_count)}" ) soft_cast_presence = ( softcore_text_policy.softcore_cast_presence_phrase( same_cast=same_softcore_cast, pov_labels=pov_character_labels, cast_label="Woman A and the listed partners", woman_label="Woman A", ) + ". " ) soft_cast_styling_sentence = ( f"Partner softcore styling: {soft_partner_outfit_text}. Cast pose: {soft_partner_styling['pose']}. " if same_softcore_cast and soft_partner_outfit_text else "" ) hard_cast = cast_summary_phrase(hard_women_count, hard_men_count) soft_descriptor_sentence = ( f"Cast descriptors: {soft_cast_descriptor_text}. " if same_softcore_cast else f"Woman A: {descriptor}. " ) return { "descriptor": descriptor, "cast_descriptors": cast_descriptors, "cast_descriptor_text": cast_descriptor_text, "soft_cast_descriptor_text": soft_cast_descriptor_text, "soft_partner_styling": soft_partner_styling, "soft_partner_outfit_text": soft_partner_outfit_text, "platform_style": platform_styles[options["platform_style"]], "soft_level": soft_levels[options["softcore_level"]], "hard_level": hardcore_levels[options["hardcore_level"]], "soft_cast": soft_cast, "soft_cast_presence": soft_cast_presence, "soft_cast_styling_sentence": soft_cast_styling_sentence, "hard_cast": hard_cast, "soft_descriptor_sentence": soft_descriptor_sentence, }