Add chainable character slot controls
This commit is contained in:
+456
-12
@@ -76,6 +76,69 @@ ETHNICITY_FILTER_CHOICES = [
|
||||
"white_asian",
|
||||
]
|
||||
|
||||
CHARACTER_LABEL_CHOICES = [
|
||||
"auto_chain",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"F",
|
||||
"G",
|
||||
"H",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"L",
|
||||
]
|
||||
CHARACTER_AGE_CHOICES = (
|
||||
["random", "manual"]
|
||||
+ [f"{age}-year-old adult" for age in range(21, 86)]
|
||||
+ [
|
||||
"late 20s adult",
|
||||
"early 30s adult",
|
||||
"mid 30s adult",
|
||||
"late 30s adult",
|
||||
"early 40s adult",
|
||||
"mid 40s adult",
|
||||
"late 40s adult",
|
||||
"early 50s adult",
|
||||
"mid 50s adult",
|
||||
"late 50s adult",
|
||||
"early 60s adult",
|
||||
"mid 60s adult",
|
||||
"late 60s adult",
|
||||
"early 70s adult",
|
||||
"mid 70s adult",
|
||||
"late 70s adult",
|
||||
"early 80s adult",
|
||||
]
|
||||
)
|
||||
CHARACTER_BODY_CHOICES = [
|
||||
"random",
|
||||
"manual",
|
||||
"slim",
|
||||
"petite adult",
|
||||
"toned",
|
||||
"athletic",
|
||||
"average",
|
||||
"curvy",
|
||||
"soft curvy",
|
||||
"curvy athletic",
|
||||
"hourglass",
|
||||
"slim busty",
|
||||
"busty",
|
||||
"busty curvy",
|
||||
"voluptuous",
|
||||
"plus-size",
|
||||
"heavyset",
|
||||
"fat",
|
||||
"stocky",
|
||||
"broad",
|
||||
"muscular",
|
||||
]
|
||||
CHARACTER_RANDOM_TOKENS = {"", "random", "auto", "global", "from_global", "default"}
|
||||
|
||||
CAMERA_DETAIL_CHOICES = ["off", "compact", "full"]
|
||||
|
||||
GENERIC_POSITIVE_SUFFIX = (
|
||||
@@ -1221,6 +1284,26 @@ def ethnicity_choices() -> list[str]:
|
||||
return list(ETHNICITY_FILTER_CHOICES)
|
||||
|
||||
|
||||
def character_label_choices() -> list[str]:
|
||||
return list(CHARACTER_LABEL_CHOICES)
|
||||
|
||||
|
||||
def character_age_choices() -> list[str]:
|
||||
return list(CHARACTER_AGE_CHOICES)
|
||||
|
||||
|
||||
def character_body_choices() -> list[str]:
|
||||
return list(CHARACTER_BODY_CHOICES)
|
||||
|
||||
|
||||
def character_ethnicity_choices() -> list[str]:
|
||||
return ["random"] + list(ETHNICITY_FILTER_CHOICES)
|
||||
|
||||
|
||||
def character_figure_choices() -> list[str]:
|
||||
return ["random", "curvy", "balanced", "bombshell"]
|
||||
|
||||
|
||||
def camera_detail_choices() -> list[str]:
|
||||
return list(CAMERA_DETAIL_CHOICES)
|
||||
|
||||
@@ -1631,6 +1714,286 @@ def _load_json_object(value: str | dict[str, Any] | None, label: str) -> dict[st
|
||||
return raw
|
||||
|
||||
|
||||
def _slot_value(value: Any) -> str:
|
||||
text = str(value or "").strip()
|
||||
if text.lower() in CHARACTER_RANDOM_TOKENS:
|
||||
return ""
|
||||
return text
|
||||
|
||||
|
||||
def _slot_manual_or_choice(choice: str, manual_value: str) -> str:
|
||||
choice = str(choice or "").strip()
|
||||
manual_value = str(manual_value or "").strip()
|
||||
if choice == "manual":
|
||||
return manual_value or "random"
|
||||
if choice.lower() in CHARACTER_RANDOM_TOKENS:
|
||||
return "random"
|
||||
return choice
|
||||
|
||||
|
||||
def _normalize_slot_ethnicity(value: Any) -> str:
|
||||
text = str(value or "").strip()
|
||||
if text.lower() in CHARACTER_RANDOM_TOKENS:
|
||||
return "random"
|
||||
if text == "any" or text in ETHNICITY_FILTER_CHOICES or "+" in text:
|
||||
return text
|
||||
return "random"
|
||||
|
||||
|
||||
def _normalize_character_slot(slot: dict[str, Any]) -> dict[str, Any]:
|
||||
subject_type = str(slot.get("subject_type") or slot.get("subject") or "").strip().lower()
|
||||
if subject_type not in ("woman", "man"):
|
||||
subject_type = "woman"
|
||||
label = str(slot.get("label") or slot.get("label_mode") or "auto_chain").strip()
|
||||
label = label.replace("Woman ", "").replace("Man ", "").strip().upper()
|
||||
if label == "AUTO_CHAIN":
|
||||
label = "auto_chain"
|
||||
if label not in CHARACTER_LABEL_CHOICES:
|
||||
label = "auto_chain"
|
||||
|
||||
age = _slot_manual_or_choice(str(slot.get("age") or "random"), str(slot.get("manual_age") or ""))
|
||||
body = _slot_manual_or_choice(str(slot.get("body") or "random"), str(slot.get("manual_body") or ""))
|
||||
figure = str(slot.get("figure") or "random").strip()
|
||||
if figure not in character_figure_choices():
|
||||
figure = "random"
|
||||
|
||||
normalized = {
|
||||
"profile_type": "character_slot",
|
||||
"subject_type": subject_type,
|
||||
"label": label,
|
||||
"age": age,
|
||||
"ethnicity": _normalize_slot_ethnicity(slot.get("ethnicity")),
|
||||
"figure": figure,
|
||||
"body": body,
|
||||
"body_phrase": _slot_value(slot.get("body_phrase")),
|
||||
"skin": _slot_value(slot.get("skin")),
|
||||
"hair": _slot_value(slot.get("hair")),
|
||||
"eyes": _slot_value(slot.get("eyes")),
|
||||
}
|
||||
normalized["summary"] = _character_slot_summary(normalized)
|
||||
return normalized
|
||||
|
||||
|
||||
def _parse_character_cast(character_cast: str | dict[str, Any] | list[Any] | None) -> list[dict[str, Any]]:
|
||||
if not character_cast:
|
||||
return []
|
||||
if isinstance(character_cast, list):
|
||||
raw = character_cast
|
||||
elif isinstance(character_cast, dict):
|
||||
raw = character_cast
|
||||
else:
|
||||
try:
|
||||
raw = json.loads(str(character_cast))
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ValueError(f"Invalid character_cast JSON: {exc}") from exc
|
||||
|
||||
if isinstance(raw, list):
|
||||
slots = raw
|
||||
elif isinstance(raw, dict) and isinstance(raw.get("slots"), list):
|
||||
slots = raw["slots"]
|
||||
elif isinstance(raw, dict) and raw.get("profile_type") == "character_slot":
|
||||
slots = [raw]
|
||||
elif isinstance(raw, dict) and raw.get("subject_type") in ("woman", "man"):
|
||||
slots = [raw]
|
||||
else:
|
||||
return []
|
||||
return [_normalize_character_slot(slot) for slot in slots if isinstance(slot, dict)]
|
||||
|
||||
|
||||
def _character_slot_summary(slot: dict[str, Any]) -> str:
|
||||
subject = str(slot.get("subject_type") or "woman")
|
||||
label = str(slot.get("label") or "auto_chain")
|
||||
label_text = "nearest free label" if label == "auto_chain" else f"{subject.capitalize()} {label}"
|
||||
parts = [
|
||||
subject,
|
||||
label_text,
|
||||
f"age={slot.get('age', 'random')}",
|
||||
f"ethnicity={slot.get('ethnicity', 'random')}",
|
||||
f"figure={slot.get('figure', 'random')}",
|
||||
f"body={slot.get('body', 'random')}",
|
||||
]
|
||||
for key in ("body_phrase", "skin", "hair", "eyes"):
|
||||
value = slot.get(key)
|
||||
if value:
|
||||
parts.append(f"{key}={value}")
|
||||
return "; ".join(parts)
|
||||
|
||||
|
||||
def build_character_slot_json(
|
||||
subject_type: str = "woman",
|
||||
label: str = "auto_chain",
|
||||
age: str = "random",
|
||||
manual_age: str = "",
|
||||
ethnicity: str = "random",
|
||||
figure: str = "random",
|
||||
body: str = "random",
|
||||
manual_body: str = "",
|
||||
body_phrase: str = "",
|
||||
skin: str = "",
|
||||
hair: str = "",
|
||||
eyes: str = "",
|
||||
enabled: bool = True,
|
||||
character_cast: str | dict[str, Any] | list[Any] | None = "",
|
||||
) -> dict[str, str]:
|
||||
existing_slots = _parse_character_cast(character_cast)
|
||||
slot = _normalize_character_slot(
|
||||
{
|
||||
"subject_type": subject_type,
|
||||
"label": label,
|
||||
"age": age,
|
||||
"manual_age": manual_age,
|
||||
"ethnicity": ethnicity,
|
||||
"figure": figure,
|
||||
"body": body,
|
||||
"manual_body": manual_body,
|
||||
"body_phrase": body_phrase,
|
||||
"skin": skin,
|
||||
"hair": hair,
|
||||
"eyes": eyes,
|
||||
}
|
||||
)
|
||||
slots = existing_slots + ([slot] if enabled else [])
|
||||
cast = {
|
||||
"profile_type": "character_cast",
|
||||
"version": 1,
|
||||
"slots": slots,
|
||||
}
|
||||
return {
|
||||
"character_cast": json.dumps(cast, ensure_ascii=True, sort_keys=True),
|
||||
"character_slot": json.dumps(slot, ensure_ascii=True, sort_keys=True) if enabled else "",
|
||||
"summary": slot["summary"] if enabled else "disabled",
|
||||
"status": f"{len(slots)} slot(s)",
|
||||
}
|
||||
|
||||
|
||||
def _slot_explicit_label(slot: dict[str, Any]) -> str:
|
||||
label = str(slot.get("label") or "").strip().upper()
|
||||
if label in CHARACTER_LABEL_CHOICES and label != "AUTO_CHAIN":
|
||||
return label
|
||||
return ""
|
||||
|
||||
|
||||
def _character_slot_label_map(slots: list[dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
||||
label_map: dict[str, dict[str, Any]] = {}
|
||||
letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
for subject_type, prefix in (("woman", "Woman"), ("man", "Man")):
|
||||
subject_slots = [slot for slot in slots if slot.get("subject_type") == subject_type]
|
||||
auto_slots = [slot for slot in subject_slots if not _slot_explicit_label(slot)]
|
||||
for index, slot in enumerate(reversed(auto_slots)):
|
||||
if index >= len(letters):
|
||||
break
|
||||
label_map[f"{prefix} {letters[index]}"] = slot
|
||||
for slot in subject_slots:
|
||||
explicit = _slot_explicit_label(slot)
|
||||
if explicit:
|
||||
label_map[f"{prefix} {explicit}"] = slot
|
||||
return label_map
|
||||
|
||||
|
||||
def _context_from_character_slot(
|
||||
rng: random.Random,
|
||||
slot: dict[str, Any],
|
||||
subject_type: str,
|
||||
ethnicity: str,
|
||||
figure: str,
|
||||
no_plus_women: bool,
|
||||
no_black: bool,
|
||||
) -> dict[str, str]:
|
||||
slot_ethnicity = _slot_value(slot.get("ethnicity"))
|
||||
slot_figure = _slot_value(slot.get("figure"))
|
||||
slot_body = _slot_value(slot.get("body"))
|
||||
effective_ethnicity = slot_ethnicity or ethnicity
|
||||
effective_figure = slot_figure if slot_figure in ("curvy", "balanced", "bombshell") else figure
|
||||
effective_no_plus = bool(no_plus_women) and not slot_body
|
||||
effective_no_black = bool(no_black) and not slot_ethnicity
|
||||
context = _appearance_for_subject(
|
||||
rng,
|
||||
subject_type,
|
||||
effective_ethnicity,
|
||||
effective_figure,
|
||||
effective_no_plus,
|
||||
effective_no_black,
|
||||
)
|
||||
|
||||
age = _slot_value(slot.get("age"))
|
||||
body_phrase = _slot_value(slot.get("body_phrase"))
|
||||
if age:
|
||||
context["age"] = age
|
||||
if slot_body:
|
||||
context["body"] = slot_body
|
||||
if subject_type == "woman":
|
||||
context["body_phrase"] = _body_phrase(slot_body, context.get("figure", ""))
|
||||
else:
|
||||
context["body_phrase"] = f"{slot_body} figure"
|
||||
if body_phrase:
|
||||
context["body_phrase"] = body_phrase
|
||||
for key in ("skin", "hair", "eyes"):
|
||||
value = _slot_value(slot.get(key))
|
||||
if value:
|
||||
context[key] = value
|
||||
context["subject_type"] = subject_type
|
||||
context["subject"] = subject_type
|
||||
context["subject_phrase"] = subject_type
|
||||
return context
|
||||
|
||||
|
||||
def _character_context_for_label(
|
||||
label: str,
|
||||
label_map: dict[str, dict[str, Any]],
|
||||
rng: random.Random,
|
||||
ethnicity: str,
|
||||
figure: str,
|
||||
no_plus_women: bool,
|
||||
no_black: bool,
|
||||
) -> tuple[dict[str, str], dict[str, Any] | None]:
|
||||
subject_type = "man" if label.startswith("Man ") else "woman"
|
||||
slot = label_map.get(label)
|
||||
if slot:
|
||||
return _context_from_character_slot(rng, slot, subject_type, ethnicity, figure, no_plus_women, no_black), slot
|
||||
return _appearance_for_subject(rng, subject_type, ethnicity, figure, no_plus_women, no_black), None
|
||||
|
||||
|
||||
def _apply_character_context_to_row(row: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]:
|
||||
for key in ("subject_type", "subject", "subject_phrase", "age", "body", "body_phrase", "skin", "hair", "eyes", "figure"):
|
||||
value = context.get(key)
|
||||
if value:
|
||||
row[key] = value
|
||||
if context.get("age"):
|
||||
row["age_band"] = context["age"]
|
||||
return row
|
||||
|
||||
|
||||
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 = "",
|
||||
) -> tuple[list[str], list[dict[str, Any]]]:
|
||||
slots = _parse_character_cast(character_cast)
|
||||
label_map = _character_slot_label_map(slots)
|
||||
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, label_map, rng, ethnicity, figure, no_plus_women, no_black)
|
||||
descriptors.append(f"{label}: {_insta_of_descriptor_from_context(context)}")
|
||||
for index in range(max(0, men_count)):
|
||||
label = f"Man {chr(ord('A') + index)}"
|
||||
context, _slot = _character_context_for_label(label, label_map, rng, ethnicity, figure, no_plus_women, no_black)
|
||||
descriptors.append(f"{label}: {_insta_of_descriptor_from_context(context)}")
|
||||
return descriptors, slots
|
||||
|
||||
|
||||
def _row_from_profile_metadata(metadata_json: str | dict[str, Any] | None) -> dict[str, Any]:
|
||||
row = _load_json_object(metadata_json, "metadata_json")
|
||||
if isinstance(row.get("softcore_row"), dict):
|
||||
@@ -2432,6 +2795,7 @@ def _build_custom_row(
|
||||
seed_config: dict[str, int],
|
||||
expression_intensity: float,
|
||||
character_profile: str | dict[str, Any] | None = None,
|
||||
character_cast: str | dict[str, Any] | list[Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
categories = load_category_library()
|
||||
category_rng = _axis_rng(seed_config, "category", seed, row_number)
|
||||
@@ -2469,9 +2833,46 @@ def _build_custom_row(
|
||||
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)
|
||||
context, applied_profile, profile_status = _apply_character_profile_to_context(context, character_profile)
|
||||
character_slots = _parse_character_cast(character_cast)
|
||||
character_slot_map = _character_slot_label_map(character_slots)
|
||||
applied_slot: dict[str, Any] = {}
|
||||
slot_status = "none"
|
||||
if context.get("subject_type") in ("woman", "man"):
|
||||
slot_label = "Woman A" if context["subject_type"] == "woman" else "Man A"
|
||||
if slot_label in character_slot_map:
|
||||
context, applied_slot = _character_context_for_label(
|
||||
slot_label,
|
||||
character_slot_map,
|
||||
person_rng,
|
||||
ethnicity,
|
||||
figure,
|
||||
no_plus_women,
|
||||
no_black,
|
||||
)
|
||||
slot_status = f"applied:{slot_label}"
|
||||
applied_profile, profile_status = {}, "skipped_character_slot"
|
||||
else:
|
||||
context, applied_profile, profile_status = _apply_character_profile_to_context(context, character_profile)
|
||||
else:
|
||||
context, applied_profile, profile_status = _apply_character_profile_to_context(context, character_profile)
|
||||
subject_type = context["subject_type"]
|
||||
role_graph = _role_graph(role_rng, subcategory, context, item_axis_values)
|
||||
cast_descriptors: list[str] = []
|
||||
cast_descriptor_text = ""
|
||||
if subject_type == "configured_cast" and character_slots:
|
||||
cast_descriptors, _descriptor_slots = _cast_descriptor_entries(
|
||||
seed_config,
|
||||
seed,
|
||||
row_number,
|
||||
ethnicity,
|
||||
figure,
|
||||
no_plus_women,
|
||||
no_black,
|
||||
women_count,
|
||||
men_count,
|
||||
character_slots,
|
||||
)
|
||||
cast_descriptor_text = _insta_of_prompt_cast_descriptors("; ".join(cast_descriptors))
|
||||
|
||||
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(
|
||||
@@ -2523,6 +2924,7 @@ def _build_custom_row(
|
||||
"composition": composition,
|
||||
"composition_prompt": _composition_prompt(composition),
|
||||
"role_graph": role_graph,
|
||||
"cast_descriptors": cast_descriptor_text,
|
||||
"positive_suffix": positive_suffix,
|
||||
"negative_prompt": negative_prompt,
|
||||
}
|
||||
@@ -2550,7 +2952,11 @@ def _build_custom_row(
|
||||
)
|
||||
|
||||
prompt = _format(template, context)
|
||||
if subject_type == "configured_cast" and cast_descriptor_text and "{cast_descriptors}" not in template:
|
||||
prompt = _insert_positive_directive(prompt, f"Characters: {cast_descriptor_text}.")
|
||||
caption = _format(caption_template, context)
|
||||
if subject_type == "configured_cast" and cast_descriptor_text and "{cast_descriptors}" not in caption_template:
|
||||
caption = f"{caption.rstrip()}, {cast_descriptor_text}"
|
||||
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)
|
||||
@@ -2582,6 +2988,8 @@ def _build_custom_row(
|
||||
"content_seed_axis": content_axis,
|
||||
"role_graph": role_graph,
|
||||
"cast_summary": context.get("cast_summary", ""),
|
||||
"cast_descriptors": cast_descriptors,
|
||||
"cast_descriptor_text": cast_descriptor_text,
|
||||
"scene_kind": context.get("scene_kind", ""),
|
||||
"women_count": context.get("women_count", ""),
|
||||
"men_count": context.get("men_count", ""),
|
||||
@@ -2589,6 +2997,9 @@ def _build_custom_row(
|
||||
"cast_count_adjustment": count_adjustment if subject_type == "configured_cast" else {},
|
||||
"character_profile": applied_profile,
|
||||
"character_profile_status": profile_status,
|
||||
"character_slot": applied_slot,
|
||||
"character_slot_status": slot_status,
|
||||
"character_cast_slots": character_slots,
|
||||
"source": "json_category",
|
||||
}
|
||||
)
|
||||
@@ -2622,6 +3033,7 @@ def build_prompt(
|
||||
camera_config: str | dict[str, Any] | None = None,
|
||||
expression_intensity: float = 0.5,
|
||||
character_profile: str | dict[str, Any] | None = None,
|
||||
character_cast: str | dict[str, Any] | list[Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
apply_pool_extensions()
|
||||
row_number = max(1, int(row_number))
|
||||
@@ -2686,6 +3098,7 @@ def build_prompt(
|
||||
parsed_seed_config,
|
||||
expression_intensity,
|
||||
character_profile,
|
||||
character_cast,
|
||||
)
|
||||
|
||||
if extra_positive.strip():
|
||||
@@ -2710,6 +3123,7 @@ def build_prompt_from_configs(
|
||||
seed_config: str | dict[str, Any] | None = "",
|
||||
camera_config: str | dict[str, Any] | None = "",
|
||||
character_profile: str | dict[str, Any] | None = "",
|
||||
character_cast: str | dict[str, Any] | list[Any] | None = "",
|
||||
extra_positive: str = "",
|
||||
extra_negative: str = "",
|
||||
) -> dict[str, Any]:
|
||||
@@ -2742,6 +3156,7 @@ def build_prompt_from_configs(
|
||||
seed_config=seed_config or "",
|
||||
camera_config=camera_config or "",
|
||||
character_profile=character_profile or "",
|
||||
character_cast=character_cast or "",
|
||||
)
|
||||
|
||||
|
||||
@@ -3060,17 +3475,21 @@ def _insta_of_cast_descriptors(
|
||||
no_black: bool,
|
||||
women_count: int,
|
||||
men_count: int,
|
||||
character_cast: str | dict[str, Any] | list[Any] | None = "",
|
||||
) -> 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)}")
|
||||
descriptors, _slots = _cast_descriptor_entries(
|
||||
seed_config,
|
||||
seed,
|
||||
row_number,
|
||||
ethnicity,
|
||||
figure,
|
||||
no_plus_women,
|
||||
no_black,
|
||||
women_count,
|
||||
men_count,
|
||||
character_cast,
|
||||
primary_descriptor=primary_descriptor,
|
||||
)
|
||||
return descriptors
|
||||
|
||||
|
||||
@@ -3164,6 +3583,7 @@ def build_insta_of_pair(
|
||||
filter_config: str | dict[str, Any] | None = None,
|
||||
camera_config: str | dict[str, Any] | None = None,
|
||||
character_profile: str | dict[str, Any] | None = "",
|
||||
character_cast: str | dict[str, Any] | list[Any] | None = "",
|
||||
extra_positive: str = "",
|
||||
extra_negative: str = "",
|
||||
) -> dict[str, Any]:
|
||||
@@ -3177,9 +3597,24 @@ def build_insta_of_pair(
|
||||
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)
|
||||
character_slots = _parse_character_cast(character_cast)
|
||||
character_slot_map = _character_slot_label_map(character_slots)
|
||||
softcore_level_key = str(options["softcore_level"])
|
||||
soft_category, soft_subcategory = _insta_of_softcore_category(softcore_level_key)
|
||||
soft_content_rng = _axis_rng(parsed_seed_config, "content", seed, row_number + 311)
|
||||
soft_person_rng = _axis_rng(parsed_seed_config, "person", seed, row_number)
|
||||
primary_slot_context = None
|
||||
primary_slot = character_slot_map.get("Woman A")
|
||||
if primary_slot:
|
||||
primary_slot_context = _context_from_character_slot(
|
||||
soft_person_rng,
|
||||
primary_slot,
|
||||
"woman",
|
||||
ethnicity,
|
||||
figure,
|
||||
no_plus_women,
|
||||
no_black,
|
||||
)
|
||||
|
||||
soft_row = build_prompt(
|
||||
category=soft_category,
|
||||
@@ -3204,8 +3639,13 @@ def build_insta_of_pair(
|
||||
women_count=1,
|
||||
men_count=0,
|
||||
expression_intensity=options["softcore_expression_intensity"],
|
||||
character_profile=character_profile or "",
|
||||
character_profile="" if primary_slot else character_profile or "",
|
||||
character_cast="",
|
||||
)
|
||||
if primary_slot_context:
|
||||
soft_row = _apply_character_context_to_row(soft_row, primary_slot_context)
|
||||
soft_row["character_slot"] = primary_slot
|
||||
soft_row["character_slot_status"] = "applied:Woman A"
|
||||
soft_row["item"] = _insta_of_softcore_outfit(soft_content_rng, softcore_level_key)
|
||||
soft_row["pose"] = _insta_of_softcore_pose(soft_content_rng, softcore_level_key)
|
||||
soft_row["item_label"] = "Insta/OF softcore outfit"
|
||||
@@ -3234,6 +3674,7 @@ def build_insta_of_pair(
|
||||
women_count=hard_women_count,
|
||||
men_count=hard_men_count,
|
||||
expression_intensity=options["hardcore_expression_intensity"],
|
||||
character_cast=character_cast or "",
|
||||
)
|
||||
|
||||
descriptor = _insta_of_descriptor(soft_row)
|
||||
@@ -3248,6 +3689,7 @@ def build_insta_of_pair(
|
||||
no_black,
|
||||
hard_women_count,
|
||||
hard_men_count,
|
||||
character_slots,
|
||||
)
|
||||
cast_descriptor_text = _insta_of_prompt_cast_descriptors("; ".join(cast_descriptors))
|
||||
soft_cast_descriptor_text = (
|
||||
@@ -3382,6 +3824,8 @@ def build_insta_of_pair(
|
||||
"hardcore_row": hard_row,
|
||||
"hardcore_women_count": hard_women_count,
|
||||
"hardcore_men_count": hard_men_count,
|
||||
"character_cast_slots": character_slots,
|
||||
"character_slot_labels": sorted(character_slot_map),
|
||||
"softcore_camera_config": soft_camera_config,
|
||||
"hardcore_camera_config": hard_camera_config,
|
||||
"softcore_camera_directive": soft_camera_directive,
|
||||
|
||||
Reference in New Issue
Block a user