Extract Krea clothing cleanup
This commit is contained in:
@@ -137,7 +137,6 @@ Keep here:
|
|||||||
- Krea prose style;
|
- Krea prose style;
|
||||||
- hardcore action sentence rewriting;
|
- hardcore action sentence rewriting;
|
||||||
- POV sentence rewriting;
|
- POV sentence rewriting;
|
||||||
- clothing naturalization;
|
|
||||||
- camera-scene preservation;
|
- camera-scene preservation;
|
||||||
- fallback text parsing.
|
- fallback text parsing.
|
||||||
|
|
||||||
@@ -145,11 +144,13 @@ Already isolated:
|
|||||||
|
|
||||||
- `krea_cast.py` owns cast descriptor parsing, cast prose, label joining, and
|
- `krea_cast.py` owns cast descriptor parsing, cast prose, label joining, and
|
||||||
natural label replacement for formatter routes.
|
natural label replacement for formatter routes.
|
||||||
|
- `krea_clothing.py` owns clothing-state cleanup and action-aware body-access
|
||||||
|
wording for formatter routes.
|
||||||
|
|
||||||
Improve later:
|
Improve later:
|
||||||
|
|
||||||
- split semantic blocks into modules:
|
- split semantic blocks into modules:
|
||||||
`krea_actions.py`, `krea_pov.py`, `krea_clothing.py`;
|
`krea_actions.py`, `krea_pov.py`;
|
||||||
- add route-level smoke fixtures for representative metadata rows;
|
- add route-level smoke fixtures for representative metadata rows;
|
||||||
- make `_hardcore_action_sentence` dispatch by action family instead of long
|
- make `_hardcore_action_sentence` dispatch by action family instead of long
|
||||||
conditional chains.
|
conditional chains.
|
||||||
@@ -330,7 +331,7 @@ Medium-term:
|
|||||||
|
|
||||||
## Recommended Next Passes
|
## Recommended Next Passes
|
||||||
|
|
||||||
1. Split Krea action/POV/clothing helpers into separate modules, using
|
1. Split Krea action/POV helpers into separate modules, using
|
||||||
`krea_cast.py` as the pattern for stable import aliases and smoke coverage.
|
`krea_cast.py` as the pattern for stable import aliases and smoke coverage.
|
||||||
2. Split `__init__.py` node classes by family after behavior is covered by smoke
|
2. Split `__init__.py` node classes by family after behavior is covered by smoke
|
||||||
checks.
|
checks.
|
||||||
|
|||||||
@@ -545,10 +545,11 @@ Important POV rule:
|
|||||||
|
|
||||||
Key Krea2 ownership:
|
Key Krea2 ownership:
|
||||||
|
|
||||||
- Cast descriptor naturalization: `_cast_prose`, `_natural_label_text`.
|
- Cast descriptor naturalization: `krea_cast.cast_prose`,
|
||||||
|
`krea_cast.natural_label_text`.
|
||||||
- Hardcore action sentence: `_hardcore_action_sentence`.
|
- Hardcore action sentence: `_hardcore_action_sentence`.
|
||||||
- POV hardcore sentence: `_pov_hardcore_pose_sentence`, `_pov_action_phrase`.
|
- POV hardcore sentence: `_pov_hardcore_pose_sentence`, `_pov_action_phrase`.
|
||||||
- Clothing state cleanup: `_natural_clothing_state`.
|
- Clothing state cleanup: `krea_clothing.natural_clothing_state`.
|
||||||
- Camera scene preservation: `_camera_scene_phrase`.
|
- Camera scene preservation: `_camera_scene_phrase`.
|
||||||
|
|
||||||
Krea2 field consumption:
|
Krea2 field consumption:
|
||||||
@@ -558,7 +559,7 @@ Krea2 field consumption:
|
|||||||
| Normal single row | `subject_type`, `item`, `pose`, `scene_text`, `expression`, `composition`, `camera_*`, style fields | `_normal_row_to_krea` |
|
| Normal single row | `subject_type`, `item`, `pose`, `scene_text`, `expression`, `composition`, `camera_*`, style fields | `_normal_row_to_krea` |
|
||||||
| Normal configured cast/hardcore row | `cast_descriptor_text`, `women_count`, `men_count`, `source_role_graph`, `role_graph`, `item`, `item_axis_values`, `source_composition`, `pov_character_labels` | `_normal_row_to_krea`, `_hardcore_action_sentence`, `_pov_action_phrase` |
|
| Normal configured cast/hardcore row | `cast_descriptor_text`, `women_count`, `men_count`, `source_role_graph`, `role_graph`, `item`, `item_axis_values`, `source_composition`, `pov_character_labels` | `_normal_row_to_krea`, `_hardcore_action_sentence`, `_pov_action_phrase` |
|
||||||
| Insta/OF pair softcore | `shared_descriptor`, `softcore_row`, `softcore_partner_styling`, options, soft camera fields | `_insta_pair_to_krea` |
|
| Insta/OF pair softcore | `shared_descriptor`, `softcore_row`, `softcore_partner_styling`, options, soft camera fields | `_insta_pair_to_krea` |
|
||||||
| Insta/OF pair hardcore | `hardcore_row`, `shared_cast_descriptors`, `hardcore_clothing_state`, `hardcore_detail_density`, hard camera fields, POV labels | `_insta_pair_to_krea`, `_hardcore_action_sentence`, `_pov_action_phrase`, `_natural_clothing_state` |
|
| Insta/OF pair hardcore | `hardcore_row`, `shared_cast_descriptors`, `hardcore_clothing_state`, `hardcore_detail_density`, hard camera fields, POV labels | `_insta_pair_to_krea`, `_hardcore_action_sentence`, `_pov_action_phrase`, `krea_clothing.natural_clothing_state` |
|
||||||
| Plain text fallback | `source_text` only | `_fallback_text_to_krea` |
|
| Plain text fallback | `source_text` only | `_fallback_text_to_krea` |
|
||||||
|
|
||||||
If metadata is connected and `method` says `text(fallback)`, the formatter did
|
If metadata is connected and `method` says `text(fallback)`, the formatter did
|
||||||
@@ -700,7 +701,7 @@ pair metadata through the core Python APIs, then verifies:
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Wrong main category/subcategory frequency | Category node config, `load_category_library`, category JSON weights. |
|
| Wrong main category/subcategory frequency | Category node config, `load_category_library`, category JSON weights. |
|
||||||
| Wrong outfit/clothing item | Relevant category JSON, `INSTA_OF_SOFTCORE_OUTFITS`, `SxCP Character Clothing`. |
|
| Wrong outfit/clothing item | Relevant category JSON, `INSTA_OF_SOFTCORE_OUTFITS`, `SxCP Character Clothing`. |
|
||||||
| Nude/clothing state confusing Krea2 | `build_insta_of_pair` clothing state helpers, then `_natural_clothing_state`. |
|
| Nude/clothing state confusing Krea2 | `build_insta_of_pair` clothing state helpers, then `krea_clothing.natural_clothing_state`. |
|
||||||
| Wrong location | `categories/location_pools.json`, category `scene_pool`, `_scene_pool`. |
|
| Wrong location | `categories/location_pools.json`, category `scene_pool`, `_scene_pool`. |
|
||||||
| Location good but camera/location layout wrong | `_camera_scene_directive_for_context`, coworking adapter functions. |
|
| Location good but camera/location layout wrong | `_camera_scene_directive_for_context`, coworking adapter functions. |
|
||||||
| Repeated desk/anchor in POV foreground | Coworking direction/distance/elevation helpers. |
|
| Repeated desk/anchor in POV foreground | Coworking direction/distance/elevation helpers. |
|
||||||
@@ -763,7 +764,7 @@ Use these traces to narrow a problem in one pass.
|
|||||||
1. Check pair root `hardcore_clothing_state`.
|
1. Check pair root `hardcore_clothing_state`.
|
||||||
2. Check hard row `item` and `source_role_graph` for access flags.
|
2. Check hard row `item` and `source_role_graph` for access flags.
|
||||||
3. Character slot `hardcore_clothing` overrides pair fallback clothing.
|
3. Character slot `hardcore_clothing` overrides pair fallback clothing.
|
||||||
4. For Krea wording, inspect `_natural_clothing_state`.
|
4. For Krea wording, inspect `krea_clothing.natural_clothing_state`.
|
||||||
5. For generation wording, inspect `_insta_of_hardcore_clothing_state`,
|
5. For generation wording, inspect `_insta_of_hardcore_clothing_state`,
|
||||||
`_hardcore_row_access_flags`, and `character_hardcore_clothing_values`.
|
`_hardcore_row_access_flags`, and `character_hardcore_clothing_values`.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .krea_cast import natural_label_text
|
||||||
|
except ImportError: # Allows local smoke tests with `python -c`.
|
||||||
|
from krea_cast import natural_label_text
|
||||||
|
|
||||||
|
|
||||||
|
def _clean(value: Any) -> str:
|
||||||
|
text = "" if value is None else str(value)
|
||||||
|
text = text.replace("\n", " ")
|
||||||
|
text = re.sub(r"\s+", " ", text).strip()
|
||||||
|
text = re.sub(r"\s+([,.;:])", r"\1", text)
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def clothing_access_phrase(action_text: Any) -> str:
|
||||||
|
text = _clean(action_text).lower()
|
||||||
|
if any(term in text for term in ("cumshot", "ejaculat", "semen", "cum on", "cum across", "post-orgasm", "aftermath")):
|
||||||
|
return "leaving the body exposed for visible semen and aftermath"
|
||||||
|
if any(term in text for term in ("boobjob", "titjob", "breast sex", "handjob", "hand job", "footjob", "testicle", "balls", "penis licking", "non-penetrative")):
|
||||||
|
return "leaving the contact point unobstructed"
|
||||||
|
if any(term in text for term in ("oral", "blowjob", "fellatio", "mouth", "tongue")):
|
||||||
|
return "leaving the oral contact unobstructed"
|
||||||
|
if any(term in text for term in ("penetrat", "thrust", "penis entering", "vaginal", "anal")):
|
||||||
|
return "leaving the penetration point unobstructed"
|
||||||
|
return "leaving skin and body contact readable"
|
||||||
|
|
||||||
|
|
||||||
|
def natural_clothing_state(text: Any, action_text: Any = "") -> str:
|
||||||
|
text = _clean(text)
|
||||||
|
if not text:
|
||||||
|
return ""
|
||||||
|
text = re.sub(r"^Clothing state:\s*", "", text, flags=re.IGNORECASE)
|
||||||
|
if re.search(r";\s*(?=(?:Woman|Man) [A-Z]\b)", text):
|
||||||
|
parts = [
|
||||||
|
natural_clothing_state(part, action_text).rstrip(".")
|
||||||
|
for part in re.split(r";\s*(?=(?:Woman|Man) [A-Z]\b)", text)
|
||||||
|
if _clean(part)
|
||||||
|
]
|
||||||
|
return ". ".join(part for part in parts if part)
|
||||||
|
body_exposure = re.match(r"^Body exposure:\s*(.*?)\.?$", text, flags=re.IGNORECASE)
|
||||||
|
if body_exposure:
|
||||||
|
return _clean(body_exposure.group(1)).rstrip(".")
|
||||||
|
if re.search(r"\bfully nude\b|\bbody is fully exposed\b|\bno clothing covering\b", text, flags=re.IGNORECASE):
|
||||||
|
owner = "the woman"
|
||||||
|
owner_match = re.match(r"^\s*((?:Woman|Man) [A-Z])\b", text)
|
||||||
|
if owner_match:
|
||||||
|
owner = natural_label_text(owner_match.group(1), ["Woman A", "Man A"]) or owner
|
||||||
|
return f"{owner.capitalize()}'s body is fully exposed, bare skin unobstructed"
|
||||||
|
match = re.match(
|
||||||
|
r"^(.*?)\b(?:softcore|teaser) outfit is (.*?)(?: for the (?:hardcore|sex) scene)?;\s*(?:softcore visual reference|teaser outfit detail):\s*(.*?)\.?$",
|
||||||
|
text,
|
||||||
|
flags=re.IGNORECASE,
|
||||||
|
)
|
||||||
|
if match:
|
||||||
|
owner = natural_label_text(match.group(1).strip(" 's"), ["Woman A", "Man A"]).strip() or "the woman"
|
||||||
|
state = _clean(match.group(2)).lower()
|
||||||
|
outfit = _clean(match.group(3)).rstrip(".")
|
||||||
|
if "fully nude" in state or "fully exposed" in state or "no clothing covering" in state:
|
||||||
|
return f"{owner.capitalize()}'s body is fully exposed, bare skin unobstructed"
|
||||||
|
if "nude-adjacent" in state:
|
||||||
|
return f"{owner.capitalize()}'s body is partly exposed"
|
||||||
|
if "partially removed" in state or "pushed aside" in state:
|
||||||
|
return f"{owner.capitalize()}'s {outfit} is pushed aside or partly removed where needed, {clothing_access_phrase(action_text)}"
|
||||||
|
if "keeps" in state:
|
||||||
|
return f"{owner.capitalize()} keeps the {outfit} on while {clothing_access_phrase(action_text)}"
|
||||||
|
text = re.sub(r";\s*(?:softcore visual reference|teaser outfit detail):\s*", ". Visual clothing state: ", text, flags=re.IGNORECASE)
|
||||||
|
text = text.replace("softcore outfit", "outfit")
|
||||||
|
text = text.replace("teaser outfit", "outfit")
|
||||||
|
text = text.replace("hardcore scene", "sex scene")
|
||||||
|
return text
|
||||||
+2
-58
@@ -16,6 +16,7 @@ try:
|
|||||||
natural_label_text as _natural_label_text,
|
natural_label_text as _natural_label_text,
|
||||||
prompt_cast_descriptors as _prompt_cast_descriptors,
|
prompt_cast_descriptors as _prompt_cast_descriptors,
|
||||||
)
|
)
|
||||||
|
from .krea_clothing import natural_clothing_state as _natural_clothing_state
|
||||||
from .prompt_hygiene import sanitize_negative_text, sanitize_prose_text
|
from .prompt_hygiene import sanitize_negative_text, sanitize_prose_text
|
||||||
except ImportError: # Allows local smoke tests with `python -c`.
|
except ImportError: # Allows local smoke tests with `python -c`.
|
||||||
from hardcore_text_cleanup import (
|
from hardcore_text_cleanup import (
|
||||||
@@ -29,6 +30,7 @@ except ImportError: # Allows local smoke tests with `python -c`.
|
|||||||
natural_label_text as _natural_label_text,
|
natural_label_text as _natural_label_text,
|
||||||
prompt_cast_descriptors as _prompt_cast_descriptors,
|
prompt_cast_descriptors as _prompt_cast_descriptors,
|
||||||
)
|
)
|
||||||
|
from krea_clothing import natural_clothing_state as _natural_clothing_state
|
||||||
from prompt_hygiene import sanitize_negative_text, sanitize_prose_text
|
from prompt_hygiene import sanitize_negative_text, sanitize_prose_text
|
||||||
|
|
||||||
|
|
||||||
@@ -593,64 +595,6 @@ def _sanitize_scene_text_for_cast(text: Any, labels: list[str]) -> str:
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
def _clothing_access_phrase(action_text: Any) -> str:
|
|
||||||
text = _clean(action_text).lower()
|
|
||||||
if any(term in text for term in ("cumshot", "ejaculat", "semen", "cum on", "cum across", "post-orgasm", "aftermath")):
|
|
||||||
return "leaving the body exposed for visible semen and aftermath"
|
|
||||||
if any(term in text for term in ("boobjob", "titjob", "breast sex", "handjob", "hand job", "footjob", "testicle", "balls", "penis licking", "non-penetrative")):
|
|
||||||
return "leaving the contact point unobstructed"
|
|
||||||
if any(term in text for term in ("oral", "blowjob", "fellatio", "mouth", "tongue")):
|
|
||||||
return "leaving the oral contact unobstructed"
|
|
||||||
if any(term in text for term in ("penetrat", "thrust", "penis entering", "vaginal", "anal")):
|
|
||||||
return "leaving the penetration point unobstructed"
|
|
||||||
return "leaving skin and body contact readable"
|
|
||||||
|
|
||||||
|
|
||||||
def _natural_clothing_state(text: Any, action_text: Any = "") -> str:
|
|
||||||
text = _clean(text)
|
|
||||||
if not text:
|
|
||||||
return ""
|
|
||||||
text = re.sub(r"^Clothing state:\s*", "", text, flags=re.IGNORECASE)
|
|
||||||
if re.search(r";\s*(?=(?:Woman|Man) [A-Z]\b)", text):
|
|
||||||
parts = [
|
|
||||||
_natural_clothing_state(part, action_text).rstrip(".")
|
|
||||||
for part in re.split(r";\s*(?=(?:Woman|Man) [A-Z]\b)", text)
|
|
||||||
if _clean(part)
|
|
||||||
]
|
|
||||||
return ". ".join(part for part in parts if part)
|
|
||||||
body_exposure = re.match(r"^Body exposure:\s*(.*?)\.?$", text, flags=re.IGNORECASE)
|
|
||||||
if body_exposure:
|
|
||||||
return _clean(body_exposure.group(1)).rstrip(".")
|
|
||||||
if re.search(r"\bfully nude\b|\bbody is fully exposed\b|\bno clothing covering\b", text, flags=re.IGNORECASE):
|
|
||||||
owner = "the woman"
|
|
||||||
owner_match = re.match(r"^\s*((?:Woman|Man) [A-Z])\b", text)
|
|
||||||
if owner_match:
|
|
||||||
owner = _natural_label_text(owner_match.group(1), ["Woman A", "Man A"]) or owner
|
|
||||||
return f"{owner.capitalize()}'s body is fully exposed, bare skin unobstructed"
|
|
||||||
match = re.match(
|
|
||||||
r"^(.*?)\b(?:softcore|teaser) outfit is (.*?)(?: for the (?:hardcore|sex) scene)?;\s*(?:softcore visual reference|teaser outfit detail):\s*(.*?)\.?$",
|
|
||||||
text,
|
|
||||||
flags=re.IGNORECASE,
|
|
||||||
)
|
|
||||||
if match:
|
|
||||||
owner = _natural_label_text(match.group(1).strip(" 's"), ["Woman A", "Man A"]).strip() or "the woman"
|
|
||||||
state = _clean(match.group(2)).lower()
|
|
||||||
outfit = _clean(match.group(3)).rstrip(".")
|
|
||||||
if "fully nude" in state or "fully exposed" in state or "no clothing covering" in state:
|
|
||||||
return f"{owner.capitalize()}'s body is fully exposed, bare skin unobstructed"
|
|
||||||
if "nude-adjacent" in state:
|
|
||||||
return f"{owner.capitalize()}'s body is partly exposed"
|
|
||||||
if "partially removed" in state or "pushed aside" in state:
|
|
||||||
return f"{owner.capitalize()}'s {outfit} is pushed aside or partly removed where needed, {_clothing_access_phrase(action_text)}"
|
|
||||||
if "keeps" in state:
|
|
||||||
return f"{owner.capitalize()} keeps the {outfit} on while {_clothing_access_phrase(action_text)}"
|
|
||||||
text = re.sub(r";\s*(?:softcore visual reference|teaser outfit detail):\s*", ". Visual clothing state: ", text, flags=re.IGNORECASE)
|
|
||||||
text = text.replace("softcore outfit", "outfit")
|
|
||||||
text = text.replace("teaser outfit", "outfit")
|
|
||||||
text = text.replace("hardcore scene", "sex scene")
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
def _axis_values_text(axis_values: Any) -> str:
|
def _axis_values_text(axis_values: Any) -> str:
|
||||||
if not isinstance(axis_values, dict):
|
if not isinstance(axis_values, dict):
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -468,6 +468,33 @@ def smoke_insta_pair() -> None:
|
|||||||
_expect("teaser outfit detail" not in clothing_state, "explicit nude pair should not repeat softcore outfit detail")
|
_expect("teaser outfit detail" not in clothing_state, "explicit nude pair should not repeat softcore outfit detail")
|
||||||
|
|
||||||
|
|
||||||
|
def smoke_krea_pair_clothing_state() -> None:
|
||||||
|
pair = pb.build_insta_of_pair(
|
||||||
|
row_number=1,
|
||||||
|
start_index=1,
|
||||||
|
seed=3511,
|
||||||
|
ethnicity="any",
|
||||||
|
figure="random",
|
||||||
|
no_plus_women=False,
|
||||||
|
no_black=False,
|
||||||
|
trigger=Trigger,
|
||||||
|
prepend_trigger_to_prompt=True,
|
||||||
|
options_json=_insta_options(hardcore_clothing_continuity="partially_removed"),
|
||||||
|
character_cast=_character_cast(),
|
||||||
|
hardcore_position_config=_action_filter("penetration_only"),
|
||||||
|
)
|
||||||
|
_expect_pair(pair, "krea_pair_clothing_state")
|
||||||
|
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore")
|
||||||
|
prompt = _expect_text("krea_pair_clothing_state.krea_prompt", krea.get("krea_prompt"), 60)
|
||||||
|
lower = prompt.lower()
|
||||||
|
_expect("metadata" in krea.get("method", ""), "pair clothing route did not use metadata")
|
||||||
|
_expect("clothing state:" not in lower, "Krea clothing route leaked raw clothing label")
|
||||||
|
_expect("visual clothing state" not in lower, "Krea clothing route fell back to visual clothing state label")
|
||||||
|
_expect("softcore outfit" not in lower and "teaser outfit" not in lower, "Krea clothing route leaked softcore outfit label")
|
||||||
|
_expect("lower body is clear" in lower, "Krea clothing route lost generated clothing continuity")
|
||||||
|
_expect("the man keeps" in lower, "Krea clothing route lost partner clothing continuity")
|
||||||
|
|
||||||
|
|
||||||
def smoke_insta_pair_pov() -> None:
|
def smoke_insta_pair_pov() -> None:
|
||||||
pair = pb.build_insta_of_pair(
|
pair = pb.build_insta_of_pair(
|
||||||
row_number=1,
|
row_number=1,
|
||||||
@@ -654,6 +681,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
|
|||||||
("hardcore_category_routes", smoke_hardcore_category_routes),
|
("hardcore_category_routes", smoke_hardcore_category_routes),
|
||||||
("krea_close_foreplay_route", smoke_krea_close_foreplay_route),
|
("krea_close_foreplay_route", smoke_krea_close_foreplay_route),
|
||||||
("insta_pair_same_cast", smoke_insta_pair),
|
("insta_pair_same_cast", smoke_insta_pair),
|
||||||
|
("krea_pair_clothing_state", smoke_krea_pair_clothing_state),
|
||||||
("insta_pair_pov_man", smoke_insta_pair_pov),
|
("insta_pair_pov_man", smoke_insta_pair_pov),
|
||||||
("insta_pair_camera_split", smoke_insta_pair_camera_split),
|
("insta_pair_camera_split", smoke_insta_pair_camera_split),
|
||||||
("pov_camera_scene", smoke_pov_camera_scene),
|
("pov_camera_scene", smoke_pov_camera_scene),
|
||||||
|
|||||||
Reference in New Issue
Block a user