diff --git a/README.md b/README.md index 3d73cf0..14af51d 100644 --- a/README.md +++ b/README.md @@ -421,6 +421,22 @@ partitions, counters, or office rows are kept visible. In male-POV setups this becomes a first-person spatial description and the external camera sentence is suppressed. +`SxCP SDXL Formatter` rewrites prompt builder output or `metadata_json` into +comma-tag SDXL/Pony-style prompts. Connect `metadata_json` when possible so +character, camera, outfit, and action metadata stay available to the tag route. + +SDXL formatter controls: + +- `formatter_profile`: `manual_controls` keeps `style_preset` and + `quality_preset` authoritative. `pony_flat_vector`, `sdxl_photo`, and + `flat_vector` apply coherent formatter defaults. +- `style_preset`: positive style anchor such as `flat_vector_pony`, + `flat_vector`, or `photographic`. +- `quality_preset`: quality/score tail such as `pony_high` or `sdxl_high`. +- `trigger` and `prepend_trigger_to_prompt`: explicit model/LoRA trigger + placement for SDXL-style workflows. +- `custom_style` and `custom_quality`: override the selected preset text. + `SxCP Caption Naturalizer` rewrites tag-like captions or labeled prompts into more natural language. Connect the prompt builder's `metadata_json` output to `source_text` for the cleanest result. You can also connect `caption` or diff --git a/__init__.py b/__init__.py index 78903e1..58614ca 100644 --- a/__init__.py +++ b/__init__.py @@ -301,6 +301,7 @@ NODE_INPUT_TOOLTIPS = { }, "SxCPSDXLFormatter": { "metadata_json": "Best input for SDXL tag formatting because it preserves cast, camera, outfit, and explicit action metadata.", + "formatter_profile": "High-level formatter defaults. manual_controls keeps style_preset and quality_preset authoritative.", "style_preset": "Positive style anchor preset. flat_vector_pony matches the old SDXL tag style.", "quality_preset": "Quality/score tag tail for SDXL or Pony-style checkpoints.", "custom_style": "Optional replacement for the style preset. Leave empty to use style_preset.", diff --git a/docs/prompt-architecture-improvement-plan.md b/docs/prompt-architecture-improvement-plan.md index 644a7cc..9e014da 100644 --- a/docs/prompt-architecture-improvement-plan.md +++ b/docs/prompt-architecture-improvement-plan.md @@ -288,10 +288,12 @@ Keep here: - shared formatter input parsing from `formatter_input.py`. - style presets, quality presets, default negative prompt, and action/position family tag hints from `sdxl_presets.py`. +- formatter profiles for manual controls, Pony flat-vector, SDXL photo, and + plain flat-vector styles live in `sdxl_presets.py` and are exposed by + `SxCP SDXL Formatter`. Improve later: -- add formatter profiles for Pony, SDXL photo, and flat vector; - make fallback cleanup use the shared field-label inventory. ### Naturalizer Path diff --git a/docs/prompt-pool-routing-map.md b/docs/prompt-pool-routing-map.md index e658dbc..692ed4e 100644 --- a/docs/prompt-pool-routing-map.md +++ b/docs/prompt-pool-routing-map.md @@ -96,7 +96,7 @@ Core helper ownership: | `prompt_hygiene.py` | Generic prompt, caption, and negative-prompt cleanup. | | `row_normalization.py` | Final prompt-row and pair metadata normalization: trigger prepending, extra-positive append, negative merge/dedupe, caption-part joining, and embedded soft/hard row sanitation. | | `formatter_input.py` | Shared formatter input parsing: text cleanup, metadata/source JSON detection, trigger-prefix stripping, shared prompt field-label inventory, `Avoid:` splitting, prompt-field extraction, and metadata row-value fallback. | -| `sdxl_presets.py` | SDXL style presets, quality presets, default negative prompt, and metadata-family tag hints used by the SDXL formatter and node choice lists. | +| `sdxl_presets.py` | SDXL formatter profiles, style presets, quality presets, default negative prompt, and metadata-family tag hints used by the SDXL formatter and node choice lists. | | `caption_policy.py` | Caption naturalizer policy data and helpers: caption profiles, style tails, item labels, metadata-family caption labels, detail/style-policy normalization, clothing cleanup, and composition cleanup. | ## Node IO Map @@ -651,8 +651,8 @@ not parse metadata. That is a wiring/input-hint issue, not a prompt pool issue. - Normal metadata row: `_row_core_tags`. - Plain text fallback: `_fallback_text_to_sdxl`. -Use this route for style triggers, weighted tag style, nude weighting, and Pony / -SDXL quality/style presets. +Use this route for style triggers, weighted tag style, nude weighting, formatter +profiles, and Pony / SDXL quality/style presets. SDXL field consumption: diff --git a/node_formatter.py b/node_formatter.py index e32cfd5..accddb7 100644 --- a/node_formatter.py +++ b/node_formatter.py @@ -4,12 +4,22 @@ try: from .caption_naturalizer import naturalize_caption from .caption_policy import caption_profile_choices from .krea_formatter import format_krea2_prompt - from .sdxl_formatter import format_sdxl_prompt, sdxl_quality_preset_choices, sdxl_style_preset_choices + from .sdxl_formatter import ( + format_sdxl_prompt, + sdxl_formatter_profile_choices, + sdxl_quality_preset_choices, + sdxl_style_preset_choices, + ) except ImportError: # Allows local smoke tests from the repository root. from caption_naturalizer import naturalize_caption from caption_policy import caption_profile_choices from krea_formatter import format_krea2_prompt - from sdxl_formatter import format_sdxl_prompt, sdxl_quality_preset_choices, sdxl_style_preset_choices + from sdxl_formatter import ( + format_sdxl_prompt, + sdxl_formatter_profile_choices, + sdxl_quality_preset_choices, + sdxl_style_preset_choices, + ) class SxCPCaptionNaturalizer: @@ -138,6 +148,7 @@ class SxCPSDXLFormatter: "source_text": ("STRING", {"default": "", "multiline": True}), "input_hint": (["auto", "metadata_json", "prompt"], {"default": "auto"}), "target": (["auto", "single", "softcore", "hardcore"], {"default": "auto"}), + "formatter_profile": (sdxl_formatter_profile_choices(), {"default": "manual_controls"}), "style_preset": (sdxl_style_preset_choices(), {"default": "flat_vector_pony"}), "quality_preset": (sdxl_quality_preset_choices(), {"default": "pony_high"}), "trigger": ("STRING", {"default": "mythp0rt", "multiline": False}), @@ -174,6 +185,7 @@ class SxCPSDXLFormatter: source_text, input_hint, target, + formatter_profile, style_preset, quality_preset, trigger, @@ -195,6 +207,7 @@ class SxCPSDXLFormatter: negative_prompt=negative_prompt or "", input_hint=input_hint, target=target, + formatter_profile=formatter_profile, style_preset=style_preset, quality_preset=quality_preset, trigger=trigger, diff --git a/sdxl_formatter.py b/sdxl_formatter.py index 603ada8..bac0de6 100644 --- a/sdxl_formatter.py +++ b/sdxl_formatter.py @@ -23,6 +23,7 @@ TRIGGER_CANDIDATES = ( SDXL_STYLE_PRESETS = sdxl_policy.SDXL_STYLE_PRESETS SDXL_QUALITY_PRESETS = sdxl_policy.SDXL_QUALITY_PRESETS +SDXL_FORMATTER_PROFILES = sdxl_policy.SDXL_FORMATTER_PROFILES SDXL_DEFAULT_NEGATIVE = sdxl_policy.SDXL_DEFAULT_NEGATIVE SDXL_ACTION_FAMILY_TAGS = sdxl_policy.SDXL_ACTION_FAMILY_TAGS SDXL_POSITION_FAMILY_TAGS = sdxl_policy.SDXL_POSITION_FAMILY_TAGS @@ -38,6 +39,10 @@ def sdxl_quality_preset_choices() -> list[str]: return sdxl_policy.sdxl_quality_preset_choices() +def sdxl_formatter_profile_choices() -> list[str]: + return sdxl_policy.sdxl_formatter_profile_choices() + + def _clean(value: Any) -> str: return input_policy.clean_text(value) @@ -436,9 +441,13 @@ def format_sdxl_prompt( custom_quality: str = "", extra_positive: str = "", extra_negative: str = "", + formatter_profile: str = "manual_controls", ) -> dict[str, str]: - style_preset = sdxl_policy.normalize_style_preset(style_preset) - quality_preset = sdxl_policy.normalize_quality_preset(quality_preset) + style_preset, quality_preset = sdxl_policy.apply_formatter_profile( + formatter_profile, + style_preset=style_preset, + quality_preset=quality_preset, + ) target = target if target in ("auto", "single", "softcore", "hardcore") else "auto" nude_weight = max(0.1, min(3.0, float(nude_weight))) row, method = _row_from_inputs(source_text, metadata_json, input_hint) diff --git a/sdxl_presets.py b/sdxl_presets.py index 344a67c..df17812 100644 --- a/sdxl_presets.py +++ b/sdxl_presets.py @@ -3,6 +3,7 @@ from __future__ import annotations DEFAULT_STYLE_PRESET = "flat_vector_pony" DEFAULT_QUALITY_PRESET = "pony_high" +DEFAULT_FORMATTER_PROFILE = "manual_controls" SDXL_STYLE_PRESETS = { "flat_vector_pony": "(skindentation:1.25), (flat color:2.0), no lineart, no outline, Flat vector", @@ -21,6 +22,22 @@ SDXL_QUALITY_PRESETS = { "none": "", } +SDXL_FORMATTER_PROFILES = { + "manual_controls": {}, + "pony_flat_vector": { + "style_preset": "flat_vector_pony", + "quality_preset": "pony_high", + }, + "sdxl_photo": { + "style_preset": "photographic", + "quality_preset": "sdxl_high", + }, + "flat_vector": { + "style_preset": "flat_vector", + "quality_preset": "sdxl_high", + }, +} + SDXL_DEFAULT_NEGATIVE = ( "worst quality, low quality, normal quality, lowres, bad anatomy, bad hands, " "extra fingers, missing fingers, fused fingers, deformed, disfigured, malformed body, " @@ -58,9 +75,30 @@ def sdxl_quality_preset_choices() -> list[str]: return list(SDXL_QUALITY_PRESETS) +def sdxl_formatter_profile_choices() -> list[str]: + return list(SDXL_FORMATTER_PROFILES) + + def normalize_style_preset(value: str) -> str: return value if value in SDXL_STYLE_PRESETS else DEFAULT_STYLE_PRESET def normalize_quality_preset(value: str) -> str: return value if value in SDXL_QUALITY_PRESETS else DEFAULT_QUALITY_PRESET + + +def normalize_formatter_profile(value: str) -> str: + return value if value in SDXL_FORMATTER_PROFILES else DEFAULT_FORMATTER_PROFILE + + +def apply_formatter_profile( + formatter_profile: str, + *, + style_preset: str, + quality_preset: str, +) -> tuple[str, str]: + profile = SDXL_FORMATTER_PROFILES[normalize_formatter_profile(formatter_profile)] + return ( + normalize_style_preset(profile.get("style_preset", style_preset)), + normalize_quality_preset(profile.get("quality_preset", quality_preset)), + ) diff --git a/tools/prompt_smoke.py b/tools/prompt_smoke.py index 33d1b99..c99cbe9 100644 --- a/tools/prompt_smoke.py +++ b/tools/prompt_smoke.py @@ -1034,14 +1034,41 @@ def smoke_sdxl_presets_policy() -> None: sdxl_formatter.SDXL_QUALITY_PRESETS is sdxl_presets.SDXL_QUALITY_PRESETS, "SDXL formatter quality presets should delegate to sdxl_presets", ) + _expect( + sdxl_formatter.SDXL_FORMATTER_PROFILES is sdxl_presets.SDXL_FORMATTER_PROFILES, + "SDXL formatter profiles should delegate to sdxl_presets", + ) _expect( sdxl_formatter.SDXL_ACTION_FAMILY_TAGS is sdxl_presets.SDXL_ACTION_FAMILY_TAGS, "SDXL formatter action-family tags should delegate to sdxl_presets", ) + _expect("sdxl_photo" in sdxl_presets.sdxl_formatter_profile_choices(), "SDXL profile choices lost sdxl_photo") _expect("flat_vector_pony" in sdxl_presets.sdxl_style_preset_choices(), "SDXL style preset choices lost default") _expect("pony_high" in sdxl_presets.sdxl_quality_preset_choices(), "SDXL quality preset choices lost default") + _expect( + sdxl_presets.normalize_formatter_profile("bad") == sdxl_presets.DEFAULT_FORMATTER_PROFILE, + "SDXL invalid profile fallback changed", + ) _expect(sdxl_presets.normalize_style_preset("bad") == sdxl_presets.DEFAULT_STYLE_PRESET, "SDXL invalid style fallback changed") _expect(sdxl_presets.normalize_quality_preset("bad") == sdxl_presets.DEFAULT_QUALITY_PRESET, "SDXL invalid quality fallback changed") + _expect( + sdxl_presets.apply_formatter_profile( + "sdxl_photo", + style_preset="flat_vector_pony", + quality_preset="pony_high", + ) + == ("photographic", "sdxl_high"), + "SDXL photo profile overrides changed", + ) + _expect( + sdxl_presets.apply_formatter_profile( + "manual_controls", + style_preset="flat_vector", + quality_preset="none", + ) + == ("flat_vector", "none"), + "SDXL manual profile should preserve explicit controls", + ) row = _fixture_hardcore_row( action_family="oral", @@ -1063,6 +1090,18 @@ def smoke_sdxl_presets_policy() -> None: _expect_trigger_once("sdxl_presets.formatted_prompt", formatted.get("sdxl_prompt"), SdxlTrigger) _expect("Flat vector" in formatted.get("sdxl_prompt", ""), "SDXL invalid style did not fall back to default preset") _expect("score_9" in formatted.get("sdxl_prompt", ""), "SDXL invalid quality did not fall back to default preset") + profiled = sdxl_formatter.format_sdxl_prompt( + _json(row), + input_hint="auto", + formatter_profile="sdxl_photo", + style_preset="flat_vector_pony", + quality_preset="pony_high", + trigger=SdxlTrigger, + prepend_trigger=True, + ) + profiled_prompt = profiled.get("sdxl_prompt", "") + _expect("realistic photo" in profiled_prompt, "SDXL photo profile did not apply photographic style") + _expect("score_9" not in profiled_prompt, "SDXL photo profile should switch away from Pony score quality tail") def smoke_hardcore_position_config_policy() -> None: @@ -2824,6 +2863,7 @@ def smoke_node_formatter_registration() -> None: "A woman standing by a window", "prompt", "single", + "manual_controls", "flat_vector_pony", "pony_high", "mythp0rt", @@ -2835,6 +2875,9 @@ def smoke_node_formatter_registration() -> None: _expect_trigger_once("node_formatter.sdxl_prompt", sdxl_output[0], "mythp0rt") _expect_text("node_formatter.sdxl_negative", sdxl_output[1], 20) _expect(sdxl_output[6].startswith("text("), "SDXL Formatter method changed unexpectedly") + sdxl_inputs = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPSDXLFormatter"].INPUT_TYPES().get("required") or {} + _expect("formatter_profile" in sdxl_inputs, "SDXL Formatter lost formatter_profile input") + _expect("tooltip" in sdxl_inputs["formatter_profile"][1], "SDXL formatter_profile tooltip injection missing") def smoke_node_insta_registration() -> None: