Extract Krea cast and hardcore cleanup helpers
This commit is contained in:
@@ -61,6 +61,15 @@ route-specific owner. It also preserves ordinary words such as `composition`
|
|||||||
inside normal sentences; empty field-label cleanup is limited to standalone
|
inside normal sentences; empty field-label cleanup is limited to standalone
|
||||||
labels.
|
labels.
|
||||||
|
|
||||||
|
Shared hardcore phrase cleanup now has one home:
|
||||||
|
|
||||||
|
- `hardcore_text_cleanup.py`
|
||||||
|
|
||||||
|
It owns environment-anchor normalization used by both prompt generation and
|
||||||
|
Krea formatting, including malformed surface joins and bed/sheet/couch anchors
|
||||||
|
that should become model-neutral body-support language. It must stay
|
||||||
|
route-neutral: no Krea prose, no SDXL tags, and no category selection logic.
|
||||||
|
|
||||||
Current integration points:
|
Current integration points:
|
||||||
|
|
||||||
- `prompt_builder.build_prompt`
|
- `prompt_builder.build_prompt`
|
||||||
@@ -95,8 +104,9 @@ Already isolated:
|
|||||||
- camera-scene prose and coworking composition adaptation live in
|
- camera-scene prose and coworking composition adaptation live in
|
||||||
`scene_camera_adapters.py`; `prompt_builder.py` still owns camera config
|
`scene_camera_adapters.py`; `prompt_builder.py` still owns camera config
|
||||||
parsing and row mutation.
|
parsing and row mutation.
|
||||||
- shared hardcore environment-anchor cleanup normalizes malformed pool joins
|
- shared hardcore environment-anchor cleanup lives in
|
||||||
such as `on against a wall` before metadata reaches formatter routes.
|
`hardcore_text_cleanup.py` and normalizes malformed pool joins before metadata
|
||||||
|
reaches formatter routes.
|
||||||
|
|
||||||
### Pair / Adapter Layer
|
### Pair / Adapter Layer
|
||||||
|
|
||||||
@@ -125,17 +135,21 @@ Owner: `krea_formatter.py`.
|
|||||||
Keep here:
|
Keep here:
|
||||||
|
|
||||||
- Krea prose style;
|
- Krea prose style;
|
||||||
- cast prose;
|
|
||||||
- hardcore action sentence rewriting;
|
- hardcore action sentence rewriting;
|
||||||
- POV sentence rewriting;
|
- POV sentence rewriting;
|
||||||
- clothing naturalization;
|
- clothing naturalization;
|
||||||
- camera-scene preservation;
|
- camera-scene preservation;
|
||||||
- fallback text parsing.
|
- fallback text parsing.
|
||||||
|
|
||||||
|
Already isolated:
|
||||||
|
|
||||||
|
- `krea_cast.py` owns cast descriptor parsing, cast prose, label joining, and
|
||||||
|
natural label replacement for formatter routes.
|
||||||
|
|
||||||
Improve later:
|
Improve later:
|
||||||
|
|
||||||
- split semantic blocks into modules:
|
- split semantic blocks into modules:
|
||||||
`krea_cast.py`, `krea_actions.py`, `krea_pov.py`, `krea_clothing.py`;
|
`krea_actions.py`, `krea_pov.py`, `krea_clothing.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.
|
||||||
@@ -316,7 +330,8 @@ Medium-term:
|
|||||||
|
|
||||||
## Recommended Next Passes
|
## Recommended Next Passes
|
||||||
|
|
||||||
1. Split Krea action/POV/clothing helpers into separate modules.
|
1. Split Krea action/POV/clothing helpers into separate modules, using
|
||||||
|
`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.
|
||||||
3. Add metadata fields such as `action_family` / `position_family` to reduce
|
3. Add metadata fields such as `action_family` / `position_family` to reduce
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ These recipes identify the intended road before editing prompt text.
|
|||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| Keep character/location but change only sexual pose | `Global Seed` or fixed seed config -> builder/pair | Keep `person_seed` and `scene_seed` fixed; change `pose_seed` and usually `role_seed`; for hardcore categories check `content_seed_axis` | `sexual_poses.json`, `hardcore_position_config`, Krea `_hardcore_action_sentence` |
|
| Keep character/location but change only sexual pose | `Global Seed` or fixed seed config -> builder/pair | Keep `person_seed` and `scene_seed` fixed; change `pose_seed` and usually `role_seed`; for hardcore categories check `content_seed_axis` | `sexual_poses.json`, `hardcore_position_config`, Krea `_hardcore_action_sentence` |
|
||||||
| Generate a specific hardcore oral/blowjob scene | `Hardcore Position Pool` -> `Hardcore Action Filter` -> `Insta/OF Prompt Pair` or `Prompt Builder` | Use `focus=oral_only` or disable non-oral families; keep `allow_oral=true`; constrain position pool to kneeling/standing/oral variants when needed | `sexual_poses.json` oral subcategory/templates, `_apply_hardcore_position_config_to_subcategory`, `_hardcore_action_sentence` |
|
| Generate a specific hardcore oral/blowjob scene | `Hardcore Position Pool` -> `Hardcore Action Filter` -> `Insta/OF Prompt Pair` or `Prompt Builder` | Use `focus=oral_only` or disable non-oral families; keep `allow_oral=true`; constrain position pool to kneeling/standing/oral variants when needed | `sexual_poses.json` oral subcategory/templates, `_apply_hardcore_position_config_to_subcategory`, `_hardcore_action_sentence` |
|
||||||
| Generate POV oral or POV penetration | `Man Slot` with POV presence -> `character_cast` -> pair/builder -> Krea2 formatter | POV man must be in the cast; use metadata into Krea2; normal camera directive is suppressed by POV | `_pov_hardcore_pose_sentence`, `_pov_action_phrase`, `_cast_prose` omit-label handling |
|
| Generate POV oral or POV penetration | `Man Slot` with POV presence -> `character_cast` -> pair/builder -> Krea2 formatter | POV man must be in the cast; use metadata into Krea2; normal camera directive is suppressed by POV | `_pov_hardcore_pose_sentence`, `_pov_action_phrase`, `krea_cast.cast_prose` omit-label handling |
|
||||||
| Generate porn-scene interaction beats | `Hardcore Position Pool` -> `Hardcore Action Filter` -> pair/builder | Use `focus=interaction_only` for kissing/body worship/transitions/guidance/camera/watching/aftercare, or `focus=manual_only` for fingering/clit/manual stimulation; constrain keys such as `camera_showing`, `wrist_pinning`, `fingering`, `aftercare` | `sexual_poses.json` interaction/manual subcategories, `_role_graph`, Krea `_is_foreplay_text` / `_hardcore_action_sentence` |
|
| Generate porn-scene interaction beats | `Hardcore Position Pool` -> `Hardcore Action Filter` -> pair/builder | Use `focus=interaction_only` for kissing/body worship/transitions/guidance/camera/watching/aftercare, or `focus=manual_only` for fingering/clit/manual stimulation; constrain keys such as `camera_showing`, `wrist_pinning`, `fingering`, `aftercare` | `sexual_poses.json` interaction/manual subcategories, `_role_graph`, Krea `_is_foreplay_text` / `_hardcore_action_sentence` |
|
||||||
| Same woman, same room, softcore and hardcore outputs | `Character Slot/Profile` -> `Insta/OF Options` -> `Insta/OF Prompt Pair` | `continuity=same_creator_same_room`; set `softcore_cast` as needed; use pair metadata into formatter | `build_insta_of_pair`, `softcore_row`, `hardcore_row`, pair metadata fields |
|
| Same woman, same room, softcore and hardcore outputs | `Character Slot/Profile` -> `Insta/OF Options` -> `Insta/OF Prompt Pair` | `continuity=same_creator_same_room`; set `softcore_cast` as needed; use pair metadata into formatter | `build_insta_of_pair`, `softcore_row`, `hardcore_row`, pair metadata fields |
|
||||||
| Same cast in softcore and hardcore | Character slot chain -> `Insta/OF Options` | `softcore_cast=same_as_hardcore`; configure partner slots/outfits if needed | `_insta_of_partner_styling`, character slot clothing, pair Krea branch |
|
| Same cast in softcore and hardcore | Character slot chain -> `Insta/OF Options` | `softcore_cast=same_as_hardcore`; configure partner slots/outfits if needed | `_insta_of_partner_styling`, character slot clothing, pair Krea branch |
|
||||||
@@ -715,6 +715,7 @@ pair metadata through the core Python APIs, then verifies:
|
|||||||
| Trigger missing in Krea2 fallback | `format_krea2_prompt` preserve-trigger fallback behavior. |
|
| Trigger missing in Krea2 fallback | `format_krea2_prompt` preserve-trigger fallback behavior. |
|
||||||
| SDXL tags too weak/wrong style | `sdxl_formatter.py` presets and `_row_core_tags` / `_soft_tags` / `_hard_tags`. |
|
| SDXL tags too weak/wrong style | `sdxl_formatter.py` presets and `_row_core_tags` / `_soft_tags` / `_hard_tags`. |
|
||||||
| Duplicate punctuation, empty labels, repeated trigger, repeated tag item | `prompt_hygiene.py`, then the route-specific formatter if the repeated content is semantic. |
|
| Duplicate punctuation, empty labels, repeated trigger, repeated tag item | `prompt_hygiene.py`, then the route-specific formatter if the repeated content is semantic. |
|
||||||
|
| Bed/sheet/couch or malformed surface wording leaks into hardcore prompts | `hardcore_text_cleanup.py`, then the relevant category pool/template. |
|
||||||
| Saved profile does not match liked character | Profile save/load path and whether the saved input is row metadata or regenerated slot config. |
|
| Saved profile does not match liked character | Profile save/load path and whether the saved input is row metadata or regenerated slot config. |
|
||||||
| Accumulator preview behavior wrong | `loop_nodes.py` accumulator methods and `web/accumulator_preview.js`. |
|
| Accumulator preview behavior wrong | `loop_nodes.py` accumulator methods and `web/accumulator_preview.js`. |
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
HARDCORE_ENVIRONMENT_ANCHOR_REPLACEMENTS = (
|
||||||
|
(r"\bon against a wall\b", "against a wall"),
|
||||||
|
(r"\bstacked bodies on the bed\b", "close body alignment"),
|
||||||
|
(r"\bstacked bodies with close body alignment\b", "close body alignment"),
|
||||||
|
(r"\boverhead tangled-body anal frame\b", "overhead rear-entry anal frame"),
|
||||||
|
(r"\btangled-body\b", "close-body"),
|
||||||
|
(r"\bthree bodies tangled on the bed\b", "three bodies tangled in close contact"),
|
||||||
|
(r"\ba triangle of bodies on the mattress\b", "a triangle of bodies in close contact"),
|
||||||
|
(r"\bbodies tangled on the sheets\b", "bodies tangled in close contact"),
|
||||||
|
(r"\bwet bodies tangled on sheets\b", "wet bodies tangled in close contact"),
|
||||||
|
(r"\bbody arched on rumpled sheets\b", "body arched with clear skin contact"),
|
||||||
|
(r"\bface-down ass-up on the mattress\b", "face-down ass-up position"),
|
||||||
|
(r"\bsitting on the edge of the bed\b", "sitting on a raised edge"),
|
||||||
|
(r"\blying at the bed edge with thighs open\b", "lying near a raised edge with thighs open"),
|
||||||
|
(r"\bedge[- ]of[- ]bed\b", "edge-supported"),
|
||||||
|
(r"\bbed[- ]edge\b", "raised edge"),
|
||||||
|
(r"\bedge of (?:the )?bed\b", "raised edge"),
|
||||||
|
(r"\bbed edge\b", "raised edge"),
|
||||||
|
(r"\bhands? braced on the bed\b", "hands braced beside the body"),
|
||||||
|
(r"\bone hand pressing into the mattress\b", "one hand braced beside the body"),
|
||||||
|
(r"\bone foot planted on the bed\b", "one foot planted for leverage"),
|
||||||
|
(r"\bfingers gripping sheets and skin\b", "fingers gripping skin"),
|
||||||
|
(r"\bfingers gripping sheets\b", "fingers gripping skin"),
|
||||||
|
(r"\bhands gripping sheets\b", "hands gripping skin"),
|
||||||
|
(r"\bone hand gripping the sheets\b", "one hand gripping skin"),
|
||||||
|
(r"\brumpled bed sheets\b", "wrinkled body-contact fabric"),
|
||||||
|
(r"\bwet sheets beneath the bodies\b", "visible wetness beneath the bodies"),
|
||||||
|
(r"\bsexual fluids on sheets\b", "sexual fluids visible on skin"),
|
||||||
|
(r"\bcum dripping onto sheets\b", "cum visible on skin"),
|
||||||
|
(r"\bfluid dripping onto sheets\b", "fluid visible on skin"),
|
||||||
|
(r"\bsquirting fluid on the sheets\b", "squirting fluid visible on skin"),
|
||||||
|
(r"\bsoft sheets\b", "soft fabric"),
|
||||||
|
(r"\bsilk sheets\b", "silk fabric"),
|
||||||
|
(r"\bsheets\b", "fabric"),
|
||||||
|
(r"\bmattress\b", "low support surface"),
|
||||||
|
(r"\ba low support surface\b", "a low body support"),
|
||||||
|
(r"\ba low mattress\b", "a low body support"),
|
||||||
|
(r"\ba wide couch\b", "a wide body support"),
|
||||||
|
(r"\bwide couch\b", "wide body support"),
|
||||||
|
(r"\bcouch\b", "body support"),
|
||||||
|
(r"\bsofa\b", "body support"),
|
||||||
|
(r"\bon the bed\b", "on a body support"),
|
||||||
|
(r"\bon a bed\b", "on a body support"),
|
||||||
|
(r"\bbedroom-floor\b", "floor-level"),
|
||||||
|
(r"\bbedroom floor\b", "floor-level"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _clean_inline(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 sanitize_hardcore_environment_anchors(value: Any) -> str:
|
||||||
|
text = _clean_inline(value)
|
||||||
|
if not text:
|
||||||
|
return ""
|
||||||
|
for pattern, replacement in HARDCORE_ENVIRONMENT_ANCHOR_REPLACEMENTS:
|
||||||
|
text = re.sub(pattern, replacement, text, flags=re.IGNORECASE)
|
||||||
|
text = re.sub(r"\s+,", ",", text)
|
||||||
|
text = re.sub(r",\s*,", ",", text)
|
||||||
|
text = re.sub(r"\s{2,}", " ", text)
|
||||||
|
return text.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_hardcore_axis_values(values: Any) -> dict[str, str]:
|
||||||
|
if not isinstance(values, dict):
|
||||||
|
return {}
|
||||||
|
return {
|
||||||
|
str(key): sanitize_hardcore_environment_anchors(value)
|
||||||
|
for key, value in values.items()
|
||||||
|
}
|
||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
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 _with_indefinite_article(text: str) -> str:
|
||||||
|
text = _clean(text)
|
||||||
|
if not text or text.lower().startswith(("a ", "an ")):
|
||||||
|
return text
|
||||||
|
article = "an" if text[:1].lower() in "aeiou" else "a"
|
||||||
|
return f"{article} {text}"
|
||||||
|
|
||||||
|
|
||||||
|
def prompt_cast_descriptors(text: str) -> str:
|
||||||
|
return _clean(text).replace("Woman A / primary creator:", "Woman A:")
|
||||||
|
|
||||||
|
|
||||||
|
def cast_entries(text: str) -> list[tuple[str, str]]:
|
||||||
|
text = prompt_cast_descriptors(text)
|
||||||
|
entries: list[tuple[str, str]] = []
|
||||||
|
for part in text.split(";"):
|
||||||
|
part = _clean(part)
|
||||||
|
match = re.match(r"^((?:Woman|Man) [A-Z]):\s*(.+)$", part)
|
||||||
|
if match:
|
||||||
|
entries.append((match.group(1), _clean(match.group(2))))
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
def label_join(labels: list[str]) -> str:
|
||||||
|
labels = [_clean(label) for label in labels if _clean(label)]
|
||||||
|
if not labels:
|
||||||
|
return "the named adults"
|
||||||
|
if set(labels) == {"Woman A", "Man A"}:
|
||||||
|
return "the woman and man"
|
||||||
|
if len(labels) == 1:
|
||||||
|
if labels[0] == "Woman A":
|
||||||
|
return "the woman"
|
||||||
|
if labels[0] == "Man A":
|
||||||
|
return "the man"
|
||||||
|
return labels[0]
|
||||||
|
if len(labels) == 2:
|
||||||
|
return f"{labels[0]} and {labels[1]}"
|
||||||
|
return f"{', '.join(labels[:-1])}, and {labels[-1]}"
|
||||||
|
|
||||||
|
|
||||||
|
def natural_label_text(text: Any, labels: list[str]) -> str:
|
||||||
|
text = _clean(text)
|
||||||
|
if not text:
|
||||||
|
return ""
|
||||||
|
if set(labels) == {"Woman A", "Man A"}:
|
||||||
|
text = re.sub(r"\bWoman A\b", "the woman", text)
|
||||||
|
text = re.sub(r"\bMan A\b", "the man", text)
|
||||||
|
elif labels == ["Woman A"]:
|
||||||
|
text = re.sub(r"\bWoman A\b", "the woman", text)
|
||||||
|
elif labels == ["Man A"]:
|
||||||
|
text = re.sub(r"\bMan A\b", "the man", text)
|
||||||
|
text = re.sub(
|
||||||
|
r"(^|[.!?]\s+)(the woman|the man)\b",
|
||||||
|
lambda match: match.group(1) + match.group(2).capitalize(),
|
||||||
|
text,
|
||||||
|
flags=re.IGNORECASE,
|
||||||
|
)
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def lowercase_for_inline_join(text: str) -> str:
|
||||||
|
return re.sub(
|
||||||
|
r"^(The woman|The man|The viewer|The named adults)\b",
|
||||||
|
lambda match: match.group(1).lower(),
|
||||||
|
_clean(text),
|
||||||
|
flags=re.IGNORECASE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def cast_prose(
|
||||||
|
text: str,
|
||||||
|
central_label: str = "Woman A",
|
||||||
|
omit_labels: list[str] | set[str] | tuple[str, ...] = (),
|
||||||
|
) -> tuple[str, list[str]]:
|
||||||
|
raw_entries = cast_entries(text)
|
||||||
|
omitted = set(omit_labels or [])
|
||||||
|
entries = [(label, descriptor) for label, descriptor in raw_entries if label not in omitted]
|
||||||
|
if raw_entries and not entries:
|
||||||
|
return "", []
|
||||||
|
if not entries:
|
||||||
|
return (f"{central_label} is {_clean(text)}" if _clean(text) else "", [])
|
||||||
|
labels = [label for label, _descriptor in entries]
|
||||||
|
if labels == ["Woman A"]:
|
||||||
|
return _with_indefinite_article(entries[0][1]), labels
|
||||||
|
if labels == ["Man A"]:
|
||||||
|
return _with_indefinite_article(entries[0][1]), labels
|
||||||
|
if set(labels) == {"Woman A", "Man A"} and len(labels) == 2:
|
||||||
|
by_label = {label: descriptor for label, descriptor in entries}
|
||||||
|
return f"{_with_indefinite_article(by_label['Woman A'])} alongside {_with_indefinite_article(by_label['Man A'])}", labels
|
||||||
|
sentences = []
|
||||||
|
for label, descriptor in entries:
|
||||||
|
sentences.append(f"{label} is {descriptor}.")
|
||||||
|
if central_label in labels:
|
||||||
|
sentences.append(f"{central_label} is the central subject.")
|
||||||
|
return " ".join(sentences), labels
|
||||||
+22
-155
@@ -5,8 +5,30 @@ import re
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
from .hardcore_text_cleanup import (
|
||||||
|
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
|
||||||
|
sanitize_hardcore_environment_anchors as _sanitize_hardcore_environment_anchors,
|
||||||
|
)
|
||||||
|
from .krea_cast import (
|
||||||
|
cast_prose as _cast_prose,
|
||||||
|
label_join as _label_join,
|
||||||
|
lowercase_for_inline_join as _lowercase_for_inline_join,
|
||||||
|
natural_label_text as _natural_label_text,
|
||||||
|
prompt_cast_descriptors as _prompt_cast_descriptors,
|
||||||
|
)
|
||||||
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 (
|
||||||
|
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
|
||||||
|
sanitize_hardcore_environment_anchors as _sanitize_hardcore_environment_anchors,
|
||||||
|
)
|
||||||
|
from krea_cast import (
|
||||||
|
cast_prose as _cast_prose,
|
||||||
|
label_join as _label_join,
|
||||||
|
lowercase_for_inline_join as _lowercase_for_inline_join,
|
||||||
|
natural_label_text as _natural_label_text,
|
||||||
|
prompt_cast_descriptors as _prompt_cast_descriptors,
|
||||||
|
)
|
||||||
from prompt_hygiene import sanitize_negative_text, sanitize_prose_text
|
from prompt_hygiene import sanitize_negative_text, sanitize_prose_text
|
||||||
|
|
||||||
|
|
||||||
@@ -46,72 +68,6 @@ def _clean(value: Any) -> str:
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
HARDCORE_ENVIRONMENT_ANCHOR_REPLACEMENTS = (
|
|
||||||
(r"\bon against a wall\b", "against a wall"),
|
|
||||||
(r"\bstacked bodies on the bed\b", "close body alignment"),
|
|
||||||
(r"\bstacked bodies with close body alignment\b", "close body alignment"),
|
|
||||||
(r"\boverhead tangled-body anal frame\b", "overhead rear-entry anal frame"),
|
|
||||||
(r"\btangled-body\b", "close-body"),
|
|
||||||
(r"\bthree bodies tangled on the bed\b", "three bodies tangled in close contact"),
|
|
||||||
(r"\ba triangle of bodies on the mattress\b", "a triangle of bodies in close contact"),
|
|
||||||
(r"\bbodies tangled on the sheets\b", "bodies tangled in close contact"),
|
|
||||||
(r"\bwet bodies tangled on sheets\b", "wet bodies tangled in close contact"),
|
|
||||||
(r"\bbody arched on rumpled sheets\b", "body arched with clear skin contact"),
|
|
||||||
(r"\bface-down ass-up on the mattress\b", "face-down ass-up position"),
|
|
||||||
(r"\bsitting on the edge of the bed\b", "sitting on a raised edge"),
|
|
||||||
(r"\blying at the bed edge with thighs open\b", "lying near a raised edge with thighs open"),
|
|
||||||
(r"\bedge[- ]of[- ]bed\b", "edge-supported"),
|
|
||||||
(r"\bbed[- ]edge\b", "raised edge"),
|
|
||||||
(r"\bedge of (?:the )?bed\b", "raised edge"),
|
|
||||||
(r"\bbed edge\b", "raised edge"),
|
|
||||||
(r"\bhands? braced on the bed\b", "hands braced beside the body"),
|
|
||||||
(r"\bone hand pressing into the mattress\b", "one hand braced beside the body"),
|
|
||||||
(r"\bone foot planted on the bed\b", "one foot planted for leverage"),
|
|
||||||
(r"\bfingers gripping sheets and skin\b", "fingers gripping skin"),
|
|
||||||
(r"\bfingers gripping sheets\b", "fingers gripping skin"),
|
|
||||||
(r"\bhands gripping sheets\b", "hands gripping skin"),
|
|
||||||
(r"\bone hand gripping the sheets\b", "one hand gripping skin"),
|
|
||||||
(r"\brumpled bed sheets\b", "wrinkled body-contact fabric"),
|
|
||||||
(r"\bwet sheets beneath the bodies\b", "visible wetness beneath the bodies"),
|
|
||||||
(r"\bsexual fluids on sheets\b", "sexual fluids visible on skin"),
|
|
||||||
(r"\bcum dripping onto sheets\b", "cum visible on skin"),
|
|
||||||
(r"\bfluid dripping onto sheets\b", "fluid visible on skin"),
|
|
||||||
(r"\bsquirting fluid on the sheets\b", "squirting fluid visible on skin"),
|
|
||||||
(r"\bsoft sheets\b", "soft fabric"),
|
|
||||||
(r"\bsilk sheets\b", "silk fabric"),
|
|
||||||
(r"\bsheets\b", "fabric"),
|
|
||||||
(r"\bmattress\b", "low support surface"),
|
|
||||||
(r"\ba low support surface\b", "a low body support"),
|
|
||||||
(r"\ba low mattress\b", "a low body support"),
|
|
||||||
(r"\ba wide couch\b", "a wide body support"),
|
|
||||||
(r"\bwide couch\b", "wide body support"),
|
|
||||||
(r"\bcouch\b", "body support"),
|
|
||||||
(r"\bsofa\b", "body support"),
|
|
||||||
(r"\bon the bed\b", "on a body support"),
|
|
||||||
(r"\bon a bed\b", "on a body support"),
|
|
||||||
(r"\bbedroom-floor\b", "floor-level"),
|
|
||||||
(r"\bbedroom floor\b", "floor-level"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _sanitize_hardcore_environment_anchors(value: Any) -> str:
|
|
||||||
text = _clean(value)
|
|
||||||
if not text:
|
|
||||||
return ""
|
|
||||||
for pattern, replacement in HARDCORE_ENVIRONMENT_ANCHOR_REPLACEMENTS:
|
|
||||||
text = re.sub(pattern, replacement, text, flags=re.IGNORECASE)
|
|
||||||
text = re.sub(r"\s+,", ",", text)
|
|
||||||
text = re.sub(r",\s*,", ",", text)
|
|
||||||
text = re.sub(r"\s{2,}", " ", text)
|
|
||||||
return text.strip()
|
|
||||||
|
|
||||||
|
|
||||||
def _sanitize_hardcore_axis_values(values: Any) -> dict[str, str]:
|
|
||||||
if not isinstance(values, dict):
|
|
||||||
return {}
|
|
||||||
return {str(key): _sanitize_hardcore_environment_anchors(value) for key, value in values.items()}
|
|
||||||
|
|
||||||
|
|
||||||
def _is_false(value: Any) -> bool:
|
def _is_false(value: Any) -> bool:
|
||||||
if isinstance(value, bool):
|
if isinstance(value, bool):
|
||||||
return value is False
|
return value is False
|
||||||
@@ -255,95 +211,6 @@ def _combine_negative(*parts: str) -> str:
|
|||||||
return ", ".join(cleaned)
|
return ", ".join(cleaned)
|
||||||
|
|
||||||
|
|
||||||
def _prompt_cast_descriptors(text: str) -> str:
|
|
||||||
return _clean(text).replace("Woman A / primary creator:", "Woman A:")
|
|
||||||
|
|
||||||
|
|
||||||
def _cast_entries(text: str) -> list[tuple[str, str]]:
|
|
||||||
text = _prompt_cast_descriptors(text)
|
|
||||||
entries: list[tuple[str, str]] = []
|
|
||||||
for part in text.split(";"):
|
|
||||||
part = _clean(part)
|
|
||||||
match = re.match(r"^((?:Woman|Man) [A-Z]):\s*(.+)$", part)
|
|
||||||
if match:
|
|
||||||
entries.append((match.group(1), _clean(match.group(2))))
|
|
||||||
return entries
|
|
||||||
|
|
||||||
|
|
||||||
def _label_join(labels: list[str]) -> str:
|
|
||||||
labels = [_clean(label) for label in labels if _clean(label)]
|
|
||||||
if not labels:
|
|
||||||
return "the named adults"
|
|
||||||
if set(labels) == {"Woman A", "Man A"}:
|
|
||||||
return "the woman and man"
|
|
||||||
if len(labels) == 1:
|
|
||||||
if labels[0] == "Woman A":
|
|
||||||
return "the woman"
|
|
||||||
if labels[0] == "Man A":
|
|
||||||
return "the man"
|
|
||||||
return labels[0]
|
|
||||||
if len(labels) == 2:
|
|
||||||
return f"{labels[0]} and {labels[1]}"
|
|
||||||
return f"{', '.join(labels[:-1])}, and {labels[-1]}"
|
|
||||||
|
|
||||||
|
|
||||||
def _natural_label_text(text: Any, labels: list[str]) -> str:
|
|
||||||
text = _clean(text)
|
|
||||||
if not text:
|
|
||||||
return ""
|
|
||||||
if set(labels) == {"Woman A", "Man A"}:
|
|
||||||
text = re.sub(r"\bWoman A\b", "the woman", text)
|
|
||||||
text = re.sub(r"\bMan A\b", "the man", text)
|
|
||||||
elif labels == ["Woman A"]:
|
|
||||||
text = re.sub(r"\bWoman A\b", "the woman", text)
|
|
||||||
elif labels == ["Man A"]:
|
|
||||||
text = re.sub(r"\bMan A\b", "the man", text)
|
|
||||||
text = re.sub(
|
|
||||||
r"(^|[.!?]\s+)(the woman|the man)\b",
|
|
||||||
lambda match: match.group(1) + match.group(2).capitalize(),
|
|
||||||
text,
|
|
||||||
flags=re.IGNORECASE,
|
|
||||||
)
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
def _lowercase_for_inline_join(text: str) -> str:
|
|
||||||
return re.sub(
|
|
||||||
r"^(The woman|The man|The viewer|The named adults)\b",
|
|
||||||
lambda match: match.group(1).lower(),
|
|
||||||
_clean(text),
|
|
||||||
flags=re.IGNORECASE,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _cast_prose(
|
|
||||||
text: str,
|
|
||||||
central_label: str = "Woman A",
|
|
||||||
omit_labels: list[str] | set[str] | tuple[str, ...] = (),
|
|
||||||
) -> tuple[str, list[str]]:
|
|
||||||
raw_entries = _cast_entries(text)
|
|
||||||
omitted = set(omit_labels or [])
|
|
||||||
entries = [(label, descriptor) for label, descriptor in raw_entries if label not in omitted]
|
|
||||||
if raw_entries and not entries:
|
|
||||||
return "", []
|
|
||||||
if not entries:
|
|
||||||
return (f"{central_label} is {_clean(text)}" if _clean(text) else "", [])
|
|
||||||
labels = [label for label, _descriptor in entries]
|
|
||||||
if labels == ["Woman A"]:
|
|
||||||
return _with_indefinite_article(entries[0][1]), labels
|
|
||||||
if labels == ["Man A"]:
|
|
||||||
return _with_indefinite_article(entries[0][1]), labels
|
|
||||||
if set(labels) == {"Woman A", "Man A"} and len(labels) == 2:
|
|
||||||
by_label = {label: descriptor for label, descriptor in entries}
|
|
||||||
return f"{_with_indefinite_article(by_label['Woman A'])} alongside {_with_indefinite_article(by_label['Man A'])}", labels
|
|
||||||
sentences = []
|
|
||||||
for label, descriptor in entries:
|
|
||||||
sentences.append(f"{label} is {descriptor}.")
|
|
||||||
if central_label in labels:
|
|
||||||
sentences.append(f"{central_label} is the central subject.")
|
|
||||||
return " ".join(sentences), labels
|
|
||||||
|
|
||||||
|
|
||||||
def _pov_labels_from_value(value: Any) -> list[str]:
|
def _pov_labels_from_value(value: Any) -> list[str]:
|
||||||
labels: list[str] = []
|
labels: list[str] = []
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
|
|||||||
+8
-64
@@ -11,6 +11,10 @@ from typing import Any, Callable
|
|||||||
try:
|
try:
|
||||||
from . import generate_prompt_batches as g
|
from . import generate_prompt_batches as g
|
||||||
from . import scene_camera_adapters
|
from . import scene_camera_adapters
|
||||||
|
from .hardcore_text_cleanup import (
|
||||||
|
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
|
||||||
|
sanitize_hardcore_environment_anchors as _sanitize_hardcore_environment_anchors,
|
||||||
|
)
|
||||||
from .prompt_hygiene import (
|
from .prompt_hygiene import (
|
||||||
sanitize_caption_text,
|
sanitize_caption_text,
|
||||||
sanitize_negative_text,
|
sanitize_negative_text,
|
||||||
@@ -19,6 +23,10 @@ try:
|
|||||||
except ImportError: # Allows local smoke tests with `python -c`.
|
except ImportError: # Allows local smoke tests with `python -c`.
|
||||||
import generate_prompt_batches as g
|
import generate_prompt_batches as g
|
||||||
import scene_camera_adapters
|
import scene_camera_adapters
|
||||||
|
from hardcore_text_cleanup import (
|
||||||
|
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
|
||||||
|
sanitize_hardcore_environment_anchors as _sanitize_hardcore_environment_anchors,
|
||||||
|
)
|
||||||
from prompt_hygiene import (
|
from prompt_hygiene import (
|
||||||
sanitize_caption_text,
|
sanitize_caption_text,
|
||||||
sanitize_negative_text,
|
sanitize_negative_text,
|
||||||
@@ -965,70 +973,6 @@ def _heuristic_cast_compatible(text: str, women_count: int, men_count: int) -> b
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
HARDCORE_ENVIRONMENT_ANCHOR_REPLACEMENTS = (
|
|
||||||
(r"\bon against a wall\b", "against a wall"),
|
|
||||||
(r"\bstacked bodies on the bed\b", "close body alignment"),
|
|
||||||
(r"\bstacked bodies with close body alignment\b", "close body alignment"),
|
|
||||||
(r"\boverhead tangled-body anal frame\b", "overhead rear-entry anal frame"),
|
|
||||||
(r"\btangled-body\b", "close-body"),
|
|
||||||
(r"\bthree bodies tangled on the bed\b", "three bodies tangled in close contact"),
|
|
||||||
(r"\ba triangle of bodies on the mattress\b", "a triangle of bodies in close contact"),
|
|
||||||
(r"\bbodies tangled on the sheets\b", "bodies tangled in close contact"),
|
|
||||||
(r"\bwet bodies tangled on sheets\b", "wet bodies tangled in close contact"),
|
|
||||||
(r"\bbody arched on rumpled sheets\b", "body arched with clear skin contact"),
|
|
||||||
(r"\bface-down ass-up on the mattress\b", "face-down ass-up position"),
|
|
||||||
(r"\bsitting on the edge of the bed\b", "sitting on a raised edge"),
|
|
||||||
(r"\blying at the bed edge with thighs open\b", "lying near a raised edge with thighs open"),
|
|
||||||
(r"\bedge[- ]of[- ]bed\b", "edge-supported"),
|
|
||||||
(r"\bbed[- ]edge\b", "raised edge"),
|
|
||||||
(r"\bedge of (?:the )?bed\b", "raised edge"),
|
|
||||||
(r"\bbed edge\b", "raised edge"),
|
|
||||||
(r"\bhands? braced on the bed\b", "hands braced beside the body"),
|
|
||||||
(r"\bone hand pressing into the mattress\b", "one hand braced beside the body"),
|
|
||||||
(r"\bone foot planted on the bed\b", "one foot planted for leverage"),
|
|
||||||
(r"\bfingers gripping sheets and skin\b", "fingers gripping skin"),
|
|
||||||
(r"\bfingers gripping sheets\b", "fingers gripping skin"),
|
|
||||||
(r"\bhands gripping sheets\b", "hands gripping skin"),
|
|
||||||
(r"\bone hand gripping the sheets\b", "one hand gripping skin"),
|
|
||||||
(r"\brumpled bed sheets\b", "wrinkled body-contact fabric"),
|
|
||||||
(r"\bwet sheets beneath the bodies\b", "visible wetness beneath the bodies"),
|
|
||||||
(r"\bsexual fluids on sheets\b", "sexual fluids visible on skin"),
|
|
||||||
(r"\bcum dripping onto sheets\b", "cum visible on skin"),
|
|
||||||
(r"\bfluid dripping onto sheets\b", "fluid visible on skin"),
|
|
||||||
(r"\bsquirting fluid on the sheets\b", "squirting fluid visible on skin"),
|
|
||||||
(r"\bsoft sheets\b", "soft fabric"),
|
|
||||||
(r"\bsilk sheets\b", "silk fabric"),
|
|
||||||
(r"\bsheets\b", "fabric"),
|
|
||||||
(r"\bmattress\b", "low support surface"),
|
|
||||||
(r"\ba low support surface\b", "a low body support"),
|
|
||||||
(r"\ba low mattress\b", "a low body support"),
|
|
||||||
(r"\ba wide couch\b", "a wide body support"),
|
|
||||||
(r"\bwide couch\b", "wide body support"),
|
|
||||||
(r"\bcouch\b", "body support"),
|
|
||||||
(r"\bsofa\b", "body support"),
|
|
||||||
(r"\bon the bed\b", "on a body support"),
|
|
||||||
(r"\bon a bed\b", "on a body support"),
|
|
||||||
(r"\bbedroom-floor\b", "floor-level"),
|
|
||||||
(r"\bbedroom floor\b", "floor-level"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _sanitize_hardcore_environment_anchors(value: Any) -> str:
|
|
||||||
text = str(value or "")
|
|
||||||
if not text:
|
|
||||||
return ""
|
|
||||||
for pattern, replacement in HARDCORE_ENVIRONMENT_ANCHOR_REPLACEMENTS:
|
|
||||||
text = re.sub(pattern, replacement, text, flags=re.IGNORECASE)
|
|
||||||
text = re.sub(r"\s+,", ",", text)
|
|
||||||
text = re.sub(r",\s*,", ",", text)
|
|
||||||
text = re.sub(r"\s{2,}", " ", text)
|
|
||||||
return text.strip()
|
|
||||||
|
|
||||||
|
|
||||||
def _sanitize_hardcore_axis_values(values: dict[str, str]) -> dict[str, str]:
|
|
||||||
return {key: _sanitize_hardcore_environment_anchors(value) for key, value in values.items()}
|
|
||||||
|
|
||||||
|
|
||||||
def _compatible_entry(entry: Any, women_count: int, men_count: int) -> bool:
|
def _compatible_entry(entry: Any, women_count: int, men_count: int) -> bool:
|
||||||
if not isinstance(entry, dict):
|
if not isinstance(entry, dict):
|
||||||
return _heuristic_cast_compatible(_entry_text(entry), women_count, men_count)
|
return _heuristic_cast_compatible(_entry_text(entry), women_count, men_count)
|
||||||
|
|||||||
Reference in New Issue
Block a user