Synchronize pair cast metadata

This commit is contained in:
2026-06-27 12:48:12 +02:00
parent 2a5e565ce7
commit e5e194c68b
4 changed files with 40 additions and 4 deletions
+4 -3
View File
@@ -273,7 +273,7 @@ Already isolated:
caption-part joining, embedded soft/hard row output synchronization, and row caption-part joining, embedded soft/hard row output synchronization, and row
sanitation before metadata leaves generation. It also copies side-specific sanitation before metadata leaves generation. It also copies side-specific
pair metadata, such as soft partner styling and hardcore clothing/detail pair metadata, such as soft partner styling and hardcore clothing/detail
state, onto the embedded soft/hard rows. state, plus shared cast descriptors, onto the embedded soft/hard rows.
- final custom-row assembly now lives in `row_assembly.py` behind - final custom-row assembly now lives in `row_assembly.py` behind
`CustomRowAssemblyRequest`, covering render context population, `CustomRowAssemblyRequest`, covering render context population,
prompt/caption rendering delegation, row-base indexing, row metadata copying, prompt/caption rendering delegation, row-base indexing, row metadata copying,
@@ -326,8 +326,9 @@ Already isolated:
Embedded soft/hard rows are synchronized to the final pair prompt, caption, Embedded soft/hard rows are synchronized to the final pair prompt, caption,
and negative outputs during normalization so serialized pair metadata does and negative outputs during normalization so serialized pair metadata does
not carry stale standalone row text. Side-specific structured fields are not carry stale standalone row text. Side-specific structured fields are
synchronized there too, including soft partner styling and hardcore clothing synchronized there too, including soft partner styling, hardcore clothing
continuity metadata. continuity metadata, and shared cast descriptors for same-cast caption and
formatter routes.
### Krea2 Formatter Path ### Krea2 Formatter Path
+1 -1
View File
@@ -536,7 +536,7 @@ plain prompt text. When debugging, inspect these fields before editing pools.
| `options` | `SxCP Insta/OF Options` | Formatters/debug | Soft/hard level, cast mode, continuity, camera modes, expression settings. | | `options` | `SxCP Insta/OF Options` | Formatters/debug | Soft/hard level, cast mode, continuity, camera modes, expression settings. |
| `shared_descriptor` | `pair_cast.py` | Pair formatters | Primary creator descriptor. | | `shared_descriptor` | `pair_cast.py` | Pair formatters | Primary creator descriptor. |
| `shared_cast_descriptors` | `pair_cast.py` | Pair formatters | Full cast descriptor list. | | `shared_cast_descriptors` | `pair_cast.py` | Pair formatters | Full cast descriptor list. |
| `softcore_row`, `hardcore_row` | Pair route | Pair formatters | Full normal metadata rows for each side; their prompt, caption, negative, and side-specific metadata fields are synchronized to the final pair outputs/root fields during pair normalization. | | `softcore_row`, `hardcore_row` | Pair route | Pair formatters | Full normal metadata rows for each side; their prompt, caption, negative, shared cast descriptors, and side-specific metadata fields are synchronized to the final pair outputs/root fields during pair normalization. |
| `softcore_prompt`, `hardcore_prompt` | `pair_output.py` | Direct output/fallback | Raw pair prompts before formatter rewrite. | | `softcore_prompt`, `hardcore_prompt` | `pair_output.py` | Direct output/fallback | Raw pair prompts before formatter rewrite. |
| `softcore_negative_prompt`, `hardcore_negative_prompt` | `pair_output.py` | Formatter negatives | Separate negatives for each side. | | `softcore_negative_prompt`, `hardcore_negative_prompt` | `pair_output.py` | Formatter negatives | Separate negatives for each side. |
| `softcore_partner_styling` | `pair_cast.py` | Krea/SDXL pair branch | Partner softcore clothing and pose when same-cast softcore is enabled. | | `softcore_partner_styling` | `pair_cast.py` | Krea/SDXL pair branch | Partner softcore clothing and pose when same-cast softcore is enabled. |
+25
View File
@@ -142,11 +142,36 @@ def synchronize_pair_side_metadata(pair: dict[str, Any]) -> dict[str, Any]:
return pair return pair
def synchronize_pair_cast_metadata(pair: dict[str, Any]) -> dict[str, Any]:
descriptors = pair.get("shared_cast_descriptors")
if isinstance(descriptors, list):
descriptor_list = [str(item).strip() for item in descriptors if str(item or "").strip()]
descriptor_text = "; ".join(descriptor_list)
else:
descriptor_text = str(descriptors or "").strip()
descriptor_list = [descriptor_text] if descriptor_text else []
if not descriptor_text:
return pair
options = pair.get("options") if isinstance(pair.get("options"), dict) else {}
row_keys = ["hardcore_row"]
if options.get("softcore_cast") == "same_as_hardcore":
row_keys.append("softcore_row")
for row_key in row_keys:
row = pair.get(row_key)
if not isinstance(row, dict):
continue
row["cast_descriptor_text"] = descriptor_text
row["cast_descriptors"] = list(descriptor_list)
return pair
def normalize_pair_metadata(pair: dict[str, Any], *, active_trigger: str = "") -> dict[str, Any]: def normalize_pair_metadata(pair: dict[str, Any], *, active_trigger: str = "") -> dict[str, Any]:
trigger = str(active_trigger or "").strip() trigger = str(active_trigger or "").strip()
triggers = _trigger_tuple(trigger) triggers = _trigger_tuple(trigger)
synchronize_pair_row_outputs(pair) synchronize_pair_row_outputs(pair)
synchronize_pair_side_metadata(pair) synchronize_pair_side_metadata(pair)
synchronize_pair_cast_metadata(pair)
for key in ("softcore_prompt", "hardcore_prompt"): for key in ("softcore_prompt", "hardcore_prompt"):
if key in pair: if key in pair:
pair[key] = sanitize_prompt_text(pair.get(key, ""), triggers=triggers) pair[key] = sanitize_prompt_text(pair.get(key, ""), triggers=triggers)
+10
View File
@@ -3976,6 +3976,16 @@ def _expect_pair(pair: dict[str, Any], name: str) -> None:
_expect_custom_row(pair.get("hardcore_row") or {}, f"{name}.hardcore_row") _expect_custom_row(pair.get("hardcore_row") or {}, f"{name}.hardcore_row")
_expect_text(f"{name}.shared_descriptor", pair.get("shared_descriptor"), 12) _expect_text(f"{name}.shared_descriptor", pair.get("shared_descriptor"), 12)
_expect(pair.get("shared_cast_descriptors"), f"{name}.shared_cast_descriptors should not be empty") _expect(pair.get("shared_cast_descriptors"), f"{name}.shared_cast_descriptors should not be empty")
shared_cast_text = "; ".join(str(item).strip() for item in pair.get("shared_cast_descriptors") or [] if str(item).strip())
_expect(
pair["hardcore_row"].get("cast_descriptor_text") == shared_cast_text,
f"{name}.hardcore_row cast descriptors drifted from pair root",
)
if pair.get("options", {}).get("softcore_cast") == "same_as_hardcore":
_expect(
pair["softcore_row"].get("cast_descriptor_text") == shared_cast_text,
f"{name}.softcore_row cast descriptors drifted from same-cast pair root",
)
_expect_text(f"{name}.softcore_prompt", pair.get("softcore_prompt"), 20) _expect_text(f"{name}.softcore_prompt", pair.get("softcore_prompt"), 20)
_expect_text(f"{name}.hardcore_prompt", pair.get("hardcore_prompt"), 20) _expect_text(f"{name}.hardcore_prompt", pair.get("hardcore_prompt"), 20)
_expect_trigger_once(f"{name}.softcore_prompt", pair.get("softcore_prompt"), Trigger) _expect_trigger_once(f"{name}.softcore_prompt", pair.get("softcore_prompt"), Trigger)