Extract Krea configured cast formatter route

This commit is contained in:
2026-06-27 11:16:08 +02:00
parent 176d4c9257
commit 5ec17df1a4
5 changed files with 249 additions and 59 deletions
@@ -335,6 +335,11 @@ Keep here:
Already isolated: Already isolated:
- `krea_configured_cast_formatter.py` owns normal metadata configured-cast
Krea prose assembly behind `KreaConfiguredCastRequest`,
`KreaConfiguredCastDependencies`, and `KreaConfiguredCastPrompt`;
`krea_formatter.py` keeps configured-cast detection and compatibility
wrapper helpers.
- `krea_pair_formatter.py` owns Insta/OF pair soft/hard Krea prose assembly - `krea_pair_formatter.py` owns Insta/OF pair soft/hard Krea prose assembly
behind `KreaPairFormatRequest`, `KreaPairFormatDependencies`, and behind `KreaPairFormatRequest`, `KreaPairFormatDependencies`, and
`KreaPairPrompts`; `krea_formatter.py` keeps the `_insta_pair_to_krea` `KreaPairPrompts`; `krea_formatter.py` keeps the `_insta_pair_to_krea`
+6 -1
View File
@@ -650,7 +650,10 @@ Important POV rule:
- Pair metadata: `krea_pair_formatter.format_insta_pair_result` through the - Pair metadata: `krea_pair_formatter.format_insta_pair_result` through the
`_insta_pair_to_krea` compatibility wrapper. `_insta_pair_to_krea` compatibility wrapper.
- Normal metadata row: `_normal_row_to_krea`. - Normal configured-cast metadata row:
`krea_configured_cast_formatter.format_configured_cast_result` through the
`_normal_row_to_krea` compatibility wrapper.
- Other normal metadata rows: `_normal_row_to_krea`.
- Plain text fallback: `_fallback_text_to_krea`. - Plain text fallback: `_fallback_text_to_krea`.
Key Krea2 ownership: Key Krea2 ownership:
@@ -666,6 +669,8 @@ Key Krea2 ownership:
- Non-POV hardcore action sentence: `krea_actions.hardcore_action_sentence`. - Non-POV hardcore action sentence: `krea_actions.hardcore_action_sentence`.
- Insta/OF pair soft/hard Krea prose assembly: - Insta/OF pair soft/hard Krea prose assembly:
`krea_pair_formatter.format_insta_pair_result`. `krea_pair_formatter.format_insta_pair_result`.
- Normal configured-cast Krea prose assembly:
`krea_configured_cast_formatter.format_configured_cast_result`.
- Shared POV labels/filtering/composition cleanup: `pov_policy.py`. - Shared POV labels/filtering/composition cleanup: `pov_policy.py`.
- Krea POV camera support: `krea_pov.py`. - Krea POV camera support: `krea_pov.py`.
- Detail clause splitting and density limiting: `krea_detail.py`. - Detail clause splitting and density limiting: `krea_detail.py`.
+134
View File
@@ -0,0 +1,134 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Callable
@dataclass(frozen=True)
class KreaConfiguredCastRequest:
row: dict[str, Any]
detail_level: str
style_mode: str
primary: str
item: str
scene: str
expression: str
composition: str
source_composition: str
camera: str
camera_scene: str
style: str
@dataclass(frozen=True)
class KreaConfiguredCastPrompt:
prompt: str
method: str = "metadata(configured_cast)"
def as_tuple(self) -> tuple[str, str]:
return self.prompt, self.method
@dataclass(frozen=True)
class KreaConfiguredCastDependencies:
clean: Callable[[Any], str]
prompt_field: Callable[[str, str], str]
sanitize_hardcore_environment_anchors: Callable[[Any], str]
sanitize_hardcore_axis_values: Callable[[Any], Any]
sanitize_scene_text_for_cast: Callable[[Any, list[str]], str]
normalize_hardcore_detail_density: Callable[[Any], str]
row_action_family: Callable[[Any], str]
hardcore_action_sentence: Callable[[str, str, str, Any, str, str], str]
pov_action_phrase: Callable[[str, list[str], str, str, str, Any, str], str]
pov_labels_from_value: Callable[[Any], list[str]]
merge_labels: Callable[..., list[str]]
cast_prose_omit: Callable[[str, list[str]], tuple[str, list[str]]]
filter_pov_labeled_clauses: Callable[[Any, list[str]], str]
natural_label_text: Callable[[Any, list[str]], str]
pov_composition_text: Callable[[Any, list[str]], str]
pov_camera_phrase: Callable[[list[str]], str]
expression_phrase: Callable[[Any], str]
composition_phrase: Callable[..., str]
paragraph: Callable[[list[str]], str]
def format_configured_cast_result(
request: KreaConfiguredCastRequest,
deps: KreaConfiguredCastDependencies,
) -> KreaConfiguredCastPrompt:
row = request.row
subject = deps.clean(row.get("subject_phrase") or request.primary or "adult sexual scene")
cast = deps.clean(row.get("cast_summary"))
try:
women_count = int(row.get("women_count") or 0)
men_count = int(row.get("men_count") or 0)
except (TypeError, ValueError):
women_count = men_count = 0
cast_descriptor_text = (
deps.clean(row.get("cast_descriptor_text"))
or deps.prompt_field(deps.clean(row.get("prompt")), "Characters")
or deps.prompt_field(deps.clean(row.get("prompt")), "Cast descriptors")
)
pov_labels = deps.pov_labels_from_value(row.get("pov_character_labels"))
camera = request.camera
if pov_labels:
camera = ""
cast_prose, cast_labels = deps.cast_prose_omit(cast_descriptor_text, pov_labels)
if not cast_labels and women_count == 1 and men_count == 1:
cast_labels = ["Woman A", "Man A"]
cast_labels = deps.merge_labels(cast_labels, pov_labels)
expression = deps.filter_pov_labeled_clauses(request.expression, pov_labels)
expression = deps.natural_label_text(expression, cast_labels)
composition = deps.sanitize_hardcore_environment_anchors(request.composition)
source_composition = deps.sanitize_hardcore_environment_anchors(request.source_composition)
role_graph = deps.sanitize_scene_text_for_cast(
deps.sanitize_hardcore_environment_anchors(row.get("source_role_graph") or row.get("role_graph")),
cast_labels,
)
item = deps.sanitize_scene_text_for_cast(
deps.sanitize_hardcore_environment_anchors(request.item),
cast_labels,
)
role_graph = deps.natural_label_text(role_graph, cast_labels)
item = deps.natural_label_text(item, cast_labels)
axis_values = deps.sanitize_hardcore_axis_values(row.get("item_axis_values"))
detail_density = deps.normalize_hardcore_detail_density(row.get("hardcore_detail_density"))
action = deps.hardcore_action_sentence(
role_graph,
item,
source_composition,
axis_values,
detail_density,
deps.row_action_family(row),
)
action = deps.pov_action_phrase(
action,
pov_labels,
role_graph,
item,
source_composition,
axis_values,
detail_density,
)
output_composition = deps.pov_composition_text(composition, pov_labels)
parts = [
action,
deps.pov_camera_phrase(pov_labels),
cast_prose,
f"A consensual explicit adult scene with {subject}" if not action else "",
f"The cast includes {cast}" if cast and not cast_prose and not (women_count == 1 and men_count == 1) else "",
f"The setting is {request.scene}" if request.scene else "",
request.camera_scene,
deps.expression_phrase(expression),
deps.composition_phrase(output_composition, action, "The image is framed as", detail_density),
camera,
request.style if request.detail_level != "concise" else "",
]
return KreaConfiguredCastPrompt(deps.paragraph(parts))
def format_configured_cast(
request: KreaConfiguredCastRequest,
deps: KreaConfiguredCastDependencies,
) -> tuple[str, str]:
return format_configured_cast_result(request, deps).as_tuple()
+94 -58
View File
@@ -11,6 +11,7 @@ try:
is_outercourse_text as _is_outercourse_text, is_outercourse_text as _is_outercourse_text,
normalize_hardcore_detail_density as _normalize_hardcore_detail_density, normalize_hardcore_detail_density as _normalize_hardcore_detail_density,
) )
from . import krea_configured_cast_formatter
from . import krea_pair_formatter from . import krea_pair_formatter
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,
@@ -43,6 +44,7 @@ except ImportError: # Allows local smoke tests with `python -c`.
is_outercourse_text as _is_outercourse_text, is_outercourse_text as _is_outercourse_text,
normalize_hardcore_detail_density as _normalize_hardcore_detail_density, normalize_hardcore_detail_density as _normalize_hardcore_detail_density,
) )
import krea_configured_cast_formatter
import krea_pair_formatter import krea_pair_formatter
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,
@@ -407,13 +409,69 @@ def _couple_clothing_phrase(item: str) -> str:
return f"The couple wears {item}" return f"The couple wears {item}"
def _normal_row_to_krea(row: dict[str, Any], detail_level: str, style_mode: str) -> tuple[str, str]: def _krea_configured_cast_dependencies() -> krea_configured_cast_formatter.KreaConfiguredCastDependencies:
subject_type = _clean(row.get("subject_type")) return krea_configured_cast_formatter.KreaConfiguredCastDependencies(
clean=_clean,
prompt_field=_prompt_field,
sanitize_hardcore_environment_anchors=_sanitize_hardcore_environment_anchors,
sanitize_hardcore_axis_values=_sanitize_hardcore_axis_values,
sanitize_scene_text_for_cast=_sanitize_scene_text_for_cast,
normalize_hardcore_detail_density=_normalize_hardcore_detail_density,
row_action_family=route_metadata_policy.row_action_family,
hardcore_action_sentence=_hardcore_action_sentence,
pov_action_phrase=_pov_action_phrase,
pov_labels_from_value=_pov_labels_from_value,
merge_labels=_merge_labels,
cast_prose_omit=lambda text, omit_labels: _cast_prose(text, omit_labels=omit_labels),
filter_pov_labeled_clauses=_filter_pov_labeled_clauses,
natural_label_text=_natural_label_text,
pov_composition_text=_pov_composition_text,
pov_camera_phrase=lambda labels: _pov_camera_phrase(labels),
expression_phrase=_expression_phrase,
composition_phrase=_composition_phrase,
paragraph=_paragraph,
)
def _krea_configured_cast_request(
row: dict[str, Any],
detail_level: str,
style_mode: str,
primary: str,
item: str,
scene: str,
expression: str,
composition: str,
source_composition: str,
camera: str,
camera_scene: str,
style: str,
) -> krea_configured_cast_formatter.KreaConfiguredCastRequest:
return krea_configured_cast_formatter.KreaConfiguredCastRequest(
row=row,
detail_level=detail_level,
style_mode=style_mode,
primary=primary,
item=item,
scene=scene,
expression=expression,
composition=composition,
source_composition=source_composition,
camera=camera,
camera_scene=camera_scene,
style=style,
)
def _krea_configured_cast_request_from_row(
row: dict[str, Any],
detail_level: str,
style_mode: str,
) -> krea_configured_cast_formatter.KreaConfiguredCastRequest:
primary = _clean(row.get("primary_subject")) primary = _clean(row.get("primary_subject"))
item = _row_value(row, "item", ("Sexual pose", "Erotic outfit", "Clothing")) or _clean(row.get("custom_item")) item = _row_value(row, "item", ("Sexual pose", "Erotic outfit", "Clothing")) or _clean(row.get("custom_item"))
item = re.sub(r",?\s*(fashion editorial|resort) styling$", "", item, flags=re.IGNORECASE) item = re.sub(r",?\s*(fashion editorial|resort) styling$", "", item, flags=re.IGNORECASE)
scene = _row_value(row, "scene_text", ("Setting", "Scene")) or _clean(row.get("scene")) scene = _row_value(row, "scene_text", ("Setting", "Scene")) or _clean(row.get("scene"))
pose = _row_value(row, "pose", ("Sexual pose", "Pose"))
expression = "" expression = ""
if not _expression_disabled(row): if not _expression_disabled(row):
expression = _row_value(row, "character_expression_text") or _row_value(row, "expression", ("Facial expressions", "Facial expression")) expression = _row_value(row, "character_expression_text") or _row_value(row, "expression", ("Facial expressions", "Facial expression"))
@@ -427,64 +485,42 @@ def _normal_row_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
camera = _camera_phrase(row) camera = _camera_phrase(row)
camera_scene = _camera_scene_phrase(row) camera_scene = _camera_scene_phrase(row)
style = _style_phrase(row, style_mode) style = _style_phrase(row, style_mode)
return _krea_configured_cast_request(
row,
detail_level,
style_mode,
primary,
item,
scene,
expression,
composition,
source_composition,
camera,
camera_scene,
style,
)
def _normal_row_to_krea(row: dict[str, Any], detail_level: str, style_mode: str) -> tuple[str, str]:
subject_type = _clean(row.get("subject_type"))
if subject_type == "configured_cast" or _clean(row.get("cast_summary")): if subject_type == "configured_cast" or _clean(row.get("cast_summary")):
subject = _clean(row.get("subject_phrase") or primary or "adult sexual scene") return krea_configured_cast_formatter.format_configured_cast(
cast = _clean(row.get("cast_summary")) _krea_configured_cast_request_from_row(row, detail_level, style_mode),
try: _krea_configured_cast_dependencies(),
women_count = int(row.get("women_count") or 0)
men_count = int(row.get("men_count") or 0)
except (TypeError, ValueError):
women_count = men_count = 0
cast_descriptor_text = (
_clean(row.get("cast_descriptor_text"))
or _prompt_field(_clean(row.get("prompt")), "Characters")
or _prompt_field(_clean(row.get("prompt")), "Cast descriptors")
) )
pov_labels = _pov_labels_from_value(row.get("pov_character_labels"))
if pov_labels: primary = _clean(row.get("primary_subject"))
camera = "" item = _row_value(row, "item", ("Sexual pose", "Erotic outfit", "Clothing")) or _clean(row.get("custom_item"))
cast_prose, cast_labels = _cast_prose(cast_descriptor_text, omit_labels=pov_labels) item = re.sub(r",?\s*(fashion editorial|resort) styling$", "", item, flags=re.IGNORECASE)
if not cast_labels and women_count == 1 and men_count == 1: scene = _row_value(row, "scene_text", ("Setting", "Scene")) or _clean(row.get("scene"))
cast_labels = ["Woman A", "Man A"] pose = _row_value(row, "pose", ("Sexual pose", "Pose"))
cast_labels = _merge_labels(cast_labels, pov_labels) expression = ""
expression = _filter_pov_labeled_clauses(expression, pov_labels) if not _expression_disabled(row):
expression = _natural_label_text(expression, cast_labels) expression = _row_value(row, "character_expression_text") or _row_value(row, "expression", ("Facial expressions", "Facial expression"))
composition = _sanitize_hardcore_environment_anchors(composition) composition = re.sub(r"^vertical\s+", "", _row_value(row, "composition", ("Composition",)), flags=re.IGNORECASE)
source_composition = _sanitize_hardcore_environment_anchors(source_composition) camera = _camera_phrase(row)
role_graph = _sanitize_scene_text_for_cast( camera_scene = _camera_scene_phrase(row)
_sanitize_hardcore_environment_anchors(row.get("source_role_graph") or row.get("role_graph")), style = _style_phrase(row, style_mode)
cast_labels,
)
item = _sanitize_scene_text_for_cast(_sanitize_hardcore_environment_anchors(item), cast_labels)
role_graph = _natural_label_text(role_graph, cast_labels)
item = _natural_label_text(item, cast_labels)
axis_values = _sanitize_hardcore_axis_values(row.get("item_axis_values"))
detail_density = _normalize_hardcore_detail_density(row.get("hardcore_detail_density"))
action = _hardcore_action_sentence(
role_graph,
item,
source_composition,
axis_values,
detail_density,
route_metadata_policy.row_action_family(row),
)
action = _pov_action_phrase(action, pov_labels, role_graph, item, source_composition, axis_values, detail_density)
output_composition = _pov_composition_text(composition, pov_labels)
parts = [
action,
_pov_camera_phrase(pov_labels),
cast_prose,
f"A consensual explicit adult scene with {subject}" if not action else "",
f"The cast includes {cast}" if cast and not cast_prose and not (women_count == 1 and men_count == 1) else "",
f"The setting is {scene}" if scene else "",
camera_scene,
_expression_phrase(expression),
_composition_phrase(output_composition, action, "The image is framed as", detail_density),
camera,
style if detail_level != "concise" else "",
]
return _paragraph(parts), "metadata(configured_cast)"
if primary in ("woman", "man") or subject_type in ("woman", "man", "single_any"): if primary in ("woman", "man") or subject_type in ("woman", "man", "single_any"):
subject = _age_subject(row, "adult woman") subject = _age_subject(row, "adult woman")
+10
View File
@@ -42,6 +42,7 @@ import __init__ as sxcp_nodes # noqa: E402
import generation_profile_config # noqa: E402 import generation_profile_config # noqa: E402
import index_switch_policy # noqa: E402 import index_switch_policy # noqa: E402
import krea_cast # noqa: E402 import krea_cast # noqa: E402
import krea_configured_cast_formatter # noqa: E402
import krea_formatter # noqa: E402 import krea_formatter # noqa: E402
import krea_pair_formatter # noqa: E402 import krea_pair_formatter # noqa: E402
import location_config # noqa: E402 import location_config # noqa: E402
@@ -566,6 +567,15 @@ def smoke_config_route_location_theme() -> None:
_expect(seed_config.get("role_seed") == 3302, "seed lock did not reroll role axis") _expect(seed_config.get("role_seed") == 3302, "seed lock did not reroll role axis")
_expect(row.get("trigger") == "sxcpinup_coloredpencil", "generation profile trigger did not apply") _expect(row.get("trigger") == "sxcpinup_coloredpencil", "generation profile trigger did not apply")
_expect_trigger_once("config_route_location_theme.prompt", row.get("prompt"), "sxcpinup_coloredpencil") _expect_trigger_once("config_route_location_theme.prompt", row.get("prompt"), "sxcpinup_coloredpencil")
typed_route = krea_configured_cast_formatter.format_configured_cast_result(
krea_formatter._krea_configured_cast_request_from_row(row, "balanced", "preserve"),
krea_formatter._krea_configured_cast_dependencies(),
)
legacy_route = krea_formatter._normal_row_to_krea(row, "balanced", "preserve")
_expect(
typed_route.as_tuple() == legacy_route,
"Typed Krea configured-cast formatter route should match legacy wrapper output",
)
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(row), target="single") krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(row), target="single")
prompt = krea.get("krea_prompt") or "" prompt = krea.get("krea_prompt") or ""
_expect("library" in prompt.lower() or "bookshelves" in prompt.lower(), "Krea config route lost theme scene") _expect("library" in prompt.lower() or "bookshelves" in prompt.lower(), "Krea config route lost theme scene")