diff --git a/README.md b/README.md index f349d37..27feedc 100644 --- a/README.md +++ b/README.md @@ -153,9 +153,10 @@ camera controls are preserved. Negative prompts stay separate. Important behavior: -- Age wording is preserved deliberately. Phrases like `21-year-old adult`, - `late 20s adult woman`, and `all participants 21+ and visibly adult` are kept - because they help avoid unwanted young-looking outputs. +- Concrete age wording is preserved deliberately. Phrases like + `25-year-old adult woman` and `late 60s adult man` are kept because they help + avoid unwanted young-looking outputs, while generic adult boilerplate is + omitted from the Krea2 rewrite. - Trigger words are removed by default because Krea2 prompting generally reads better as natural language. Enable `preserve_trigger` if you still need a LoRA trigger in the positive prompt. @@ -200,6 +201,9 @@ It outputs: - `shared_descriptor` - `metadata_json` +The positive Insta/OF outputs do not embed an `Avoid:` section; use the matching +negative prompt outputs when wiring the sampler. + `SxCP Insta/OF Options` outputs `options_json`, which can be connected to the pair node. Defaults are set so the softcore prompt is solo while the hardcore prompt can include partners. It also defaults the camera to handheld selfie @@ -214,7 +218,9 @@ Options: `use_counts`. The pair mode always keeps at least one adult woman as the primary creator so the shared descriptor remains valid. - `softcore_level`: `social_tease`, `lingerie_tease`, `implied_nude`, or - `explicit_tease`. + `explicit_tease`. Insta/OF softcore uses dedicated outfit pools so teaser + prompts do not randomly pull hardcore-adjacent harness, nude accessory, + microwear, or shirtless partner styling. - `hardcore_level`: `explicit` or `hardcore`. - `softcore_expression_intensity`: `0.0` is mild/controlled, `0.5` is sensual, `1.0` strongly favors more heated softcore faces. diff --git a/krea_formatter.py b/krea_formatter.py index 3fb6662..94f9f10 100644 --- a/krea_formatter.py +++ b/krea_formatter.py @@ -347,7 +347,7 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str) ) same_soft_cast = options.get("softcore_cast") == "same_as_hardcore" soft_cast_presence = ( - "The same listed adult cast is present together in this softcore version in a non-explicit teaser pose, with no sex act or genital contact" + "The same cast is present together in a non-explicit teaser pose, with no sex act or genital contact" if same_soft_cast else "The softcore version focuses on Woman A alone" ) @@ -361,7 +361,7 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str) partner_pose = "" soft_parts = [ - f"A visibly adult creator, {descriptor}", + descriptor, f"Shared cast descriptors: {soft_cast_descriptor_text}" if same_soft_cast and soft_cast_descriptor_text else "", f"Softcore primary creator descriptor: {soft_cast_descriptor_text}" if not same_soft_cast and soft_cast_descriptor_text else "", soft_cast_presence, @@ -377,9 +377,9 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str) soft_style if detail_level != "concise" else "", ] hard_parts = [ - f"The same visibly adult creator, {descriptor}, is the visually central woman in a consensual explicit adult {hard_level or 'hardcore'} scene", + f"The primary creator remains {descriptor}, visually central in a {hard_level or 'hardcore'} scene", f"{'Shared' if same_soft_cast else 'Hardcore'} cast descriptors: {cast_descriptor_text}" if cast_descriptor_text else "", - f"all participants are 21+ and visibly adult; the cast includes {hard_cast_text}" if hard_cast_text else "all participants are 21+ and visibly adult", + f"The cast includes {hard_cast_text}" if hard_cast_text else "", _clean(hard.get("role_graph")), f"The sexual action is {hard.get('item')}" if hard.get("item") else "", f"set in {hard_scene}" if hard_scene else "", diff --git a/prompt_builder.py b/prompt_builder.py index 126ebaf..335f308 100644 --- a/prompt_builder.py +++ b/prompt_builder.py @@ -2571,9 +2571,9 @@ def build_prompt_from_configs( INSTA_OF_SOFT_LEVELS = { "social_tease": "Instagram-style thirst-trap post, suggestive but non-explicit, polished social feed energy", - "lingerie_tease": "premium OF teaser set, lingerie-focused, sensual and intimate but without explicit sex", - "implied_nude": "implied nude creator set, strategically covered body, sensual but no visible sex act", - "explicit_tease": "explicit adult tease, nudity can be visible, but no penetration or partnered sex act", + "lingerie_tease": "premium OF teaser set, lingerie-focused, sensual and intimate", + "implied_nude": "implied nude creator set, strategically covered body and intimate teaser framing", + "explicit_tease": "stronger adult teaser set with bolder nude-adjacent styling and solo-tease framing", } INSTA_OF_HARDCORE_LEVELS = { @@ -2593,9 +2593,114 @@ INSTA_OF_NEGATIVE = ( ) INSTA_OF_SOFT_NEGATIVE = ( - INSTA_OF_NEGATIVE + ", explicit intercourse, penetration, oral sex, cumshot, genital contact, group sex" + INSTA_OF_NEGATIVE + + ", explicit intercourse, penetration, oral sex, cumshot, genital contact, group sex, " + "shirtless partner, bare-chested partner, partner nudity" ) +INSTA_OF_SOFTCORE_SUBCATEGORY_BY_LEVEL = { + "social_tease": "Casual clothes / Smart casual", + "lingerie_tease": "Provocative erotic clothes / Provocative lingerie", + "implied_nude": "Provocative erotic clothes / Provocative lingerie", + "explicit_tease": "Provocative erotic clothes / Sheer exposed", +} + +INSTA_OF_SOFTCORE_OUTFITS = { + "social_tease": [ + "cropped fitted tee, low-rise jeans, delicate jewelry, and polished feed-post styling", + "oversized off-shoulder sweater with fitted shorts and soft lounge socks", + "ribbed tank top, mini skirt, hoop earrings, and casual creator styling", + "silky camisole tucked into relaxed trousers with a subtle waist chain", + "sporty crop top, bike shorts, clean sneakers, and glossy social-feed styling", + "button-down shirt tied at the waist over a fitted bralette and denim shorts", + "body-hugging knit dress with bare shoulders and simple heels", + "relaxed hoodie half-zipped over a crop top with high-cut shorts", + ], + "lingerie_tease": [ + "black lace lingerie set with opaque cups, high-waisted briefs, garter straps, and sheer robe", + "satin bralette and matching high-waisted panties under an oversized shirt", + "lace bodysuit with opaque cups, soft stockings, and delicate garter details", + "silk slip dress with thin straps, thigh slit, and subtle lace trim", + "matching balconette bra and brief set under a loosely draped satin robe", + "velvet lingerie set with covered cups, garter belt, sheer stockings, and small gold accents", + "mesh robe over a covered lace teddy, styled as a premium non-explicit teaser", + "structured corset top with opaque panels, matching briefs, and sheer stockings", + ], + "implied_nude": [ + "oversized white shirt slipping off one shoulder, body mostly covered, bare legs, and soft sheets", + "towel wrap held across the chest and hips, implied nude but fully covered", + "satin sheet wrapped around the body with shoulders and legs visible but intimate areas covered", + "open robe held closed by hand, implied nude beneath without explicit exposure", + "bath towel and damp hair after a shower, covered chest and hips, intimate creator styling", + "soft blanket wrapped around the body, bare shoulders visible, sensual but covered", + ], + "explicit_tease": [ + "sheer robe over matching lingerie with intimate areas obscured by lace pattern and pose", + "wet-look bodysuit with opaque panels, high-cut legs, and glossy club-light styling", + "transparent mesh dress over covered lingerie, posed as an adult teaser without sex act", + "lace teddy with strategic opaque embroidery, garter straps, and sheer stockings", + "bare-shoulder robe opened around covered lingerie, explicit adult tease without partnered contact", + "strappy lingerie set with covered cups and high-waisted bottoms, styled as a stronger solo teaser", + ], +} + +INSTA_OF_SOFTCORE_POSES = { + "social_tease": [ + "taking a mirror selfie with one hip angled and relaxed social-feed confidence", + "leaning against a doorway with one hand holding the phone and a casual teasing smile", + "sitting on the edge of the bed for a polished outfit-check selfie", + "standing by the window with shoulders relaxed and body angled toward the phone", + "posing in a clean feed-post stance with one hand at the waist", + "stretching one arm above the head in a casual morning selfie pose", + ], + "lingerie_tease": [ + "taking a mirror lingerie selfie with one hip angled and the outfit clearly visible", + "kneeling on the bed in a covered lingerie teaser pose with hands kept on fabric", + "leaning against the vanity with the robe draped around covered lingerie", + "standing in a three-quarter lingerie outfit-check pose with legs softly crossed", + "sitting on the bed with stockings and garter details visible, non-explicit and posed", + "turning slightly over one shoulder to show the lingerie silhouette", + ], + "implied_nude": [ + "holding the towel or sheet securely in place while posing for an implied nude selfie", + "sitting under soft sheets with shoulders visible and the body strategically covered", + "standing by the bathroom mirror with a towel wrapped around the body", + "reclining under a satin sheet with intimate areas fully obscured", + "holding an open robe closed in a covered implied nude teaser pose", + "looking into the phone camera while wrapped in a blanket with bare shoulders visible", + ], + "explicit_tease": [ + "posing in a stronger adult teaser stance with covered lingerie and no partnered contact", + "kneeling on the bed with a sheer robe arranged around covered lingerie", + "standing close to the mirror with the outfit framed boldly but non-explicitly", + "leaning forward slightly with hands on the robe and intimate areas obscured", + "sitting at the vanity in a bolder covered lingerie pose with direct eye contact", + "arching subtly in a solo adult tease while the styling keeps explicit anatomy obscured", + ], +} + +INSTA_OF_SOFTCORE_PARTNER_WOMEN_OUTFITS = [ + "satin slip dress under an oversized shirt", + "soft cardigan over a camisole with relaxed trousers", + "fitted crop top with high-waisted jeans", + "silky robe over a covered bralette and lounge shorts", + "bodycon mini dress with simple heels", + "ribbed tank top with joggers and delicate jewelry", + "oversized tee with fitted shorts and lounge socks", + "button-down shirt with a fitted skirt", +] + +INSTA_OF_SOFTCORE_PARTNER_MEN_OUTFITS = [ + "fitted black tee with dark jeans", + "buttoned linen shirt with chinos", + "hoodie and joggers", + "open overshirt over a fitted tank with relaxed trousers", + "gym tee with track pants and a towel over one shoulder", + "casual knit shirt with tailored trousers", + "dark crewneck sweater with jeans", + "short-sleeve button-up shirt with relaxed shorts", +] + def build_insta_of_options_json( softcore_cast: str = "solo", @@ -2772,6 +2877,25 @@ SOFTCORE_CAST_POSES = [ ] +def _insta_of_softcore_category(level: str) -> tuple[str, str]: + subcategory = INSTA_OF_SOFTCORE_SUBCATEGORY_BY_LEVEL.get( + level, + INSTA_OF_SOFTCORE_SUBCATEGORY_BY_LEVEL["lingerie_tease"], + ) + category, _subcategory = subcategory.split(" / ", 1) + return category, subcategory + + +def _insta_of_softcore_outfit(rng: random.Random, level: str) -> str: + pool = INSTA_OF_SOFTCORE_OUTFITS.get(level, INSTA_OF_SOFTCORE_OUTFITS["lingerie_tease"]) + return g.choose(rng, pool) + + +def _insta_of_softcore_pose(rng: random.Random, level: str) -> str: + pool = INSTA_OF_SOFTCORE_POSES.get(level, INSTA_OF_SOFTCORE_POSES["lingerie_tease"]) + return g.choose(rng, pool) + + def _insta_of_partner_styling( seed_config: dict[str, int], seed: int, @@ -2784,10 +2908,10 @@ def _insta_of_partner_styling( outfits: list[str] = [] for index in range(max(0, women_count - 1)): label = chr(ord("B") + index) - outfits.append(f"Woman {label} wears {g.choose(content_rng, g.WOMEN_CLOTHES_MINIMAL)}") + outfits.append(f"Woman {label} wears {g.choose(content_rng, INSTA_OF_SOFTCORE_PARTNER_WOMEN_OUTFITS)}") for index in range(max(0, men_count)): label = chr(ord("A") + index) - outfits.append(f"Man {label} wears {g.choose(content_rng, g.MEN_CLOTHES_MINIMAL)}") + outfits.append(f"Man {label} wears {g.choose(content_rng, INSTA_OF_SOFTCORE_PARTNER_MEN_OUTFITS)}") return { "outfits": outfits, "pose": g.choose(pose_rng, SOFTCORE_CAST_POSES), @@ -2819,10 +2943,13 @@ def build_insta_of_pair( hard_women_count, hard_men_count = _insta_of_hardcore_counts(options) active_trigger = trigger.strip() or g.TRIGGER parsed_seed_config = _parse_seed_config(seed_config) + softcore_level_key = str(options["softcore_level"]) + soft_category, soft_subcategory = _insta_of_softcore_category(softcore_level_key) + soft_content_rng = _axis_rng(parsed_seed_config, "content", seed, row_number + 311) soft_row = build_prompt( - category="Provocative erotic clothes", - subcategory=RANDOM_SUBCATEGORY, + category=soft_category, + subcategory=soft_subcategory, row_number=row_number, start_index=start_index, seed=seed, @@ -2845,6 +2972,11 @@ def build_insta_of_pair( expression_intensity=options["softcore_expression_intensity"], character_profile=character_profile or "", ) + soft_row["item"] = _insta_of_softcore_outfit(soft_content_rng, softcore_level_key) + soft_row["pose"] = _insta_of_softcore_pose(soft_content_rng, softcore_level_key) + soft_row["item_label"] = "Insta/OF softcore outfit" + soft_row["custom_item"] = "insta_of_softcore_outfit" + soft_row["softcore_outfit_policy"] = "insta_of_safe_softcore" hard_row = build_prompt( category="Hardcore sexual poses", subcategory=RANDOM_SUBCATEGORY, @@ -2919,7 +3051,7 @@ def build_insta_of_pair( else f"non-explicit teaser setup with the same adult cast as the hardcore version: {_insta_of_cast_phrase(hard_women_count, hard_men_count)}" ) soft_cast_presence = ( - "Show the same listed adult cast together in the softcore version, present in the same location in a non-explicit teaser pose with no sex act or genital contact. " + "Keep the same cast together in the softcore version in a non-explicit teaser pose with no sex act or genital contact. " if options["softcore_cast"] == "same_as_hardcore" else "Keep the softcore version focused on Woman A alone. " ) @@ -2939,8 +3071,8 @@ def build_insta_of_pair( f"Outfit: {soft_row['item']}. Pose: {soft_row['pose']}. Setting: {soft_row['scene_text']}. " f"Facial expression: {soft_row['expression']}. Composition: {soft_row['composition']}. " f"{soft_camera_sentence}" - "Keep the softcore version adult-only, consensual, seductive, creator-shot, and non-explicit. " - f"{soft_row['positive_suffix']} Avoid: {INSTA_OF_SOFT_NEGATIVE}." + "Keep the softcore version seductive, creator-shot, and non-explicit. " + f"{soft_row['positive_suffix']}." ) hard_prompt = ( f"Insta/OF hardcore mode: {platform_style}. Shared primary creator descriptor: {descriptor}. " @@ -2950,8 +3082,7 @@ def build_insta_of_pair( f"Role graph: {hard_row['role_graph']} Sexual scene: {hard_row['item']}. " f"Setting: {hard_scene}. Facial expressions: {hard_row['expression']}. Composition: {hard_composition}. " f"{hard_camera_sentence}" - "All participants are consenting adults 21+. " - f"{hard_row['positive_suffix']} Avoid: {INSTA_OF_NEGATIVE}." + f"{hard_row['positive_suffix']}." ) if extra_positive.strip(): soft_prompt = f"{soft_prompt.rstrip()} {extra_positive.strip()}"