Extract Insta pair output assembly

This commit is contained in:
2026-06-26 21:51:32 +02:00
parent 8bff345cf7
commit e1ec8bd823
5 changed files with 253 additions and 126 deletions
+4 -4
View File
@@ -147,14 +147,11 @@ Keep here:
- soft/hard row creation; - soft/hard row creation;
- continuity policy; - continuity policy;
- softcore cast policy; - softcore cast policy;
- pair metadata shape.
Improve later: Improve later:
- make a single pair metadata sanitizer that normalizes `softcore_row`,
`hardcore_row`, pair prompts, negatives, captions, and camera fields;
- split pair assembly into small functions by phase: - split pair assembly into small functions by phase:
`build_soft_row`, `build_hard_row`, `assemble_pair_metadata`. `build_soft_row`, `build_hard_row`.
Already isolated: Already isolated:
@@ -166,6 +163,9 @@ Already isolated:
including action-aware body-access flags, conflicting outfit-piece cleanup, including action-aware body-access flags, conflicting outfit-piece cleanup,
default visible-men clothing, character-clothing override handling, and final default visible-men clothing, character-clothing override handling, and final
root clothing-state assembly. root clothing-state assembly.
- final pair output assembly lives in `pair_output.py`, including soft/hard
prompt strings, trigger preservation, negatives, captions, and root metadata
shape.
### Krea2 Formatter Path ### Krea2 Formatter Path
+5 -4
View File
@@ -67,6 +67,7 @@ Core helper ownership:
| `category_library.py` | JSON category loading, subcategory normalization, named scene/expression/composition pool loading, cast compatibility filtering, exact subcategory lookup, and inheritance-based pool merging. | | `category_library.py` | JSON category loading, subcategory normalization, named scene/expression/composition pool loading, cast compatibility filtering, exact subcategory lookup, and inheritance-based pool merging. |
| `pair_camera.py` | Insta/OF soft/hard camera route resolution, same-as-softcore camera mode, camera-detail override, camera-aware composition mutation, POV camera suppression, and synchronized row/root camera metadata. | | `pair_camera.py` | Insta/OF soft/hard camera route resolution, same-as-softcore camera mode, camera-detail override, camera-aware composition mutation, POV camera suppression, and synchronized row/root camera metadata. |
| `pair_clothing.py` | Insta/OF hardcore clothing continuity, action-aware body-access flags, conflicting outfit-piece cleanup, default visible-men clothing, and final root clothing-state assembly. | | `pair_clothing.py` | Insta/OF hardcore clothing continuity, action-aware body-access flags, conflicting outfit-piece cleanup, default visible-men clothing, and final root clothing-state assembly. |
| `pair_output.py` | Insta/OF final pair prompts, trigger preservation, negative prompts, captions, and root pair metadata assembly. |
| `hardcore_role_graphs.py` | Source role graph construction for hardcore configured-cast rows, including POV-aware interaction geometry. | | `hardcore_role_graphs.py` | Source role graph construction for hardcore configured-cast rows, including POV-aware interaction geometry. |
| `hardcore_role_fallback.py` | Solo, same-sex, mixed group fallback, and support-partner role graph wording for configured casts. | | `hardcore_role_fallback.py` | Solo, same-sex, mixed group fallback, and support-partner role graph wording for configured casts. |
| `hardcore_role_interaction.py` | Foreplay, manual stimulation, body worship, clothing transition, dominant guidance, camera performance, aftercare, and group coordination role graph wording. | | `hardcore_role_interaction.py` | Foreplay, manual stimulation, body worship, clothing transition, dominant guidance, camera performance, aftercare, and group coordination role graph wording. |
@@ -456,8 +457,8 @@ plain prompt text. When debugging, inspect these fields before editing pools.
| `shared_descriptor` | Soft row descriptor | Pair formatters | Primary creator descriptor. | | `shared_descriptor` | Soft row descriptor | Pair formatters | Primary creator descriptor. |
| `shared_cast_descriptors` | Cast descriptor builder | Pair formatters | Full cast descriptor list. | | `shared_cast_descriptors` | Cast descriptor builder | Pair formatters | Full cast descriptor list. |
| `softcore_row`, `hardcore_row` | Pair route | Pair formatters | Full normal metadata rows for each side. | | `softcore_row`, `hardcore_row` | Pair route | Pair formatters | Full normal metadata rows for each side. |
| `softcore_prompt`, `hardcore_prompt` | Pair assembly | 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 assembly | 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` | `_insta_of_partner_styling` | Krea/SDXL pair branch | Partner softcore clothing and pose when same-cast softcore is enabled. | | `softcore_partner_styling` | `_insta_of_partner_styling` | Krea/SDXL pair branch | Partner softcore clothing and pose when same-cast softcore is enabled. |
| `character_hardcore_clothing` | Character slots | Krea pair branch | Explicit per-character hardcore clothing state. | | `character_hardcore_clothing` | Character slots | Krea pair branch | Explicit per-character hardcore clothing state. |
| `default_man_hardcore_clothing` | Pair fallback | Krea pair branch | Auto clothing for visible men without configured clothing. | | `default_man_hardcore_clothing` | Pair fallback | Krea pair branch | Auto clothing for visible men without configured clothing. |
@@ -851,8 +852,8 @@ Use these traces to narrow a problem in one pass.
1. Check whether the prompt came from pair softcore or normal category builder. 1. Check whether the prompt came from pair softcore or normal category builder.
2. In pair softcore, inspect `softcore_partner_styling`, `softcore_row.item`, 2. In pair softcore, inspect `softcore_partner_styling`, `softcore_row.item`,
`softcore_row.pose`, and options `softcore_cast`. `softcore_row.pose`, and options `softcore_cast`.
3. If the raw soft prompt contains awkward defensive clauses, fix 3. If the raw soft prompt contains awkward defensive clauses, inspect
`build_insta_of_pair` soft prompt assembly. `pair_output.py`.
4. If Krea adds the awkwardness, inspect `_insta_pair_to_krea`. 4. If Krea adds the awkwardness, inspect `_insta_pair_to_krea`.
### Location composition mentions irrelevant props ### Location composition mentions irrelevant props
+190
View File
@@ -0,0 +1,190 @@
from __future__ import annotations
from typing import Any, Callable
try:
from .prompt_hygiene import sanitize_caption_text, sanitize_negative_text, sanitize_prompt_text
except ImportError: # Allows local smoke tests with `python tools/prompt_smoke.py`.
from prompt_hygiene import sanitize_caption_text, sanitize_negative_text, sanitize_prompt_text
def _labeled_expression_sentence(label: str, expression: Any) -> str:
expression = str(expression or "").strip()
if not expression:
return ""
return f"{label}: {expression}. "
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 assemble_insta_pair_metadata(
*,
active_trigger: str,
prepend_trigger_to_prompt: bool,
extra_positive: str,
extra_negative: str,
soft_negative_base: str,
hard_negative_base: str,
options: dict[str, Any],
platform_style: str,
soft_descriptor_sentence: str,
soft_level: str,
soft_cast: str,
soft_cast_presence: str,
soft_cast_styling_sentence: str,
soft_row: dict[str, Any],
soft_camera_scene_sentence: str,
soft_camera_sentence: str,
hard_level: str,
hard_cast: str,
cast_descriptor_text: str,
pov_directive: str,
pov_character_labels: list[str],
hard_clothing_sentence: str,
hard_row: dict[str, Any],
hard_scene: str,
hard_camera_scene_sentence: str,
hard_composition: str,
hard_detail_directive: str,
hard_camera_sentence: str,
descriptor: str,
soft_partner_outfit_text: str,
soft_partner_styling: dict[str, Any],
soft_camera_scene_directive: str,
soft_camera_config: dict[str, Any],
soft_camera_directive: str,
hard_camera_scene_directive: str,
hard_camera_config: dict[str, Any],
hard_camera_directive: str,
camera_caption_text: Callable[[dict[str, Any]], str],
cast_descriptors: list[str],
character_hardcore_clothing_entries: list[str],
default_man_hardcore_clothing_entries: list[str],
hard_clothing_state: str,
hard_detail_density: str,
hard_women_count: int,
hard_men_count: int,
character_slots: list[dict[str, Any]],
character_slot_map: dict[str, dict[str, Any]],
) -> dict[str, Any]:
soft_prompt = (
f"Insta/OF softcore mode: {platform_style}. "
f"{soft_descriptor_sentence}"
f"Softcore setup: {soft_level}. Cast: {soft_cast}. "
f"{soft_cast_presence}"
f"{soft_cast_styling_sentence}"
f"{soft_row['softcore_item_prompt_label']}: {soft_row['item']}. Pose: {soft_row['pose']}. Setting: {soft_row['scene_text']}. "
f"{soft_camera_scene_sentence}"
f"{_labeled_expression_sentence('Facial expression', soft_row.get('expression'))}"
f"Composition: {soft_row['composition']}. "
f"{soft_camera_sentence}"
"Keep the softcore version seductive, creator-shot, and styled as a soft teaser. "
f"{soft_row['positive_suffix']}."
)
hard_prompt = (
f"Insta/OF hardcore mode: {platform_style}. "
f"Hardcore setup: {hard_level}. Cast: {hard_cast}. "
f"Cast descriptors: {cast_descriptor_text}. "
f"{pov_directive + ' ' if pov_directive else ''}"
f"{'Keep Woman A visually central from the POV camera. ' if pov_character_labels else 'Keep Woman A visually central. '}"
f"{hard_clothing_sentence}"
f"Role graph: {hard_row['role_graph']} Sexual scene: {hard_row['item']}. "
f"Setting: {hard_scene}. "
f"{hard_camera_scene_sentence}"
f"{_labeled_expression_sentence('Facial expressions', hard_row.get('expression'))}"
f"Composition: {hard_composition}. "
f"{hard_detail_directive}"
f"{hard_camera_sentence}"
f"{hard_row['positive_suffix']}."
)
if extra_positive.strip():
soft_prompt = f"{soft_prompt.rstrip()} {extra_positive.strip()}"
hard_prompt = f"{hard_prompt.rstrip()} {extra_positive.strip()}"
soft_prompt = _prepend_trigger(soft_prompt, active_trigger, bool(prepend_trigger_to_prompt))
hard_prompt = _prepend_trigger(hard_prompt, active_trigger, bool(prepend_trigger_to_prompt))
soft_prompt = sanitize_prompt_text(soft_prompt, triggers=(active_trigger,))
hard_prompt = sanitize_prompt_text(hard_prompt, triggers=(active_trigger,))
soft_negative = sanitize_negative_text(_combined_negative(soft_negative_base, extra_negative))
hard_negative = sanitize_negative_text(_combined_negative(hard_negative_base, 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_camera_scene_directive,
soft_row["composition"],
camera_caption_text(soft_camera_config) if soft_camera_directive else "",
]
soft_caption = sanitize_caption_text(
", ".join(str(part).strip() for part in soft_caption_parts if str(part).strip()),
triggers=(active_trigger,),
)
hard_caption_parts = [
active_trigger,
"Insta/OF hardcore mode",
"Woman A",
descriptor,
hard_cast,
hard_row["role_graph"],
hard_row["item"],
hard_scene,
hard_camera_scene_directive,
hard_composition,
camera_caption_text(hard_camera_config) if hard_camera_directive else "",
]
hard_caption = sanitize_caption_text(
", ".join(str(part).strip() for part in hard_caption_parts if str(part).strip()),
triggers=(active_trigger,),
)
return {
"mode": "Insta/OF",
"options": options,
"shared_descriptor": descriptor,
"shared_cast_descriptors": cast_descriptors,
"pov_character_labels": pov_character_labels,
"pov_prompt_directive": pov_directive,
"softcore_partner_styling": soft_partner_styling,
"character_hardcore_clothing": character_hardcore_clothing_entries,
"default_man_hardcore_clothing": default_man_hardcore_clothing_entries,
"hardcore_clothing_state": hard_clothing_state,
"hardcore_detail_density": hard_detail_density,
"hardcore_position_config": hard_row.get("hardcore_position_config", {}),
"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,
"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,
"hardcore_camera_directive": hard_camera_directive,
"softcore_camera_scene_directive": soft_camera_scene_directive,
"hardcore_camera_scene_directive": hard_camera_scene_directive,
}
+50 -118
View File
@@ -27,6 +27,7 @@ try:
from . import generate_prompt_batches as g from . import generate_prompt_batches as g
from . import pair_clothing from . import pair_clothing
from . import pair_camera from . import pair_camera
from . import pair_output
from . import scene_camera_adapters from . import scene_camera_adapters
from .hardcore_text_cleanup import ( from .hardcore_text_cleanup import (
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values, sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
@@ -58,6 +59,7 @@ except ImportError: # Allows local smoke tests with `python -c`.
import generate_prompt_batches as g import generate_prompt_batches as g
import pair_clothing import pair_clothing
import pair_camera import pair_camera
import pair_output
import scene_camera_adapters import scene_camera_adapters
from hardcore_text_cleanup import ( from hardcore_text_cleanup import (
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values, sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
@@ -2797,13 +2799,6 @@ def _disable_row_expression(row: dict[str, Any], source: str = "disabled") -> di
return row return row
def _labeled_expression_sentence(label: str, expression: Any) -> str:
expression = str(expression or "").strip()
if not expression:
return ""
return f"{label}: {expression}. "
def _prepend_trigger(prompt: str, trigger: str, enabled: bool) -> str: def _prepend_trigger(prompt: str, trigger: str, enabled: bool) -> str:
trigger = trigger.strip() trigger = trigger.strip()
if not enabled or not trigger: if not enabled or not trigger:
@@ -6959,10 +6954,6 @@ def _insta_of_partner_styling(
} }
def _insta_of_active_trigger(prompt: str, trigger: str, enabled: bool) -> str:
return _prepend_trigger(prompt, trigger, enabled)
def build_insta_of_pair( def build_insta_of_pair(
row_number: int, row_number: int,
start_index: int, start_index: int,
@@ -7256,111 +7247,52 @@ def build_insta_of_pair(
else f"Woman A: {descriptor}. " else f"Woman A: {descriptor}. "
) )
soft_prompt = ( return pair_output.assemble_insta_pair_metadata(
f"Insta/OF softcore mode: {platform_style}. " active_trigger=active_trigger,
f"{soft_descriptor_sentence}" prepend_trigger_to_prompt=bool(prepend_trigger_to_prompt),
f"Softcore setup: {soft_level}. Cast: {soft_cast}. " extra_positive=extra_positive,
f"{soft_cast_presence}" extra_negative=extra_negative,
f"{soft_cast_styling_sentence}" soft_negative_base=INSTA_OF_SOFT_NEGATIVE,
f"{soft_row['softcore_item_prompt_label']}: {soft_row['item']}. Pose: {soft_row['pose']}. Setting: {soft_row['scene_text']}. " hard_negative_base=INSTA_OF_NEGATIVE,
f"{soft_camera_scene_sentence}" options=options,
f"{_labeled_expression_sentence('Facial expression', soft_row.get('expression'))}" platform_style=platform_style,
f"Composition: {soft_row['composition']}. " soft_descriptor_sentence=soft_descriptor_sentence,
f"{soft_camera_sentence}" soft_level=soft_level,
"Keep the softcore version seductive, creator-shot, and styled as a soft teaser. " soft_cast=soft_cast,
f"{soft_row['positive_suffix']}." soft_cast_presence=soft_cast_presence,
soft_cast_styling_sentence=soft_cast_styling_sentence,
soft_row=soft_row,
soft_camera_scene_sentence=soft_camera_scene_sentence,
soft_camera_sentence=soft_camera_sentence,
hard_level=hard_level,
hard_cast=hard_cast,
cast_descriptor_text=cast_descriptor_text,
pov_directive=pov_directive,
pov_character_labels=pov_character_labels,
hard_clothing_sentence=hard_clothing_sentence,
hard_row=hard_row,
hard_scene=hard_scene,
hard_camera_scene_sentence=hard_camera_scene_sentence,
hard_composition=hard_composition,
hard_detail_directive=hard_detail_directive,
hard_camera_sentence=hard_camera_sentence,
descriptor=descriptor,
soft_partner_outfit_text=soft_partner_outfit_text,
soft_partner_styling=soft_partner_styling,
soft_camera_scene_directive=soft_camera_scene_directive,
soft_camera_config=soft_camera_config,
soft_camera_directive=soft_camera_directive,
hard_camera_scene_directive=hard_camera_scene_directive,
hard_camera_config=hard_camera_config,
hard_camera_directive=hard_camera_directive,
camera_caption_text=_camera_caption_text,
cast_descriptors=cast_descriptors,
character_hardcore_clothing_entries=character_hardcore_clothing_entries,
default_man_hardcore_clothing_entries=default_man_hardcore_clothing_entries,
hard_clothing_state=hard_clothing_state,
hard_detail_density=hard_detail_density,
hard_women_count=hard_women_count,
hard_men_count=hard_men_count,
character_slots=character_slots,
character_slot_map=character_slot_map,
) )
hard_prompt = (
f"Insta/OF hardcore mode: {platform_style}. "
f"Hardcore setup: {hard_level}. Cast: {hard_cast}. "
f"Cast descriptors: {cast_descriptor_text}. "
f"{pov_directive + ' ' if pov_directive else ''}"
f"{'Keep Woman A visually central from the POV camera. ' if pov_character_labels else 'Keep Woman A visually central. '}"
f"{hard_clothing_sentence}"
f"Role graph: {hard_row['role_graph']} Sexual scene: {hard_row['item']}. "
f"Setting: {hard_scene}. "
f"{hard_camera_scene_sentence}"
f"{_labeled_expression_sentence('Facial expressions', hard_row.get('expression'))}"
f"Composition: {hard_composition}. "
f"{hard_detail_directive}"
f"{hard_camera_sentence}"
f"{hard_row['positive_suffix']}."
)
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_prompt = sanitize_prompt_text(soft_prompt, triggers=(active_trigger,))
hard_prompt = sanitize_prompt_text(hard_prompt, triggers=(active_trigger,))
soft_negative = sanitize_negative_text(_combined_negative(INSTA_OF_SOFT_NEGATIVE, extra_negative))
hard_negative = sanitize_negative_text(_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_camera_scene_directive,
soft_row["composition"],
_camera_caption_text(soft_camera_config) if soft_camera_directive else "",
]
soft_caption = sanitize_caption_text(
", ".join(str(part).strip() for part in soft_caption_parts if str(part).strip()),
triggers=(active_trigger,),
)
hard_caption_parts = [
active_trigger,
"Insta/OF hardcore mode",
"Woman A",
descriptor,
hard_cast,
hard_row["role_graph"],
hard_row["item"],
hard_scene,
hard_camera_scene_directive,
hard_composition,
_camera_caption_text(hard_camera_config) if hard_camera_directive else "",
]
hard_caption = sanitize_caption_text(
", ".join(str(part).strip() for part in hard_caption_parts if str(part).strip()),
triggers=(active_trigger,),
)
metadata = {
"mode": "Insta/OF",
"options": options,
"shared_descriptor": descriptor,
"shared_cast_descriptors": cast_descriptors,
"pov_character_labels": pov_character_labels,
"pov_prompt_directive": pov_directive,
"softcore_partner_styling": soft_partner_styling,
"character_hardcore_clothing": character_hardcore_clothing_entries,
"default_man_hardcore_clothing": default_man_hardcore_clothing_entries,
"hardcore_clothing_state": hard_clothing_state,
"hardcore_detail_density": hard_detail_density,
"hardcore_position_config": hard_row.get("hardcore_position_config", {}),
"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,
"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,
"hardcore_camera_directive": hard_camera_directive,
"softcore_camera_scene_directive": soft_camera_scene_directive,
"hardcore_camera_scene_directive": hard_camera_scene_directive,
}
return metadata
+4
View File
@@ -632,6 +632,10 @@ 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}.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}.hardcore_prompt", pair.get("hardcore_prompt"), Trigger)
_expect_trigger_once(f"{name}.softcore_caption", pair.get("softcore_caption"), Trigger)
_expect_trigger_once(f"{name}.hardcore_caption", pair.get("hardcore_caption"), Trigger)
_expect_no_duplicate_comma_items(f"{name}.softcore_negative", pair.get("softcore_negative_prompt")) _expect_no_duplicate_comma_items(f"{name}.softcore_negative", pair.get("softcore_negative_prompt"))
_expect_no_duplicate_comma_items(f"{name}.hardcore_negative", pair.get("hardcore_negative_prompt")) _expect_no_duplicate_comma_items(f"{name}.hardcore_negative", pair.get("hardcore_negative_prompt"))
_expect_formatter_outputs(pair, name, target="softcore") _expect_formatter_outputs(pair, name, target="softcore")