149 lines
6.2 KiB
Python
149 lines
6.2 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Any
|
|
|
|
|
|
STYLE_CONFIG_SCHEMA = "sxcp_style_config_v1"
|
|
STYLE_COMBINE_MODES = ("replace", "add", "disabled")
|
|
STYLE_PRESETS = {
|
|
"category_default": {
|
|
"style": "",
|
|
"positive_suffix": "",
|
|
"summary": "category default style",
|
|
},
|
|
"realistic_photo": {
|
|
"style": "realistic adult photographic scene, natural camera capture",
|
|
"positive_suffix": "Use realistic skin texture, natural light response, coherent anatomy, readable contact points, and believable spatial depth.",
|
|
"summary": "realistic photographic style",
|
|
},
|
|
"creator_phone_photo": {
|
|
"style": "realistic creator-shot phone photo, natural adult social-media image",
|
|
"positive_suffix": "Use handheld camera realism, natural skin texture, readable body positioning, and believable room depth.",
|
|
"summary": "creator phone photo style",
|
|
},
|
|
"documentary_flash": {
|
|
"style": "realistic direct-flash documentary photo, raw adult snapshot",
|
|
"positive_suffix": "Use direct flash, natural skin texture, sharp foreground detail, visible contact points, and unpolished camera realism.",
|
|
"summary": "direct flash documentary style",
|
|
},
|
|
"cinematic_realism": {
|
|
"style": "cinematic realistic adult scene, natural lens perspective",
|
|
"positive_suffix": "Use realistic anatomy, readable blocking, natural depth, motivated lighting, and coherent camera perspective.",
|
|
"summary": "cinematic realism style",
|
|
},
|
|
"comic_pinup_colored_pencil": {
|
|
"style": "adult erotic coloured-pencil comic pin-up style",
|
|
"positive_suffix": "Use crisp comic linework, detailed hatching, warm erotic lighting, soft skin shading, and tactile textured paper.",
|
|
"summary": "colored-pencil comic pin-up style",
|
|
},
|
|
"flat_vector_comic": {
|
|
"style": "flat vector adult comic illustration",
|
|
"positive_suffix": "Use flat color, clean graphic shapes, crisp outlines, simplified shadows, and readable adult body positioning.",
|
|
"summary": "flat vector comic style",
|
|
},
|
|
}
|
|
|
|
|
|
def style_pool_preset_choices() -> list[str]:
|
|
return list(STYLE_PRESETS)
|
|
|
|
|
|
def style_combine_mode_choices() -> list[str]:
|
|
return list(STYLE_COMBINE_MODES)
|
|
|
|
|
|
def _clean_text(value: Any) -> str:
|
|
return str(value or "").strip()
|
|
|
|
|
|
def _join_text(*values: Any) -> str:
|
|
parts: list[str] = []
|
|
for value in values:
|
|
text = _clean_text(value)
|
|
if text and text not in parts:
|
|
parts.append(text.rstrip("."))
|
|
return ". ".join(parts)
|
|
|
|
|
|
def parse_style_config(style_config: str | dict[str, Any] | None) -> dict[str, Any]:
|
|
if not style_config:
|
|
return {"enabled": False, "combine_mode": "disabled", "style": "", "positive_suffix": "", "negative_prompt": ""}
|
|
if isinstance(style_config, dict):
|
|
raw = dict(style_config)
|
|
else:
|
|
try:
|
|
raw = json.loads(str(style_config))
|
|
except json.JSONDecodeError:
|
|
return {"enabled": False, "combine_mode": "disabled", "style": "", "positive_suffix": "", "negative_prompt": ""}
|
|
if raw.get("schema") != STYLE_CONFIG_SCHEMA:
|
|
return {"enabled": False, "combine_mode": "disabled", "style": "", "positive_suffix": "", "negative_prompt": ""}
|
|
combine_mode = _clean_text(raw.get("combine_mode")) or "replace"
|
|
if combine_mode not in STYLE_COMBINE_MODES:
|
|
combine_mode = "replace"
|
|
return {
|
|
"schema": STYLE_CONFIG_SCHEMA,
|
|
"version": 1,
|
|
"enabled": bool(raw.get("enabled", True)) and combine_mode != "disabled",
|
|
"combine_mode": combine_mode,
|
|
"preset": _clean_text(raw.get("preset")) or "category_default",
|
|
"style": _clean_text(raw.get("style")),
|
|
"positive_suffix": _clean_text(raw.get("positive_suffix")),
|
|
"negative_prompt": _clean_text(raw.get("negative_prompt")),
|
|
"summary": _clean_text(raw.get("summary")) or "style config",
|
|
}
|
|
|
|
|
|
def build_style_config_json(
|
|
*,
|
|
enabled: bool = True,
|
|
combine_mode: str = "replace",
|
|
preset: str = "category_default",
|
|
custom_style: str = "",
|
|
custom_positive_suffix: str = "",
|
|
custom_negative: str = "",
|
|
style_config: str | dict[str, Any] | None = "",
|
|
) -> str:
|
|
if combine_mode not in STYLE_COMBINE_MODES:
|
|
combine_mode = "replace"
|
|
base = parse_style_config(style_config)
|
|
preset_entry = STYLE_PRESETS.get(preset, STYLE_PRESETS["category_default"])
|
|
style = _clean_text(custom_style) or preset_entry["style"]
|
|
positive_suffix = _clean_text(custom_positive_suffix) or preset_entry["positive_suffix"]
|
|
negative_prompt = _clean_text(custom_negative)
|
|
if combine_mode == "add" and base.get("enabled"):
|
|
style = _join_text(base.get("style"), style)
|
|
positive_suffix = _join_text(base.get("positive_suffix"), positive_suffix)
|
|
negative_prompt = _join_text(base.get("negative_prompt"), negative_prompt)
|
|
payload = {
|
|
"schema": STYLE_CONFIG_SCHEMA,
|
|
"version": 1,
|
|
"enabled": bool(enabled) and combine_mode != "disabled",
|
|
"combine_mode": combine_mode,
|
|
"preset": preset,
|
|
"style": style,
|
|
"positive_suffix": positive_suffix,
|
|
"negative_prompt": negative_prompt,
|
|
"summary": "style disabled" if not enabled or combine_mode == "disabled" else preset_entry["summary"],
|
|
}
|
|
return json.dumps(payload, ensure_ascii=True, sort_keys=True)
|
|
|
|
|
|
def resolve_style_fields(base_style: str, base_positive_suffix: str, style_config: str | dict[str, Any] | None) -> tuple[str, str]:
|
|
config = parse_style_config(style_config)
|
|
if not config.get("enabled"):
|
|
return base_style, base_positive_suffix
|
|
if config["combine_mode"] == "add":
|
|
return (
|
|
_join_text(base_style, config.get("style")),
|
|
_join_text(base_positive_suffix, config.get("positive_suffix")),
|
|
)
|
|
return config.get("style", "") or base_style, config.get("positive_suffix", "") or base_positive_suffix
|
|
|
|
|
|
def merge_negative_prompt(base_negative: str, style_config: str | dict[str, Any] | None) -> str:
|
|
config = parse_style_config(style_config)
|
|
if not config.get("enabled"):
|
|
return base_negative
|
|
return _join_text(base_negative, config.get("negative_prompt"))
|