diff --git a/README.md b/README.md index 59babc1..70c4a57 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,11 @@ Options: - `softcore_level`: `social_tease`, `lingerie_tease`, `implied_nude`, or `explicit_tease`. - `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. +- `hardcore_expression_intensity`: `0.0` is controlled, `0.5` is balanced + hardcore, `1.0` strongly favors ahegao-style, drooling, fucked-out, climax, + and messy orgasm expressions. - `platform_style`: `hybrid`, `instagram`, or `onlyfans`. - `continuity`: `same_creator_same_room` keeps the scene/composition aligned; `same_creator_new_scene` keeps the same creator descriptor but lets the @@ -144,6 +149,11 @@ The node keeps the original generator controls: - `minimal_clothing_ratio`: `-1` disables mixing; `0.0` to `1.0` mixes minimal/full clothing. - `ethnicity`: `any`, `asian`, `white_asian`. - `poses`: `standard` or `evocative`. +- `expression_intensity`: `0.0` favors mild, neutral, controlled expressions; + `0.5` favors balanced category expressions; `1.0` strongly favors the most + intense expressions available in the selected category. This affects custom + JSON categories such as `Provocative erotic clothes` and `Hardcore sexual + poses`. - `standard_pose_ratio`: `-1` disables mixing; `0.0` to `1.0` mixes standard/evocative poses. - `backside_bias`: `0.0` to `1.0`, applies to evocative single-subject poses. - `figure`: `curvy`, `balanced`, `bombshell`. @@ -210,22 +220,36 @@ provided, the node uses a generic composer that selects subject appearance, scene, pose, expression, composition, and a random item from the selected subcategory. -Reusable location banks can be defined with top-level `scene_pools` in any -`categories/*.json` file. Categories, subcategories, and items can reference -them with `scene_pools`; referenced pools are merged with any local `scenes`. -This keeps location expansion scalable without duplicating the same bedroom, -selfie, mirror, creator, or group-sex locations across every subcategory. +Reusable banks can be defined with top-level `scene_pools`, +`expression_pools`, and `composition_pools` in any `categories/*.json` file. +Categories, subcategories, and items can reference them with `scene_pools`, +`expression_pools`, and `composition_pools`; referenced pools are merged with +any local `scenes`, `expressions`, or `compositions`. This keeps expansion +scalable without duplicating the same bedroom, selfie, mirror, creator, +expression, camera, or group-sex framing across every subcategory. -Set `"inherit_scenes": false` on a subcategory or item when it should use only -its own `scenes` and `scene_pools` instead of also inheriting parent category -locations. This is useful for narrow subcategories such as group scenes, vehicle -sets, outdoor-only sets, or any category where a generic parent room would be a -bad match. +Set `"inherit_scenes": false`, `"inherit_expressions": false`, or +`"inherit_compositions": false` on a subcategory or item when it should use only +its own pools instead of also inheriting parent category values. This is useful +for narrow subcategories such as group scenes, fetish sets, outdoor-only sets, +or any category where generic parent wording would be a bad match. Example: ```json { + "expression_pools": { + "creator_tease_faces": [ + "direct creator-shot eye contact", + "heavy-lidded bedroom gaze" + ] + }, + "composition_pools": { + "creator_selfie_frames": [ + "handheld selfie crop from face to hips", + "mirror selfie with phone visible and body framed clearly" + ] + }, "scene_pools": { "creator_selfie_rooms": [ { @@ -241,7 +265,11 @@ Example: { "name": "Selfie set", "inherit_scenes": false, + "inherit_expressions": false, + "inherit_compositions": false, "scene_pools": ["creator_selfie_rooms"], + "expression_pools": ["creator_tease_faces"], + "composition_pools": ["creator_selfie_frames"], "items": ["simple outfit prompt"] } ] diff --git a/__init__.py b/__init__.py index b291062..0b3147b 100644 --- a/__init__.py +++ b/__init__.py @@ -57,6 +57,7 @@ class SxCPPromptBuilder: "clothing": (["full", "minimal"], {"default": "full"}), "ethnicity": (["any", "asian", "white_asian"], {"default": "any"}), "poses": (["standard", "evocative"], {"default": "standard"}), + "expression_intensity": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), "backside_bias": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}), "figure": (["curvy", "balanced", "bombshell"], {"default": "curvy"}), "no_plus_women": ("BOOLEAN", {"default": False}), @@ -91,6 +92,7 @@ class SxCPPromptBuilder: clothing, ethnicity, poses, + expression_intensity, backside_bias, figure, no_plus_women, @@ -115,6 +117,7 @@ class SxCPPromptBuilder: clothing=clothing, ethnicity=ethnicity, poses=poses, + expression_intensity=expression_intensity, backside_bias=backside_bias, figure=figure, no_plus_women=no_plus_women, @@ -359,6 +362,8 @@ class SxCPInstaOFOptions: "hardcore_men_count": ("INT", {"default": 1, "min": 0, "max": 12, "step": 1}), "softcore_level": (["social_tease", "lingerie_tease", "implied_nude", "explicit_tease"], {"default": "lingerie_tease"}), "hardcore_level": (["explicit", "hardcore"], {"default": "hardcore"}), + "softcore_expression_intensity": ("FLOAT", {"default": 0.45, "min": 0.0, "max": 1.0, "step": 0.01}), + "hardcore_expression_intensity": ("FLOAT", {"default": 0.85, "min": 0.0, "max": 1.0, "step": 0.01}), "platform_style": (["hybrid", "instagram", "onlyfans"], {"default": "hybrid"}), "continuity": (["same_creator_same_room", "same_creator_new_scene"], {"default": "same_creator_same_room"}), "softcore_camera_mode": (camera_mode_choices(), {"default": "handheld_selfie"}), @@ -379,6 +384,8 @@ class SxCPInstaOFOptions: hardcore_men_count, softcore_level, hardcore_level, + softcore_expression_intensity, + hardcore_expression_intensity, platform_style, continuity, softcore_camera_mode, @@ -392,6 +399,8 @@ class SxCPInstaOFOptions: hardcore_men_count=hardcore_men_count, softcore_level=softcore_level, hardcore_level=hardcore_level, + softcore_expression_intensity=softcore_expression_intensity, + hardcore_expression_intensity=hardcore_expression_intensity, platform_style=platform_style, continuity=continuity, softcore_camera_mode=softcore_camera_mode, diff --git a/categories/default_categories.json b/categories/default_categories.json index b8c236a..adf50d9 100644 --- a/categories/default_categories.json +++ b/categories/default_categories.json @@ -9,12 +9,18 @@ "item_label": "Clothing", "style": "tasteful adult fashion-editorial coloured-pencil comic illustration with casual everyday styling", "positive_suffix": "Use crisp clean comic linework, soft fabric texture, detailed hatching, warm natural light, and tactile textured paper.", + "expression_pools": ["casual_observational_expressions"], + "composition_pools": ["casual_fashion_compositions"], "subcategories": [ { "name": "Streetwear", "slug": "streetwear", "weight": 1.0, + "inherit_expressions": false, + "inherit_compositions": false, "scene_pools": ["casual_urban_scenes"], + "expression_pools": ["streetwear_attitude_expressions"], + "composition_pools": ["streetwear_phone_compositions"], "items": ["streetwear outfit generator"], "item_templates": [ "{outerwear} layered over {top}, with {bottom}, {footwear}, and {accessory}", @@ -169,7 +175,11 @@ "name": "Summer casual", "slug": "summer_casual", "weight": 1.0, + "inherit_expressions": false, + "inherit_compositions": false, "scene_pools": ["casual_summer_scenes"], + "expression_pools": ["summer_candid_expressions"], + "composition_pools": ["summer_lifestyle_compositions"], "items": ["summer casual outfit generator"], "item_templates": [ "{dress} with {footwear}, {accessory}, and {fabric_detail}", @@ -342,7 +352,11 @@ "name": "Cozy lounge", "slug": "cozy_lounge", "weight": 1.0, + "inherit_expressions": false, + "inherit_compositions": false, "scene_pools": ["casual_lounge_scenes"], + "expression_pools": ["cozy_lounge_expressions"], + "composition_pools": ["cozy_home_compositions"], "items": ["cozy lounge outfit generator"], "item_templates": [ "{layer} over {top}, with {bottom}, {footwear}, and {texture_detail}", diff --git a/categories/erotic_clothes.json b/categories/erotic_clothes.json index 829a62d..44bba9a 100644 --- a/categories/erotic_clothes.json +++ b/categories/erotic_clothes.json @@ -11,6 +11,8 @@ "positive_suffix": "Use crisp clean comic linework, detailed hatching, soft skin shading, tactile fabric texture, warm intimate lighting, and textured paper.", "negative_prompt": "minors, childlike appearance, schoolgirl, childlike costume, non-consensual, coercion, violence, injury, watermark", "scene_pools": ["softcore_creator_scenes", "mirror_scenes"], + "expression_pools": ["softcore_creator_expressions", "erotic_inviting_expressions"], + "composition_pools": ["softcore_creator_compositions", "boudoir_body_compositions"], "expressions": [ "heavy-lidded seductive gaze", "direct erotic stare", @@ -552,7 +554,11 @@ "name": "Fetish inspired", "slug": "fetish_inspired", "weight": 1.0, + "inherit_expressions": false, + "inherit_compositions": false, "scene_pools": ["fetish_studio_scenes"], + "expression_pools": ["fetish_control_expressions"], + "composition_pools": ["fetish_studio_compositions"], "item_templates": [ "{color} {material} {latex_piece} with {zipper_detail}, {boot_style}, and {glove_style}", "{color} {harness_style} over {breast_exposure} with {bottom_detail}, {hardware}, and {shoe_style}", diff --git a/categories/expression_composition_pools.json b/categories/expression_composition_pools.json new file mode 100644 index 0000000..6c6a7dc --- /dev/null +++ b/categories/expression_composition_pools.json @@ -0,0 +1,435 @@ +{ + "version": 1, + "expression_pools": { + "casual_observational_expressions": [ + "neutral relaxed face with direct eye contact", + "quiet focused gaze", + "slightly aloof editorial stare", + "calm unsmiling expression", + "reserved half-smile", + "thoughtful side glance", + "candid mid-walk look", + "subtle skeptical eyebrow raise", + "composed fashion-model stare", + "soft tired morning expression", + "mildly distracted street-photo look", + "relaxed closed-mouth smile" + ], + "streetwear_attitude_expressions": [ + "cool unsmiling streetwear stare", + "confident sideways glance", + "detached outfit-check expression", + "small knowing smirk", + "phone-camera look with raised brow", + "focused city-walk expression", + "low-key bored fashion face", + "direct lens stare under sunglasses", + "casual closed-mouth grin", + "serious model-off-duty expression" + ], + "summer_candid_expressions": [ + "squinting softly in bright sun", + "windblown candid smile", + "relaxed vacation gaze", + "half-lidded warm-weather expression", + "calm look toward the horizon", + "soft open-air market glance", + "subtle smile while turning away", + "sunlit thoughtful expression", + "quiet contented look", + "fresh-faced candid expression" + ], + "cozy_lounge_expressions": [ + "sleepy relaxed expression", + "quiet morning stare", + "soft unfocused gaze", + "warm but tired half-smile", + "barely awake expression", + "peaceful closed-mouth smile", + "intimate home-camera gaze", + "relaxed thoughtful look", + "softly serious window-light expression", + "calm private-moment face" + ], + "softcore_creator_expressions": [ + "direct creator-shot eye contact", + "heavy-lidded bedroom gaze", + "teasing open-mouthed breath", + "parted lips with flushed cheeks", + "knowing subscriber-camera stare", + "playful tongue touching the upper lip", + "slow inviting half-smile", + "soft aroused gaze", + "confident mirror-selfie stare", + "breathy expression with relaxed jaw", + "shameless teasing grin", + "dreamy post-tease expression", + "subtle lip bite", + "intense close-camera stare", + "flushed skin and glossy eyes", + "smirking phone-camera expression" + ], + "erotic_inviting_expressions": [ + "wet parted lips and heavy-lidded eyes", + "bold lustful eye contact", + "open-mouthed sensual gasp", + "slow predatory smile", + "flushed cheeks with a shameless stare", + "soft moaning expression without a sex act", + "inviting gaze with relaxed mouth", + "bedroom eyes and a teasing smirk", + "dreamy aroused expression", + "confident erotic glare", + "mouth slightly open with glossy lips", + "intimate close-lens stare", + "playful tongue-out tease", + "commanding sensual expression", + "heated side glance", + "after-dark seduction face" + ], + "fetish_control_expressions": [ + "strict dominant stare", + "cold confident gaze", + "smirking control expression", + "commanding eye contact", + "calm predatory smile", + "unblinking fetish-studio stare", + "chin-raised confident expression", + "sharp side-eye under dramatic light", + "teasingly stern face", + "intense latex-gloss stare", + "detached dominant expression", + "slow controlled smile" + ], + "hardcore_orgasm_expressions": [ + "controlled intense eye contact during sex", + "focused adult pleasure face", + "bitten-lip near-orgasm expression", + "steady flushed gaze with held breath", + "adult ahegao-style crossed eyes with tongue out", + "drooling open-mouthed orgasm face", + "eyes rolled upward in climax", + "tongue out with flushed cheeks", + "overwhelmed orgasmic grimace", + "wide open mouth mid-moan", + "glassy unfocused climax stare", + "sweaty breathless face with parted lips", + "jaw slack from intense pleasure", + "eyes squeezed shut during orgasm", + "wild aroused expression with messy hair", + "near-climax trembling lips", + "desperate open-mouth pleasure face", + "flushed face with saliva at the lips", + "shameless fucked-out expression", + "dazed post-orgasm stare", + "heavy-lidded exhausted pleasure face", + "raw orgasmic face with wet lips" + ], + "hardcore_messy_expressions": [ + "controlled sweaty eye contact", + "focused heavy-breathing expression", + "bitten-lip messy pleasure face", + "steady post-sex stare", + "messy drooling pleasure expression", + "cum-smeared lips with dazed eye contact", + "saliva-shiny tongue-out expression", + "sweaty flushed face and open mouth", + "mascara-smudged adult climax face", + "fucked-out stare with slack jaw", + "wild tongue-out expression", + "wet lips and unfocused eyes", + "breathless panting expression", + "overheated flushed cheeks", + "messy post-climax grin", + "shaking near-orgasm expression", + "glossy eyes and parted lips", + "drool at the corner of the mouth", + "intense shameless eye contact", + "spent satisfied expression" + ], + "hardcore_penetration_expressions": [ + "controlled eye contact during penetration", + "focused adult pleasure face during thrusting", + "bitten-lip concentration under penetration", + "braced expression with held breath", + "open-mouthed moan during penetration", + "eyes rolled back from deep penetration", + "bitten-lip expression under hard thrusting", + "desperate eye contact during penetration", + "jaw slack from deep thrusts", + "flushed face with a strained moan", + "adult ahegao-style penetration face", + "breathless grimace from intense pressure", + "fucked-out gaze while hips are pinned", + "tongue out during hard penetration", + "sweaty orgasm-building expression", + "eyes squeezed shut as penetration deepens", + "wild pleasure face during thrusting", + "near-climax pleading stare" + ], + "hardcore_oral_expressions": [ + "focused eye contact during oral sex", + "controlled oral-sex expression", + "steady gaze while mouth is close to genitals", + "bitten-lip anticipation before oral contact", + "wet open-mouth oral expression", + "drooling tongue-out oral face", + "glossy eyes while mouth is occupied", + "messy lips during oral sex", + "gagging-but-consensual adult oral expression", + "hungry eye contact during oral sex", + "saliva strings at the lips", + "sex-drunk dazed expression", + "tongue extended toward genitals", + "cum-smeared mouth and heavy-lidded eyes", + "breathless oral-sex face", + "wide open mouth ready for oral contact", + "wet lips pressed around a partner", + "shameless oral-service stare" + ], + "hardcore_anal_dp_expressions": [ + "controlled braced expression during anal sex", + "focused eye contact while hips are held open", + "bitten-lip pressure expression", + "steady concentrated pleasure face", + "overwhelmed face during anal penetration", + "eyes rolled back from double penetration", + "strained open-mouthed pleasure expression", + "adult ahegao-style double-penetration face", + "fucked-out expression between two partners", + "breathless face under intense pressure", + "tongue out with flushed cheeks", + "near-orgasm grimace during anal sex", + "wide-eyed pleasure shock", + "sweaty clenched-jaw moan", + "dazed expression while hips are held open", + "desperate heavy-lidded stare", + "open mouth and trembling lips", + "messy post-anal climax face" + ], + "hardcore_group_expressions": [ + "controlled aroused expressions across the group", + "focused eye contact between several partners", + "steady concentrated pleasure faces", + "bitten-lip group-sex expressions", + "multiple adults with open-mouthed orgasm faces", + "mixed dazed and hungry faces across the group", + "several tongue-out ahegao-style adult expressions", + "flushed sweaty faces around the pile", + "one partner drooling while others moan", + "overwhelmed group-sex expressions", + "fucked-out faces across several bodies", + "heavy-lidded eye contact between partners", + "wild orgasmic faces in the group", + "messy saliva-shiny mouths across the scene", + "shameless aroused grins and moans", + "near-climax expressions on multiple adults", + "spent post-orgy faces", + "open mouths and glossy eyes across the group", + "dazed pleasure expressions from every visible adult", + "heated direct eye contact between participants" + ], + "hardcore_climax_expressions": [ + "controlled post-climax stare", + "focused breathless aftermath expression", + "bitten-lip satisfied expression", + "steady flushed face after orgasm", + "eyes rolled back during climax", + "mouth open as orgasm hits", + "cum-smeared dazed expression", + "tongue out with visible post-climax mess", + "breathless post-orgasm stare", + "spent fucked-out face", + "shaking orgasm expression", + "wet lips and flushed cheeks after climax", + "heavy-lidded satisfied stare", + "dazed expression under phone flash", + "open-mouthed post-climax panting", + "messy satisfied grin", + "adult ahegao-style climax face", + "glassy eyes and slack jaw after release" + ] + }, + "composition_pools": { + "casual_fashion_compositions": [ + "vertical outfit-check composition with the full silhouette readable", + "three-quarter fashion crop with face, outfit, and shoes visible", + "street-style editorial frame with environmental context", + "candid phone-photo composition with natural body spacing", + "mirror outfit-check view with the room visible", + "full-body standing frame with no cropped shoes", + "mid-distance lifestyle frame with relaxed negative space", + "clean fashion catalog composition", + "slightly low phone angle emphasizing outfit shape", + "waist-up crop focused on layering and accessories", + "over-the-shoulder fashion pose with background context", + "side-profile frame showing the outfit silhouette" + ], + "streetwear_phone_compositions": [ + "low-angle streetwear phone shot with sneakers near the foreground", + "mirror elevator outfit-check composition", + "crosswalk mid-step vertical frame", + "wall-leaning full-body street portrait", + "storefront reflection composition", + "wide sidewalk frame with city lines behind the body", + "seated curb-level streetwear pose", + "phone-snapshot crop from head to shoes", + "three-quarter frame emphasizing jacket layers", + "rear three-quarter streetwear turn" + ], + "summer_lifestyle_compositions": [ + "sunlit full-body summer lifestyle frame", + "promenade walking shot with fabric movement", + "market-stall candid composition", + "seated low-wall frame with legs and sandals visible", + "balcony outfit-check with plants and skyline", + "bright high-angle phone photo", + "soft side-profile summer silhouette", + "beach-cafe seated composition", + "wide resort-day frame with clear warm light", + "close crop on face, neckline, and fabric texture" + ], + "cozy_home_compositions": [ + "soft window-light home portrait", + "sofa-corner seated vertical frame", + "bedroom-floor lounge composition", + "mirror view showing cozy layers and room context", + "kneeling rug-level composition", + "waist-up intimate home-camera crop", + "full-body barefoot frame near curtains", + "reading-nook composition with relaxed negative space", + "overhead sofa composition", + "side-profile lounge silhouette" + ], + "softcore_creator_compositions": [ + "handheld selfie crop from face to hips", + "mirror selfie with phone visible and body framed clearly", + "phone-tripod vertical creator frame", + "bed selfie with sheets around the body", + "bathroom mirror crop with phone and torso visible", + "close subscriber-view crop from thighs to face", + "ring-light creator setup with full body visible", + "over-the-shoulder mirror view", + "kneeling bed-frame creator composition", + "side-profile body curve composition", + "low-angle phone shot emphasizing legs and hips", + "full-body lingerie outfit-check frame", + "tight crop on face, chest, hands, and fabric", + "vertical story-frame creator shot" + ], + "boudoir_body_compositions": [ + "reclining boudoir full-body composition", + "arched-back side-profile lingerie frame", + "mirror-view bedroom composition", + "kneeling bed composition with face and torso visible", + "standing center-frame boudoir poster crop", + "seated legs-forward lingerie frame", + "close crop on lace, skin, and facial expression", + "over-the-shoulder rear-view boudoir frame", + "bed-edge full-body composition", + "soft overhead bed composition", + "low-angle glamour frame emphasizing hips and thighs", + "three-quarter body editorial lingerie pose" + ], + "fetish_studio_compositions": [ + "low-angle fetish-studio dominance frame", + "centered full-body latex poster composition", + "mirror-wall fetish composition with hard rim light", + "kneeling front-facing studio frame", + "standing wide-leg dominance composition", + "close crop on harness, expression, and hands", + "rear three-quarter fetish silhouette", + "glossy floor reflection composition", + "symmetrical private-studio frame", + "side-profile body-line composition", + "dramatic overhead studio crop", + "tight vertical frame with props and body centered" + ], + "hardcore_explicit_compositions": [ + {"text": "full-body explicit sex frame with all adult bodies visible", "min_people": 2}, + {"text": "bed-level camera angle focused on genital contact and faces", "min_people": 2, "max_people": 3}, + {"text": "side-profile view showing penetration alignment clearly", "min_people": 2, "max_people": 3}, + {"text": "mirror-reflected explicit sex composition with direct and reflected views", "min_people": 2, "max_people": 3}, + {"text": "overhead view of intertwined adult bodies", "min_people": 2}, + {"text": "low-angle explicit body-contact frame", "min_people": 2}, + {"text": "close crop on hips, thighs, hands, genitals, and faces", "min_people": 2, "max_people": 3}, + {"text": "wide full-body explicit composition with readable anatomy", "min_people": 2}, + {"text": "creator-shot vertical frame with phone-camera intimacy", "min_people": 2}, + {"text": "front-facing explicit frame with body contact centered", "min_people": 2}, + {"text": "floor-level sex composition with sheets and bodies filling the frame", "min_people": 2}, + {"text": "tight subscriber-view crop with face and explicit contact visible", "min_people": 2, "max_people": 3} + ], + "penetration_compositions": [ + {"text": "low-angle penetration view with hips near the camera", "min_people": 2, "max_people": 3}, + {"text": "side-profile penetration frame with genital contact readable", "min_people": 2, "max_people": 3}, + {"text": "bed-edge explicit penetration composition", "min_people": 2, "max_people": 3}, + {"text": "overhead penetration frame with legs and hips visible", "min_people": 2, "max_people": 3}, + {"text": "mirror-reflected penetration view", "min_people": 2, "max_people": 3}, + {"text": "close crop on penetration, hands, and orgasmic face", "min_people": 2, "max_people": 3}, + {"text": "wide full-body penetration frame", "min_people": 2, "max_people": 3}, + {"text": "rear three-quarter penetration composition", "min_people": 2, "max_people": 3}, + {"text": "kneeling penetration frame with bodies stacked vertically", "min_people": 2, "max_people": 3}, + {"text": "front-facing explicit thrusting composition", "min_people": 2, "max_people": 3} + ], + "oral_compositions": [ + {"text": "mouth-level oral-sex close-up with eyes and hands visible", "min_people": 2, "max_people": 3}, + {"text": "kneeling oral composition with genitals and face centered", "min_people": 2, "max_people": 3}, + {"text": "front-view oral-sex frame from bed height", "min_people": 2, "max_people": 3}, + {"text": "low phone-camera angle focused on mouth contact", "min_people": 2, "max_people": 3}, + {"text": "mirror-reflected oral scene with face and body visible", "min_people": 2, "max_people": 3}, + {"text": "overhead oral-sex frame with thighs and mouth contact readable", "min_people": 2, "max_people": 3}, + {"text": "side-profile oral composition with saliva and expression visible", "min_people": 2, "max_people": 3}, + {"text": "tight crop on mouth, hands, genitals, and eyes", "min_people": 2, "max_people": 3}, + {"text": "wide full-body oral-sex composition", "min_people": 2, "max_people": 3}, + {"text": "bedside kneeling oral frame with phone-shot intimacy", "min_people": 2, "max_people": 3} + ], + "anal_dp_compositions": [ + {"text": "rear-view anal composition with hips and face visible", "min_people": 2, "max_people": 3}, + {"text": "side-profile anal penetration frame", "min_people": 2, "max_people": 3}, + {"text": "front-and-back double-penetration composition", "min_people": 3}, + {"text": "mirror-reflected double-penetration view", "min_people": 3}, + {"text": "low-angle bent-over explicit frame", "min_people": 2, "max_people": 3}, + {"text": "bed-edge rear-entry composition", "min_people": 2, "max_people": 3}, + {"text": "close crop on hips, hands, and overwhelmed face", "min_people": 2, "max_people": 3}, + {"text": "wide frame with all penetration points visible", "min_people": 3}, + {"text": "kneeling double-contact composition", "min_people": 3}, + {"text": "overhead tangled-body anal frame", "min_people": 2, "max_people": 3} + ], + "threesome_compositions": [ + {"text": "centered threesome composition with all three adult bodies visible", "min_people": 3, "max_people": 3}, + {"text": "front-and-back threesome frame", "min_people": 3, "max_people": 3}, + {"text": "overhead three-body composition", "min_people": 3, "max_people": 3}, + {"text": "wide bed threesome frame with faces and genitals visible", "min_people": 3, "max_people": 3}, + {"text": "mirror-wall threesome composition", "min_people": 3, "max_people": 3}, + {"text": "low mattress three-person frame", "min_people": 3, "max_people": 3}, + {"text": "side-profile threesome alignment view", "min_people": 3, "max_people": 3}, + {"text": "tight center-body crop with both partners visible", "min_people": 3, "max_people": 3}, + {"text": "floor-level three-body explicit frame", "min_people": 3, "max_people": 3}, + {"text": "standing and kneeling threesome composition", "min_people": 3, "max_people": 3} + ], + "group_sex_compositions": [ + {"text": "wide orgy composition with every visible adult body readable", "min_people": 4}, + {"text": "overhead group-sex frame with bodies arranged in layers", "min_people": 4}, + {"text": "floor-level group pile with multiple acts visible", "min_people": 4}, + {"text": "wide mirror-wall group-sex composition", "min_people": 4}, + {"text": "multi-couch group scene with all participants in frame", "min_people": 4}, + {"text": "centered group pile with penetration and oral contact visible", "min_people": 4}, + {"text": "wide private-suite orgy frame with readable body positions", "min_people": 4}, + {"text": "high-angle group-sex composition showing the whole arrangement", "min_people": 4}, + {"text": "long vertical creator-shot group frame", "min_people": 4}, + {"text": "full-room group-sex composition with clear adult spacing", "min_people": 4} + ], + "climax_compositions": [ + {"text": "tight post-climax crop on face, body, hands, and visible fluids", "min_people": 1}, + {"text": "phone-flash close-up of orgasm aftermath", "min_people": 1}, + {"text": "bed-level climax frame with thighs, face, and fluid detail visible", "min_people": 1}, + {"text": "mirror-reflected post-climax composition", "min_people": 1}, + {"text": "front-facing cumshot composition with body and expression centered", "min_people": 1}, + {"text": "overhead post-orgasm frame on rumpled sheets", "min_people": 1}, + {"text": "kneeling climax frame with open mouth and body visible", "min_people": 1}, + {"text": "wide aftermath composition with all adult bodies visible", "min_people": 2}, + {"text": "tight subscriber-view crop of climax expression and fluid detail", "min_people": 1}, + {"text": "side-profile post-climax body-line composition", "min_people": 1} + ] + } +} diff --git a/categories/sexual_poses.json b/categories/sexual_poses.json index 928d8ef..2c5510b 100644 --- a/categories/sexual_poses.json +++ b/categories/sexual_poses.json @@ -11,39 +11,43 @@ "positive_suffix": "Use clear adult anatomy, visible sexual contact, intense body language, crisp comic linework, detailed hatching, warm erotic lighting, and tactile textured paper.", "negative_prompt": "minors, childlike appearance, teen, schoolgirl, incest, bestiality, non-consensual, coercion, rape, violence, injury, blood, gore, watermark", "scene_pools": ["hardcore_private_scenes"], + "expression_pools": ["hardcore_orgasm_expressions", "hardcore_messy_expressions"], + "composition_pools": ["hardcore_explicit_compositions"], "prompt_template": "{subject_phrase}, all 21+ consenting adults: {style}. Cast: {cast_summary}. Role graph: {role_graph} Sexual pose: {item}. Setting: {scene}. Composition: {composition}. Facial expressions: {expression}. Make the scene explicit, hardcore, and anatomically clear, with visible genital contact and adult bodies only. {positive_suffix} Avoid: {negative_prompt}.", "caption_template": "{trigger}, {scene_kind}, {cast_summary}, {role_graph}, {item}, {scene}, {composition}, explicit consensual adult hardcore sex illustration", "expressions": [ - "open-mouthed orgasmic expression", - "heavy-lidded lustful gaze", - "flushed faces and parted lips", - "intense eye contact during sex", - "breathless pleasure expression", - "commanding erotic stare", - "shameless aroused grin", - "eyes closed in climax", - "teasing dominant smile", - "desperate hungry gaze", - "sweaty post-orgasmic glow", - "bitten-lip pleasure expression", - "wild aroused expression", - "soft moaning expression", - "focused intimate eye contact", - "overwhelmed climax expression" + "adult ahegao-style orgasm face", + "eyes rolled back with tongue out", + "drooling open-mouthed moan", + "fucked-out dazed stare", + "sweaty flushed orgasm expression", + "jaw slack from intense pleasure", + "messy saliva-shiny lips", + "desperate near-climax gaze", + "wild tongue-out arousal", + "breathless post-orgasm face", + "cum-smeared lips and glossy eyes", + "open mouth and trembling lips", + "heavy-lidded exhausted pleasure stare", + "raw overwhelmed climax expression", + "shameless messy aroused grin", + "eyes squeezed shut during orgasm", + "dazed sex-drunk expression", + "heated direct eye contact during sex" ], "compositions": [ - "full-body bed scene with all bodies visible", - "low-angle explicit penetration view", - "overhead view of intertwined bodies", - "side-profile view showing genital contact", - "mirror-view sex composition", - "close crop on hips, thighs, and faces", - "wide orgy composition with all participants visible", - "centered threesome composition", - "kneeling and standing composition", - "reclining full-body composition", + "full-body explicit sex frame with all adult bodies visible", + "low-angle view focused on genital contact", + "overhead view of intertwined adult bodies", + "side-profile view showing penetration alignment", + "mirror-view explicit sex composition", + "close crop on hips, thighs, hands, genitals, and faces", + "wide group-sex composition with all participants visible", + "centered threesome composition with all three bodies readable", + "kneeling and standing explicit body-contact frame", + "reclining full-body sex composition", "floor-level explicit sex composition", - "front-facing explicit pin-up composition" + "front-facing hardcore creator-shot composition" ], "scenes": [ { @@ -84,8 +88,12 @@ "name": "Penetrative sex", "slug": "penetrative_sex", "min_people": 2, + "inherit_expressions": false, + "inherit_compositions": false, "weight": 1.0, "scene_pools": ["hardcore_penetrative_scenes", "hardcore_bed_scenes", "hardcore_mirror_scenes"], + "expression_pools": ["hardcore_penetration_expressions"], + "composition_pools": ["penetration_compositions"], "item_templates": [ "{penetration_act} in {position}, with {body_contact}, {intensity}, and {visibility}", "{position} while {penetration_act}, {hand_detail}, {mouth_detail}, and {visibility}", @@ -249,8 +257,12 @@ "name": "Oral sex", "slug": "oral_sex", "min_people": 2, + "inherit_expressions": false, + "inherit_compositions": false, "weight": 1.0, "scene_pools": ["hardcore_oral_scenes", "hardcore_bed_scenes", "hardcore_mirror_scenes"], + "expression_pools": ["hardcore_oral_expressions"], + "composition_pools": ["oral_compositions"], "item_templates": [ "{oral_act} in {position}, with {hand_detail}, {expression_detail}, and {visibility}", "{position} featuring {oral_act}, {body_contact}, {saliva_detail}, and {climax_hint}", @@ -382,8 +394,12 @@ "name": "Anal and double penetration", "slug": "anal_double_penetration", "min_people": 2, + "inherit_expressions": false, + "inherit_compositions": false, "weight": 1.0, "scene_pools": ["hardcore_anal_scenes", "hardcore_bed_scenes", "hardcore_mirror_scenes"], + "expression_pools": ["hardcore_anal_dp_expressions"], + "composition_pools": ["anal_dp_compositions"], "item_templates": [ "{anal_act} in {position}, with {leg_detail}, {hand_detail}, and {visibility}", "{double_act} with {body_arrangement}, {intensity}, {mouth_detail}, and {visibility}", @@ -587,8 +603,12 @@ "name": "Threesomes", "slug": "threesomes", "min_people": 3, + "inherit_expressions": false, + "inherit_compositions": false, "weight": 1.0, "scene_pools": ["hardcore_threesome_scenes", "hardcore_group_scenes", "hardcore_mirror_scenes"], + "expression_pools": ["hardcore_group_expressions"], + "composition_pools": ["threesome_compositions"], "item_templates": [ "{threesome_act} with {body_arrangement}, {oral_detail}, {penetration_detail}, and {visibility}", "{body_arrangement} while {threesome_act}, with {hand_detail}, {mouth_detail}, and {climax_hint}", @@ -766,8 +786,12 @@ "slug": "group_sex_orgy", "min_people": 4, "inherit_scenes": false, + "inherit_expressions": false, + "inherit_compositions": false, "weight": 1.0, "scene_pools": ["hardcore_group_scenes"], + "expression_pools": ["hardcore_group_expressions"], + "composition_pools": ["group_sex_compositions"], "item_templates": [ "{group_act} with {arrangement}, {contact_detail}, {fluid_detail}, and {visibility}", "{arrangement} featuring {group_act}, {oral_detail}, {penetration_detail}, and {intensity}", @@ -934,8 +958,12 @@ "name": "Cumshot and climax", "slug": "cumshot_climax", "min_people": 1, + "inherit_expressions": false, + "inherit_compositions": false, "weight": 1.0, "scene_pools": ["hardcore_climax_scenes", "hardcore_bed_scenes", "hardcore_mirror_scenes"], + "expression_pools": ["hardcore_climax_expressions"], + "composition_pools": ["climax_compositions"], "item_templates": [ "{climax_act} with {fluid_location}, {body_position}, {expression_detail}, and {visibility}", "{body_position} during {climax_act}, with {hand_detail}, {fluid_location}, and {fluid_detail}", diff --git a/prompt_builder.py b/prompt_builder.py index 80c11c9..c96bad4 100644 --- a/prompt_builder.py +++ b/prompt_builder.py @@ -562,15 +562,15 @@ def load_category_library() -> list[dict[str, Any]]: return categories -def load_scene_pool_library() -> dict[str, list[Any]]: +def _load_named_pool_library(key: str) -> dict[str, list[Any]]: pools: dict[str, list[Any]] = {} for path in _json_files(): data = _read_json(path) - raw_pools = data.get("scene_pools", {}) + raw_pools = data.get(key, {}) if not raw_pools: continue if not isinstance(raw_pools, dict): - raise ValueError(f"scene_pools in {path} must be an object") + raise ValueError(f"{key} in {path} must be an object") for name, entries in raw_pools.items(): pool_name = str(name).strip() if not pool_name: @@ -580,6 +580,18 @@ def load_scene_pool_library() -> dict[str, list[Any]]: return pools +def load_scene_pool_library() -> dict[str, list[Any]]: + return _load_named_pool_library("scene_pools") + + +def load_expression_pool_library() -> dict[str, list[Any]]: + return _load_named_pool_library("expression_pools") + + +def load_composition_pool_library() -> dict[str, list[Any]]: + return _load_named_pool_library("composition_pools") + + def _extension_targets() -> dict[str, tuple[list[Any], bool]]: return { "women_clothes": (g.WOMEN_CLOTHES, False), @@ -654,6 +666,14 @@ def _ratio_or_none(value: float) -> float | None: return max(0.0, min(1.0, ratio)) +def _clamped_float(value: Any, default: float = 0.5, min_value: float = 0.0, max_value: float = 1.0) -> float: + try: + number = float(value) + except (TypeError, ValueError): + return default + return max(min_value, min(max_value, number)) + + def build_seed_config_json( category_seed: int = -1, subcategory_seed: int = -1, @@ -1384,6 +1404,180 @@ def _scene_pool(category: dict[str, Any], subcategory: dict[str, Any], item: Any return scene_entries or fallback +def _sources_with_inheritance( + category: dict[str, Any], + subcategory: dict[str, Any], + item: Any, + inherit_key: str, +) -> tuple[Any, ...]: + item_source = item if isinstance(item, dict) else None + if item_source is not None and _is_false(item_source.get(inherit_key)): + return (item_source,) + if _is_false(subcategory.get(inherit_key)): + return (subcategory, item_source) + return (category, subcategory, item_source) + + +def _configured_pool( + category: dict[str, Any], + subcategory: dict[str, Any], + item: Any, + direct_key: str, + pool_key: str, + pool_library: dict[str, list[Any]], + inherit_key: str, +) -> list[Any]: + entries: list[Any] = [] + singular_pool_key = pool_key[:-1] if pool_key.endswith("s") else pool_key + for source in _sources_with_inheritance(category, subcategory, item, inherit_key): + if not isinstance(source, dict): + continue + if direct_key in source: + _unique_extend(entries, _list_from(source[direct_key])) + refs = _list_from(source.get(singular_pool_key)) + _list_from(source.get(pool_key)) + for ref in refs: + ref_name = str(ref).strip() + if ref_name not in pool_library: + raise ValueError(f"Unknown {singular_pool_key} '{ref_name}'") + _unique_extend(entries, pool_library[ref_name]) + return entries + + +def _expression_pool(category: dict[str, Any], subcategory: dict[str, Any], item: Any) -> list[Any]: + return _configured_pool( + category, + subcategory, + item, + "expressions", + "expression_pools", + load_expression_pool_library(), + "inherit_expressions", + ) or g.EXPRESSIONS + + +def _expression_intensity_hint(entry: Any) -> float: + if isinstance(entry, dict): + for key in ("expression_intensity", "intensity"): + if key in entry: + return _clamped_float(entry[key], 0.5) + + text = _entry_text(entry).lower() + high_terms = ( + "ahegao", + "orgasm", + "climax", + "drool", + "drooling", + "tongue out", + "eyes rolled", + "fucked-out", + "cum-smeared", + "saliva", + "gagging", + "slack jaw", + "jaw slack", + "slack-jawed", + "sex-drunk", + "overwhelmed", + "strained", + "messy", + "panting", + "trembling", + "shaking", + "wide open mouth", + "raw ", + "wild ", + "dazed", + "spent", + ) + if any(term in text for term in high_terms): + return 0.9 + + medium_terms = ( + "seductive", + "teasing", + "lustful", + "aroused", + "bedroom", + "dominant", + "predatory", + "control", + "stern", + "strict", + "smirk", + "parted lips", + "open-mouthed", + "heated", + "hungry", + "inviting", + "sensual", + "fetish", + "commanding", + "flushed", + "moan", + ) + if any(term in text for term in medium_terms): + return 0.62 + + low_terms = ( + "neutral", + "quiet", + "calm", + "reserved", + "relaxed", + "candid", + "closed-mouth", + "thoughtful", + "controlled", + "focused", + "steady", + "bitten-lip", + "braced", + "held breath", + "concentrated", + "aloof", + "bored", + "tired", + "unfocused", + "contented", + "fashion", + "soft", + "sleepy", + "fresh-faced", + ) + if any(term in text for term in low_terms): + return 0.25 + return 0.5 + + +def _expression_entries_for_intensity(entries: list[Any], expression_intensity: float) -> list[Any]: + target = _clamped_float(expression_intensity, 0.5) + weighted: list[Any] = [] + for entry in entries: + entry_intensity = _expression_intensity_hint(entry) + distance = abs(target - entry_intensity) + if distance <= 0.18: + intensity_weight = 4.0 + elif distance <= 0.35: + intensity_weight = 1.4 + elif distance <= 0.55: + intensity_weight = 0.35 + else: + intensity_weight = 0.05 + + if isinstance(entry, dict): + adjusted = dict(entry) + try: + base_weight = float(adjusted.get("weight", 1.0)) + except (TypeError, ValueError): + base_weight = 1.0 + adjusted["weight"] = max(0.0, base_weight) * intensity_weight + weighted.append(adjusted) + else: + weighted.append({"text": _entry_text(entry), "weight": intensity_weight}) + return weighted or entries + + def _pose_pool(category: dict[str, Any], subcategory: dict[str, Any], item: Any, subject_type: str, poses: str) -> list[Any]: configured = _merged_field(category, subcategory, item, "poses") if configured: @@ -1396,9 +1590,17 @@ def _pose_pool(category: dict[str, Any], subcategory: dict[str, Any], item: Any, def _composition_pool(category: dict[str, Any], subcategory: dict[str, Any], item: Any, subject_type: str) -> list[Any]: - configured = _merged_field(category, subcategory, item, "compositions") + configured = _configured_pool( + category, + subcategory, + item, + "compositions", + "composition_pools", + load_composition_pool_library(), + "inherit_compositions", + ) if configured: - return _list_from(configured) + return configured if subject_type in ("group", "configured_cast"): return g.GROUP_COMPOSITIONS if subject_type in ("layout", "scene"): @@ -1420,6 +1622,7 @@ def _build_custom_row( men_count: int, seed: int, seed_config: dict[str, int], + expression_intensity: float, ) -> dict[str, Any]: categories = load_category_library() category_rng = _axis_rng(seed_config, "category", seed, row_number) @@ -1454,12 +1657,14 @@ def _build_custom_row( pose = str(_merged_field(category, subcategory, item, "pose", "") or context.get("fallback_pose") or _choose_text( pose_rng, _compatible_entries(_pose_pool(category, subcategory, item, subject_type, poses), women_count, men_count) )) - expression = _choose_text( - expression_rng, - _compatible_entries(_list_from(_merged_field(category, subcategory, item, "expressions", g.EXPRESSIONS)), women_count, men_count), + expression_entries = _compatible_entries( + _expression_entries_for_intensity(_expression_pool(category, subcategory, item), expression_intensity), + women_count, + men_count, ) + expression = _choose_text(expression_rng, expression_entries) if subject_type in ("couple", "group") and ";" not in expression: - expression = f"{expression}; {_choose_text(expression_rng, g.EXPRESSIONS)}" + expression = f"{expression}; {_choose_text(expression_rng, expression_entries)}" composition = _choose_text( composition_rng, _compatible_entries(_composition_pool(category, subcategory, item, subject_type), women_count, men_count), @@ -1492,6 +1697,7 @@ def _build_custom_row( "scene_slug": scene_slug, "pose": pose, "expression": expression, + "expression_intensity": expression_intensity, "composition": composition, "role_graph": role_graph, "positive_suffix": positive_suffix, @@ -1588,6 +1794,7 @@ def build_prompt( women_count: int = 1, men_count: int = 1, camera_config: str | dict[str, Any] | None = None, + expression_intensity: float = 0.5, ) -> dict[str, Any]: apply_pool_extensions() row_number = max(1, int(row_number)) @@ -1599,6 +1806,7 @@ def build_prompt( figure = figure if figure in ("curvy", "balanced", "bombshell") else "curvy" minimal_ratio = _ratio_or_none(minimal_clothing_ratio) pose_ratio = _ratio_or_none(standard_pose_ratio) + expression_intensity = _clamped_float(expression_intensity, 0.5) parsed_seed_config = _parse_seed_config(seed_config) exact_custom_subcategory = bool(subcategory and subcategory != RANDOM_SUBCATEGORY and " / " in subcategory) @@ -1649,6 +1857,7 @@ def build_prompt( int(men_count), seed, parsed_seed_config, + expression_intensity, ) if extra_positive.strip(): @@ -1658,6 +1867,7 @@ def build_prompt( row["prompt"] = _prepend_trigger(row["prompt"], active_trigger, bool(prepend_trigger_to_prompt)) row["negative_prompt"] = _combined_negative(row.get("negative_prompt", g.NEGATIVE_PROMPT), extra_negative) row["trigger"] = active_trigger + row["expression_intensity"] = expression_intensity return row @@ -1700,6 +1910,8 @@ def build_insta_of_options_json( continuity: str = "same_creator_same_room", softcore_camera_mode: str = "handheld_selfie", hardcore_camera_mode: str = "same_as_softcore", + softcore_expression_intensity: float = 0.45, + hardcore_expression_intensity: float = 0.85, ) -> str: return json.dumps( { @@ -1713,6 +1925,8 @@ def build_insta_of_options_json( "continuity": continuity, "softcore_camera_mode": softcore_camera_mode, "hardcore_camera_mode": hardcore_camera_mode, + "softcore_expression_intensity": _clamped_float(softcore_expression_intensity, 0.45), + "hardcore_expression_intensity": _clamped_float(hardcore_expression_intensity, 0.85), }, ensure_ascii=True, sort_keys=True, @@ -1731,6 +1945,8 @@ def _parse_insta_of_options(options_json: str | dict[str, Any] | None) -> dict[s "continuity": "same_creator_same_room", "softcore_camera_mode": "handheld_selfie", "hardcore_camera_mode": "same_as_softcore", + "softcore_expression_intensity": 0.45, + "hardcore_expression_intensity": 0.85, } if not options_json: return defaults @@ -1753,6 +1969,14 @@ def _parse_insta_of_options(options_json: str | dict[str, Any] | None) -> dict[s parsed["softcore_camera_mode"] = parsed["softcore_camera_mode"] if parsed["softcore_camera_mode"] in CAMERA_MODE_PROMPTS else defaults["softcore_camera_mode"] if parsed["hardcore_camera_mode"] not in CAMERA_MODE_PROMPTS and parsed["hardcore_camera_mode"] != "same_as_softcore": parsed["hardcore_camera_mode"] = defaults["hardcore_camera_mode"] + parsed["softcore_expression_intensity"] = _clamped_float( + parsed.get("softcore_expression_intensity"), + defaults["softcore_expression_intensity"], + ) + parsed["hardcore_expression_intensity"] = _clamped_float( + parsed.get("hardcore_expression_intensity"), + defaults["hardcore_expression_intensity"], + ) for key in ("hardcore_women_count", "hardcore_men_count"): try: parsed[key] = max(0, min(12, int(parsed[key]))) @@ -1845,6 +2069,7 @@ def build_insta_of_pair( seed_config=parsed_seed_config, women_count=1, men_count=0, + expression_intensity=options["softcore_expression_intensity"], ) hard_row = build_prompt( category="Hardcore sexual poses", @@ -1868,6 +2093,7 @@ def build_insta_of_pair( seed_config=parsed_seed_config, women_count=hard_women_count, men_count=hard_men_count, + expression_intensity=options["hardcore_expression_intensity"], ) descriptor = _insta_of_descriptor(soft_row)