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