Add camera control node
This commit is contained in:
@@ -7,6 +7,7 @@ The node is registered as:
|
|||||||
|
|
||||||
- `prompt_builder / SxCP Prompt Builder`
|
- `prompt_builder / SxCP Prompt Builder`
|
||||||
- `prompt_builder / SxCP Seed Control`
|
- `prompt_builder / SxCP Seed Control`
|
||||||
|
- `prompt_builder / SxCP Camera Control`
|
||||||
- `prompt_builder / SxCP Caption Naturalizer`
|
- `prompt_builder / SxCP Caption Naturalizer`
|
||||||
- `prompt_builder / SxCP Insta/OF Options`
|
- `prompt_builder / SxCP Insta/OF Options`
|
||||||
- `prompt_builder / SxCP Insta/OF Prompt Pair`
|
- `prompt_builder / SxCP Insta/OF Prompt Pair`
|
||||||
@@ -23,6 +24,27 @@ It outputs:
|
|||||||
`SxCP Seed Control` outputs `seed_config`, which can be connected to the prompt
|
`SxCP Seed Control` outputs `seed_config`, which can be connected to the prompt
|
||||||
builder's optional `seed_config` input.
|
builder's optional `seed_config` input.
|
||||||
|
|
||||||
|
`SxCP Camera Control` outputs `camera_config`, which can be connected to the
|
||||||
|
prompt builder or the Insta/OF pair node. It makes camera/framing first-class
|
||||||
|
instead of relying on a weak phrase inside the prompt.
|
||||||
|
|
||||||
|
Camera controls:
|
||||||
|
|
||||||
|
- `camera_mode`: `standard`, `handheld_selfie`, `mirror_selfie`,
|
||||||
|
`phone_tripod`, `creator_pov`, `bed_selfie`, `bathroom_mirror`,
|
||||||
|
`phone_flash`, or `action_cam`.
|
||||||
|
- `shot_size`: `auto`, `full_body`, `three_quarter`, `waist_up`, `close_up`,
|
||||||
|
or `extreme_close_up`.
|
||||||
|
- `angle`: `auto`, `eye_level`, `high_angle`, `low_angle`, `overhead`,
|
||||||
|
`side_profile`, `rear_view`, or `mirror_reflection`.
|
||||||
|
- `lens`: `auto`, `smartphone_wide`, `ultra_wide`, `portrait_lens`,
|
||||||
|
`telephoto`, or `macro_detail`.
|
||||||
|
- `distance`: `auto`, `arm_length`, `near_body`, `bedside`, or `room_corner`.
|
||||||
|
- `orientation`: `auto`, `vertical_story`, `square_feed`, or `horizontal`.
|
||||||
|
- `phone_visibility`: `auto`, `phone_visible`, `phone_hidden`,
|
||||||
|
`screen_reflection`, or `ring_light_visible`.
|
||||||
|
- `priority`: `soft_hint`, `strong`, or `locked`.
|
||||||
|
|
||||||
`SxCP Caption Naturalizer` rewrites tag-like captions or labeled prompts into
|
`SxCP Caption Naturalizer` rewrites tag-like captions or labeled prompts into
|
||||||
more natural language. Connect the prompt builder's `metadata_json` output to
|
more natural language. Connect the prompt builder's `metadata_json` output to
|
||||||
`source_text` for the cleanest result. You can also connect `caption` or
|
`source_text` for the cleanest result. You can also connect `caption` or
|
||||||
@@ -61,7 +83,9 @@ It outputs:
|
|||||||
|
|
||||||
`SxCP Insta/OF Options` outputs `options_json`, which can be connected to the
|
`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
|
pair node. Defaults are set so the softcore prompt is solo while the hardcore
|
||||||
prompt can include partners.
|
prompt can include partners. It also defaults the camera to handheld selfie
|
||||||
|
framing. For stronger camera control, connect `SxCP Camera Control` to the pair
|
||||||
|
node's optional `camera_config` input.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
@@ -77,6 +101,9 @@ Options:
|
|||||||
- `continuity`: `same_creator_same_room` keeps the scene/composition aligned;
|
- `continuity`: `same_creator_same_room` keeps the scene/composition aligned;
|
||||||
`same_creator_new_scene` keeps the same creator descriptor but lets the
|
`same_creator_new_scene` keeps the same creator descriptor but lets the
|
||||||
hardcore scene use its own setting.
|
hardcore scene use its own setting.
|
||||||
|
- `softcore_camera_mode`: base camera mode for the softcore output.
|
||||||
|
- `hardcore_camera_mode`: `same_as_softcore` or a separate base camera mode for
|
||||||
|
the hardcore output.
|
||||||
|
|
||||||
## Built-In Categories
|
## Built-In Categories
|
||||||
|
|
||||||
@@ -92,6 +119,9 @@ The node keeps the original generator controls:
|
|||||||
- `figure`: `curvy`, `balanced`, `bombshell`.
|
- `figure`: `curvy`, `balanced`, `bombshell`.
|
||||||
- `no_plus_women`: excludes plus-size women.
|
- `no_plus_women`: excludes plus-size women.
|
||||||
- `no_black`: excludes Black/African-coded women from women-focused pools.
|
- `no_black`: excludes Black/African-coded women from women-focused pools.
|
||||||
|
- Optional `camera_config`: connect `SxCP Camera Control` to force selfie,
|
||||||
|
phone, lens, angle, distance, crop, and camera-priority behavior. This applies
|
||||||
|
to custom categories too, including `Hardcore sexual poses`.
|
||||||
|
|
||||||
`auto_weighted` uses the original batch mix: mostly women, then men, couples, and
|
`auto_weighted` uses the original batch mix: mostly women, then men, couples, and
|
||||||
group/layout rows. Direct categories generate only that selected category.
|
group/layout rows. Direct categories generate only that selected category.
|
||||||
|
|||||||
+78
@@ -4,20 +4,38 @@ import json
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
from .prompt_builder import (
|
from .prompt_builder import (
|
||||||
|
build_camera_config_json,
|
||||||
build_insta_of_options_json,
|
build_insta_of_options_json,
|
||||||
build_insta_of_pair,
|
build_insta_of_pair,
|
||||||
build_prompt,
|
build_prompt,
|
||||||
build_seed_config_json,
|
build_seed_config_json,
|
||||||
|
camera_angle_choices,
|
||||||
|
camera_distance_choices,
|
||||||
|
camera_lens_choices,
|
||||||
|
camera_mode_choices,
|
||||||
|
camera_orientation_choices,
|
||||||
|
camera_phone_choices,
|
||||||
|
camera_priority_choices,
|
||||||
|
camera_shot_choices,
|
||||||
category_choices,
|
category_choices,
|
||||||
subcategory_choices,
|
subcategory_choices,
|
||||||
)
|
)
|
||||||
from .caption_naturalizer import naturalize_caption
|
from .caption_naturalizer import naturalize_caption
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from prompt_builder import (
|
from prompt_builder import (
|
||||||
|
build_camera_config_json,
|
||||||
build_insta_of_options_json,
|
build_insta_of_options_json,
|
||||||
build_insta_of_pair,
|
build_insta_of_pair,
|
||||||
build_prompt,
|
build_prompt,
|
||||||
build_seed_config_json,
|
build_seed_config_json,
|
||||||
|
camera_angle_choices,
|
||||||
|
camera_distance_choices,
|
||||||
|
camera_lens_choices,
|
||||||
|
camera_mode_choices,
|
||||||
|
camera_orientation_choices,
|
||||||
|
camera_phone_choices,
|
||||||
|
camera_priority_choices,
|
||||||
|
camera_shot_choices,
|
||||||
category_choices,
|
category_choices,
|
||||||
subcategory_choices,
|
subcategory_choices,
|
||||||
)
|
)
|
||||||
@@ -50,6 +68,7 @@ class SxCPPromptBuilder:
|
|||||||
},
|
},
|
||||||
"optional": {
|
"optional": {
|
||||||
"seed_config": ("STRING", {"default": "", "multiline": True}),
|
"seed_config": ("STRING", {"default": "", "multiline": True}),
|
||||||
|
"camera_config": ("STRING", {"default": "", "multiline": True}),
|
||||||
"extra_positive": ("STRING", {"default": "", "multiline": True}),
|
"extra_positive": ("STRING", {"default": "", "multiline": True}),
|
||||||
"extra_negative": ("STRING", {"default": "", "multiline": True}),
|
"extra_negative": ("STRING", {"default": "", "multiline": True}),
|
||||||
},
|
},
|
||||||
@@ -81,6 +100,7 @@ class SxCPPromptBuilder:
|
|||||||
trigger,
|
trigger,
|
||||||
prepend_trigger_to_prompt,
|
prepend_trigger_to_prompt,
|
||||||
seed_config="",
|
seed_config="",
|
||||||
|
camera_config="",
|
||||||
extra_positive="",
|
extra_positive="",
|
||||||
extra_negative="",
|
extra_negative="",
|
||||||
):
|
):
|
||||||
@@ -106,6 +126,7 @@ class SxCPPromptBuilder:
|
|||||||
extra_positive=extra_positive or "",
|
extra_positive=extra_positive or "",
|
||||||
extra_negative=extra_negative or "",
|
extra_negative=extra_negative or "",
|
||||||
seed_config=seed_config or "",
|
seed_config=seed_config or "",
|
||||||
|
camera_config=camera_config or "",
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
row["prompt"],
|
row["prompt"],
|
||||||
@@ -167,6 +188,52 @@ class SxCPSeedControl:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SxCPCameraControl:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"camera_mode": (camera_mode_choices(), {"default": "handheld_selfie"}),
|
||||||
|
"shot_size": (camera_shot_choices(), {"default": "auto"}),
|
||||||
|
"angle": (camera_angle_choices(), {"default": "auto"}),
|
||||||
|
"lens": (camera_lens_choices(), {"default": "smartphone_wide"}),
|
||||||
|
"distance": (camera_distance_choices(), {"default": "arm_length"}),
|
||||||
|
"orientation": (camera_orientation_choices(), {"default": "vertical_story"}),
|
||||||
|
"phone_visibility": (camera_phone_choices(), {"default": "phone_visible"}),
|
||||||
|
"priority": (camera_priority_choices(), {"default": "locked"}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("STRING",)
|
||||||
|
RETURN_NAMES = ("camera_config",)
|
||||||
|
FUNCTION = "build"
|
||||||
|
CATEGORY = "prompt_builder"
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
camera_mode,
|
||||||
|
shot_size,
|
||||||
|
angle,
|
||||||
|
lens,
|
||||||
|
distance,
|
||||||
|
orientation,
|
||||||
|
phone_visibility,
|
||||||
|
priority,
|
||||||
|
):
|
||||||
|
return (
|
||||||
|
build_camera_config_json(
|
||||||
|
camera_mode=camera_mode,
|
||||||
|
shot_size=shot_size,
|
||||||
|
angle=angle,
|
||||||
|
lens=lens,
|
||||||
|
distance=distance,
|
||||||
|
orientation=orientation,
|
||||||
|
phone_visibility=phone_visibility,
|
||||||
|
priority=priority,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SxCPCaptionNaturalizer:
|
class SxCPCaptionNaturalizer:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(cls):
|
def INPUT_TYPES(cls):
|
||||||
@@ -223,6 +290,8 @@ class SxCPInstaOFOptions:
|
|||||||
"hardcore_level": (["explicit", "hardcore"], {"default": "hardcore"}),
|
"hardcore_level": (["explicit", "hardcore"], {"default": "hardcore"}),
|
||||||
"platform_style": (["hybrid", "instagram", "onlyfans"], {"default": "hybrid"}),
|
"platform_style": (["hybrid", "instagram", "onlyfans"], {"default": "hybrid"}),
|
||||||
"continuity": (["same_creator_same_room", "same_creator_new_scene"], {"default": "same_creator_same_room"}),
|
"continuity": (["same_creator_same_room", "same_creator_new_scene"], {"default": "same_creator_same_room"}),
|
||||||
|
"softcore_camera_mode": (camera_mode_choices(), {"default": "handheld_selfie"}),
|
||||||
|
"hardcore_camera_mode": (["same_as_softcore"] + camera_mode_choices(), {"default": "same_as_softcore"}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,6 +310,8 @@ class SxCPInstaOFOptions:
|
|||||||
hardcore_level,
|
hardcore_level,
|
||||||
platform_style,
|
platform_style,
|
||||||
continuity,
|
continuity,
|
||||||
|
softcore_camera_mode,
|
||||||
|
hardcore_camera_mode,
|
||||||
):
|
):
|
||||||
return (
|
return (
|
||||||
build_insta_of_options_json(
|
build_insta_of_options_json(
|
||||||
@@ -252,6 +323,8 @@ class SxCPInstaOFOptions:
|
|||||||
hardcore_level=hardcore_level,
|
hardcore_level=hardcore_level,
|
||||||
platform_style=platform_style,
|
platform_style=platform_style,
|
||||||
continuity=continuity,
|
continuity=continuity,
|
||||||
|
softcore_camera_mode=softcore_camera_mode,
|
||||||
|
hardcore_camera_mode=hardcore_camera_mode,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -274,6 +347,7 @@ class SxCPInstaOFPromptPair:
|
|||||||
"optional": {
|
"optional": {
|
||||||
"seed_config": ("STRING", {"default": "", "multiline": True}),
|
"seed_config": ("STRING", {"default": "", "multiline": True}),
|
||||||
"options_json": ("STRING", {"default": "", "multiline": True}),
|
"options_json": ("STRING", {"default": "", "multiline": True}),
|
||||||
|
"camera_config": ("STRING", {"default": "", "multiline": True}),
|
||||||
"extra_positive": ("STRING", {"default": "", "multiline": True}),
|
"extra_positive": ("STRING", {"default": "", "multiline": True}),
|
||||||
"extra_negative": ("STRING", {"default": "", "multiline": True}),
|
"extra_negative": ("STRING", {"default": "", "multiline": True}),
|
||||||
},
|
},
|
||||||
@@ -306,6 +380,7 @@ class SxCPInstaOFPromptPair:
|
|||||||
prepend_trigger_to_prompt,
|
prepend_trigger_to_prompt,
|
||||||
seed_config="",
|
seed_config="",
|
||||||
options_json="",
|
options_json="",
|
||||||
|
camera_config="",
|
||||||
extra_positive="",
|
extra_positive="",
|
||||||
extra_negative="",
|
extra_negative="",
|
||||||
):
|
):
|
||||||
@@ -321,6 +396,7 @@ class SxCPInstaOFPromptPair:
|
|||||||
prepend_trigger_to_prompt=prepend_trigger_to_prompt,
|
prepend_trigger_to_prompt=prepend_trigger_to_prompt,
|
||||||
seed_config=seed_config or "",
|
seed_config=seed_config or "",
|
||||||
options_json=options_json or "",
|
options_json=options_json or "",
|
||||||
|
camera_config=camera_config or "",
|
||||||
extra_positive=extra_positive or "",
|
extra_positive=extra_positive or "",
|
||||||
extra_negative=extra_negative or "",
|
extra_negative=extra_negative or "",
|
||||||
)
|
)
|
||||||
@@ -339,6 +415,7 @@ class SxCPInstaOFPromptPair:
|
|||||||
NODE_CLASS_MAPPINGS = {
|
NODE_CLASS_MAPPINGS = {
|
||||||
"SxCPPromptBuilder": SxCPPromptBuilder,
|
"SxCPPromptBuilder": SxCPPromptBuilder,
|
||||||
"SxCPSeedControl": SxCPSeedControl,
|
"SxCPSeedControl": SxCPSeedControl,
|
||||||
|
"SxCPCameraControl": SxCPCameraControl,
|
||||||
"SxCPCaptionNaturalizer": SxCPCaptionNaturalizer,
|
"SxCPCaptionNaturalizer": SxCPCaptionNaturalizer,
|
||||||
"SxCPInstaOFOptions": SxCPInstaOFOptions,
|
"SxCPInstaOFOptions": SxCPInstaOFOptions,
|
||||||
"SxCPInstaOFPromptPair": SxCPInstaOFPromptPair,
|
"SxCPInstaOFPromptPair": SxCPInstaOFPromptPair,
|
||||||
@@ -347,6 +424,7 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||||
"SxCPPromptBuilder": "SxCP Prompt Builder",
|
"SxCPPromptBuilder": "SxCP Prompt Builder",
|
||||||
"SxCPSeedControl": "SxCP Seed Control",
|
"SxCPSeedControl": "SxCP Seed Control",
|
||||||
|
"SxCPCameraControl": "SxCP Camera Control",
|
||||||
"SxCPCaptionNaturalizer": "SxCP Caption Naturalizer",
|
"SxCPCaptionNaturalizer": "SxCP Caption Naturalizer",
|
||||||
"SxCPInstaOFOptions": "SxCP Insta/OF Options",
|
"SxCPInstaOFOptions": "SxCP Insta/OF Options",
|
||||||
"SxCPInstaOFPromptPair": "SxCP Insta/OF Prompt Pair",
|
"SxCPInstaOFPromptPair": "SxCP Insta/OF Prompt Pair",
|
||||||
|
|||||||
+262
-2
@@ -76,6 +76,95 @@ LAYOUT_TEMPLATE = (
|
|||||||
"Avoid: {negative_prompt}. Use no readable text unless the layout naturally needs small decorative placeholder marks."
|
"Avoid: {negative_prompt}. Use no readable text unless the layout naturally needs small decorative placeholder marks."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CAMERA_MODE_PROMPTS = {
|
||||||
|
"standard": "",
|
||||||
|
"handheld_selfie": (
|
||||||
|
"Camera mode: handheld smartphone selfie, close arm-length framing, visible creator-shot perspective, "
|
||||||
|
"slight wide-angle intimacy, direct eye contact, natural phone-camera composition."
|
||||||
|
),
|
||||||
|
"mirror_selfie": (
|
||||||
|
"Camera mode: mirror selfie with the phone visible in one hand, reflective framing, creator looking at the screen, "
|
||||||
|
"body and environment visible through the mirror."
|
||||||
|
),
|
||||||
|
"phone_tripod": (
|
||||||
|
"Camera mode: phone on tripod or ring-light stand, creator-facing social-video framing, stable vertical composition, "
|
||||||
|
"hands-free self-recorded setup."
|
||||||
|
),
|
||||||
|
"creator_pov": (
|
||||||
|
"Camera mode: creator-held POV, intimate subscriber-view angle, the creator controls the camera, close foreground body framing."
|
||||||
|
),
|
||||||
|
"bed_selfie": (
|
||||||
|
"Camera mode: bed selfie shot from a phone held above or beside the body, intimate close framing, sheets visible around the subject."
|
||||||
|
),
|
||||||
|
"bathroom_mirror": (
|
||||||
|
"Camera mode: bathroom mirror selfie, phone visible, tiled private room, close vertical framing, candid creator-shot energy."
|
||||||
|
),
|
||||||
|
"phone_flash": (
|
||||||
|
"Camera mode: direct phone-flash selfie, crisp flash highlights, candid night-post feeling, hard-edged smartphone shadows."
|
||||||
|
),
|
||||||
|
"action_cam": (
|
||||||
|
"Camera mode: body-mounted or handheld action-camera intimacy, very close wide-angle perspective, dynamic creator-shot framing."
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
CAMERA_SHOT_PROMPTS = {
|
||||||
|
"auto": "",
|
||||||
|
"full_body": "Shot size: full body visible, head-to-toe framing, no important body parts cropped out.",
|
||||||
|
"three_quarter": "Shot size: three-quarter body framing, face, torso, hips, and thighs clearly visible.",
|
||||||
|
"waist_up": "Shot size: waist-up creator framing with face and upper body as the focus.",
|
||||||
|
"close_up": "Shot size: close-up framing with face, expression, hands, and body contact emphasized.",
|
||||||
|
"extreme_close_up": "Shot size: extreme close-up detail shot, tightly framed and intimate.",
|
||||||
|
}
|
||||||
|
|
||||||
|
CAMERA_ANGLE_PROMPTS = {
|
||||||
|
"auto": "",
|
||||||
|
"eye_level": "Angle: eye-level camera angle with direct creator eye contact.",
|
||||||
|
"high_angle": "Angle: high-angle selfie looking down toward the body.",
|
||||||
|
"low_angle": "Angle: low-angle phone camera looking upward from near the body.",
|
||||||
|
"overhead": "Angle: overhead phone shot looking down at the full pose.",
|
||||||
|
"side_profile": "Angle: side-profile camera view emphasizing body silhouette and contact points.",
|
||||||
|
"rear_view": "Angle: rear-view camera framing with the body turned away from the lens.",
|
||||||
|
"mirror_reflection": "Angle: mirror-reflection composition with the phone and reflected body placement readable.",
|
||||||
|
}
|
||||||
|
|
||||||
|
CAMERA_LENS_PROMPTS = {
|
||||||
|
"auto": "",
|
||||||
|
"smartphone_wide": "Lens: smartphone wide-angle lens with slight edge distortion and close personal scale.",
|
||||||
|
"ultra_wide": "Lens: ultra-wide phone lens, exaggerated near-camera perspective, environmental context visible.",
|
||||||
|
"portrait_lens": "Lens: phone portrait mode, shallow depth of field, crisp subject separation.",
|
||||||
|
"telephoto": "Lens: compressed telephoto-style framing, flatter proportions, less distortion.",
|
||||||
|
"macro_detail": "Lens: macro-detail phone shot focused on texture, skin, fabric, and contact detail.",
|
||||||
|
}
|
||||||
|
|
||||||
|
CAMERA_DISTANCE_PROMPTS = {
|
||||||
|
"auto": "",
|
||||||
|
"arm_length": "Camera distance: arm-length selfie distance, close enough to feel handheld.",
|
||||||
|
"near_body": "Camera distance: near-body camera placement with intimate foreground framing.",
|
||||||
|
"bedside": "Camera distance: phone placed beside the body on the bed or floor.",
|
||||||
|
"room_corner": "Camera distance: phone set across the room, self-recorded but wider and more observational.",
|
||||||
|
}
|
||||||
|
|
||||||
|
CAMERA_ORIENTATION_PROMPTS = {
|
||||||
|
"auto": "",
|
||||||
|
"vertical_story": "Orientation: vertical 9:16 story/reel framing.",
|
||||||
|
"square_feed": "Orientation: square social-feed crop.",
|
||||||
|
"horizontal": "Orientation: horizontal phone-video crop.",
|
||||||
|
}
|
||||||
|
|
||||||
|
CAMERA_PHONE_PROMPTS = {
|
||||||
|
"auto": "",
|
||||||
|
"phone_visible": "Phone visibility: phone visible in hand or mirror, clearly creator-shot.",
|
||||||
|
"phone_hidden": "Phone visibility: phone is implied but not visible, preserving the selfie/creator-shot perspective.",
|
||||||
|
"screen_reflection": "Phone visibility: screen glow or reflection visible in the scene.",
|
||||||
|
"ring_light_visible": "Phone visibility: ring light or tripod visible enough to read as self-recorded content.",
|
||||||
|
}
|
||||||
|
|
||||||
|
CAMERA_PRIORITY_PROMPTS = {
|
||||||
|
"soft_hint": "Camera priority: treat the camera notes as style guidance.",
|
||||||
|
"strong": "Camera priority: strongly preserve the selected camera, lens, angle, crop, and phone-shot perspective.",
|
||||||
|
"locked": "Camera priority: locked camera constraint; do not replace this with a studio, third-person, cinematic, or unrelated camera view.",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
_EXTENSIONS_APPLIED = False
|
_EXTENSIONS_APPLIED = False
|
||||||
|
|
||||||
@@ -641,6 +730,148 @@ def _combined_negative(base: str, extra: str) -> str:
|
|||||||
return ", ".join(parts)
|
return ", ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def camera_mode_choices() -> list[str]:
|
||||||
|
return list(CAMERA_MODE_PROMPTS)
|
||||||
|
|
||||||
|
|
||||||
|
def camera_shot_choices() -> list[str]:
|
||||||
|
return list(CAMERA_SHOT_PROMPTS)
|
||||||
|
|
||||||
|
|
||||||
|
def camera_angle_choices() -> list[str]:
|
||||||
|
return list(CAMERA_ANGLE_PROMPTS)
|
||||||
|
|
||||||
|
|
||||||
|
def camera_lens_choices() -> list[str]:
|
||||||
|
return list(CAMERA_LENS_PROMPTS)
|
||||||
|
|
||||||
|
|
||||||
|
def camera_distance_choices() -> list[str]:
|
||||||
|
return list(CAMERA_DISTANCE_PROMPTS)
|
||||||
|
|
||||||
|
|
||||||
|
def camera_orientation_choices() -> list[str]:
|
||||||
|
return list(CAMERA_ORIENTATION_PROMPTS)
|
||||||
|
|
||||||
|
|
||||||
|
def camera_phone_choices() -> list[str]:
|
||||||
|
return list(CAMERA_PHONE_PROMPTS)
|
||||||
|
|
||||||
|
|
||||||
|
def camera_priority_choices() -> list[str]:
|
||||||
|
return list(CAMERA_PRIORITY_PROMPTS)
|
||||||
|
|
||||||
|
|
||||||
|
def build_camera_config_json(
|
||||||
|
camera_mode: str = "standard",
|
||||||
|
shot_size: str = "auto",
|
||||||
|
angle: str = "auto",
|
||||||
|
lens: str = "auto",
|
||||||
|
distance: str = "auto",
|
||||||
|
orientation: str = "auto",
|
||||||
|
phone_visibility: str = "auto",
|
||||||
|
priority: str = "strong",
|
||||||
|
) -> str:
|
||||||
|
return json.dumps(
|
||||||
|
{
|
||||||
|
"camera_mode": camera_mode,
|
||||||
|
"shot_size": shot_size,
|
||||||
|
"angle": angle,
|
||||||
|
"lens": lens,
|
||||||
|
"distance": distance,
|
||||||
|
"orientation": orientation,
|
||||||
|
"phone_visibility": phone_visibility,
|
||||||
|
"priority": priority,
|
||||||
|
},
|
||||||
|
ensure_ascii=True,
|
||||||
|
sort_keys=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _choice(value: Any, choices: dict[str, str], default: str) -> str:
|
||||||
|
value = str(value or default)
|
||||||
|
return value if value in choices else default
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_camera_config(camera_config: str | dict[str, Any] | None) -> dict[str, str]:
|
||||||
|
defaults = {
|
||||||
|
"camera_mode": "standard",
|
||||||
|
"shot_size": "auto",
|
||||||
|
"angle": "auto",
|
||||||
|
"lens": "auto",
|
||||||
|
"distance": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"phone_visibility": "auto",
|
||||||
|
"priority": "strong",
|
||||||
|
}
|
||||||
|
if not camera_config:
|
||||||
|
return defaults
|
||||||
|
if isinstance(camera_config, dict):
|
||||||
|
raw = camera_config
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
raw = json.loads(str(camera_config))
|
||||||
|
except json.JSONDecodeError as exc:
|
||||||
|
raise ValueError(f"Invalid camera_config JSON: {exc}") from exc
|
||||||
|
if not isinstance(raw, dict):
|
||||||
|
raise ValueError("camera_config must be a JSON object")
|
||||||
|
parsed = {**defaults, **raw}
|
||||||
|
return {
|
||||||
|
"camera_mode": _choice(parsed.get("camera_mode"), CAMERA_MODE_PROMPTS, defaults["camera_mode"]),
|
||||||
|
"shot_size": _choice(parsed.get("shot_size"), CAMERA_SHOT_PROMPTS, defaults["shot_size"]),
|
||||||
|
"angle": _choice(parsed.get("angle"), CAMERA_ANGLE_PROMPTS, defaults["angle"]),
|
||||||
|
"lens": _choice(parsed.get("lens"), CAMERA_LENS_PROMPTS, defaults["lens"]),
|
||||||
|
"distance": _choice(parsed.get("distance"), CAMERA_DISTANCE_PROMPTS, defaults["distance"]),
|
||||||
|
"orientation": _choice(parsed.get("orientation"), CAMERA_ORIENTATION_PROMPTS, defaults["orientation"]),
|
||||||
|
"phone_visibility": _choice(parsed.get("phone_visibility"), CAMERA_PHONE_PROMPTS, defaults["phone_visibility"]),
|
||||||
|
"priority": _choice(parsed.get("priority"), CAMERA_PRIORITY_PROMPTS, defaults["priority"]),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _camera_config_with_mode(camera_config: str | dict[str, Any] | None, camera_mode: str) -> dict[str, str]:
|
||||||
|
parsed = _parse_camera_config(camera_config)
|
||||||
|
if camera_mode and camera_mode != "from_camera_config":
|
||||||
|
parsed["camera_mode"] = _choice(camera_mode, CAMERA_MODE_PROMPTS, parsed["camera_mode"])
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
|
def _camera_directive(camera_config: str | dict[str, Any] | None) -> tuple[str, dict[str, str]]:
|
||||||
|
parsed = _parse_camera_config(camera_config)
|
||||||
|
parts = [
|
||||||
|
CAMERA_MODE_PROMPTS[parsed["camera_mode"]],
|
||||||
|
CAMERA_SHOT_PROMPTS[parsed["shot_size"]],
|
||||||
|
CAMERA_ANGLE_PROMPTS[parsed["angle"]],
|
||||||
|
CAMERA_LENS_PROMPTS[parsed["lens"]],
|
||||||
|
CAMERA_DISTANCE_PROMPTS[parsed["distance"]],
|
||||||
|
CAMERA_ORIENTATION_PROMPTS[parsed["orientation"]],
|
||||||
|
CAMERA_PHONE_PROMPTS[parsed["phone_visibility"]],
|
||||||
|
]
|
||||||
|
parts = [part for part in parts if part]
|
||||||
|
if not parts:
|
||||||
|
return "", parsed
|
||||||
|
parts.append(CAMERA_PRIORITY_PROMPTS[parsed["priority"]])
|
||||||
|
return " ".join(parts), parsed
|
||||||
|
|
||||||
|
|
||||||
|
def _insert_positive_directive(prompt: str, directive: str) -> str:
|
||||||
|
marker = " Avoid:"
|
||||||
|
if marker in prompt:
|
||||||
|
before, after = prompt.split(marker, 1)
|
||||||
|
return f"{before.rstrip()} {directive}{marker}{after}"
|
||||||
|
return f"{prompt.rstrip()} {directive}"
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_camera_config(row: dict[str, Any], camera_config: str | dict[str, Any] | None) -> dict[str, Any]:
|
||||||
|
directive, parsed = _camera_directive(camera_config)
|
||||||
|
row["camera_config"] = parsed
|
||||||
|
row["camera_directive"] = directive
|
||||||
|
if not directive:
|
||||||
|
return row
|
||||||
|
row["prompt"] = _insert_positive_directive(row["prompt"], directive)
|
||||||
|
row["caption"] = f"{row.get('caption', '').rstrip()}, {parsed['camera_mode'].replace('_', ' ')} camera framing"
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
def _row_seed(seed: int, row_number: int, salt: int = 0) -> int:
|
def _row_seed(seed: int, row_number: int, salt: int = 0) -> int:
|
||||||
return int(seed) + int(row_number) * 1009 + salt * 9176
|
return int(seed) + int(row_number) * 1009 + salt * 9176
|
||||||
|
|
||||||
@@ -1310,6 +1541,7 @@ def build_prompt(
|
|||||||
seed_config: str | dict[str, Any] | None = None,
|
seed_config: str | dict[str, Any] | None = None,
|
||||||
women_count: int = 1,
|
women_count: int = 1,
|
||||||
men_count: int = 1,
|
men_count: int = 1,
|
||||||
|
camera_config: str | dict[str, Any] | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
apply_pool_extensions()
|
apply_pool_extensions()
|
||||||
row_number = max(1, int(row_number))
|
row_number = max(1, int(row_number))
|
||||||
@@ -1375,6 +1607,7 @@ def build_prompt(
|
|||||||
|
|
||||||
if extra_positive.strip():
|
if extra_positive.strip():
|
||||||
row["prompt"] = f"{row['prompt'].rstrip()} {extra_positive.strip()}"
|
row["prompt"] = f"{row['prompt'].rstrip()} {extra_positive.strip()}"
|
||||||
|
row = _apply_camera_config(row, camera_config)
|
||||||
active_trigger = trigger.strip() or g.TRIGGER
|
active_trigger = trigger.strip() or g.TRIGGER
|
||||||
row["prompt"] = _prepend_trigger(row["prompt"], active_trigger, bool(prepend_trigger_to_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["negative_prompt"] = _combined_negative(row.get("negative_prompt", g.NEGATIVE_PROMPT), extra_negative)
|
||||||
@@ -1419,6 +1652,8 @@ def build_insta_of_options_json(
|
|||||||
hardcore_level: str = "hardcore",
|
hardcore_level: str = "hardcore",
|
||||||
platform_style: str = "hybrid",
|
platform_style: str = "hybrid",
|
||||||
continuity: str = "same_creator_same_room",
|
continuity: str = "same_creator_same_room",
|
||||||
|
softcore_camera_mode: str = "handheld_selfie",
|
||||||
|
hardcore_camera_mode: str = "same_as_softcore",
|
||||||
) -> str:
|
) -> str:
|
||||||
return json.dumps(
|
return json.dumps(
|
||||||
{
|
{
|
||||||
@@ -1430,6 +1665,8 @@ def build_insta_of_options_json(
|
|||||||
"hardcore_level": hardcore_level,
|
"hardcore_level": hardcore_level,
|
||||||
"platform_style": platform_style,
|
"platform_style": platform_style,
|
||||||
"continuity": continuity,
|
"continuity": continuity,
|
||||||
|
"softcore_camera_mode": softcore_camera_mode,
|
||||||
|
"hardcore_camera_mode": hardcore_camera_mode,
|
||||||
},
|
},
|
||||||
ensure_ascii=True,
|
ensure_ascii=True,
|
||||||
sort_keys=True,
|
sort_keys=True,
|
||||||
@@ -1446,6 +1683,8 @@ def _parse_insta_of_options(options_json: str | dict[str, Any] | None) -> dict[s
|
|||||||
"hardcore_level": "hardcore",
|
"hardcore_level": "hardcore",
|
||||||
"platform_style": "hybrid",
|
"platform_style": "hybrid",
|
||||||
"continuity": "same_creator_same_room",
|
"continuity": "same_creator_same_room",
|
||||||
|
"softcore_camera_mode": "handheld_selfie",
|
||||||
|
"hardcore_camera_mode": "same_as_softcore",
|
||||||
}
|
}
|
||||||
if not options_json:
|
if not options_json:
|
||||||
return defaults
|
return defaults
|
||||||
@@ -1465,6 +1704,9 @@ def _parse_insta_of_options(options_json: str | dict[str, Any] | None) -> dict[s
|
|||||||
parsed["hardcore_level"] = parsed["hardcore_level"] if parsed["hardcore_level"] in INSTA_OF_HARDCORE_LEVELS else defaults["hardcore_level"]
|
parsed["hardcore_level"] = parsed["hardcore_level"] if parsed["hardcore_level"] in INSTA_OF_HARDCORE_LEVELS else defaults["hardcore_level"]
|
||||||
parsed["platform_style"] = parsed["platform_style"] if parsed["platform_style"] in INSTA_OF_PLATFORM_STYLES else defaults["platform_style"]
|
parsed["platform_style"] = parsed["platform_style"] if parsed["platform_style"] in INSTA_OF_PLATFORM_STYLES else defaults["platform_style"]
|
||||||
parsed["continuity"] = parsed["continuity"] if parsed["continuity"] in ("same_creator_same_room", "same_creator_new_scene") else defaults["continuity"]
|
parsed["continuity"] = parsed["continuity"] if parsed["continuity"] in ("same_creator_same_room", "same_creator_new_scene") else defaults["continuity"]
|
||||||
|
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"]
|
||||||
for key in ("hardcore_women_count", "hardcore_men_count"):
|
for key in ("hardcore_women_count", "hardcore_men_count"):
|
||||||
try:
|
try:
|
||||||
parsed[key] = max(0, min(12, int(parsed[key])))
|
parsed[key] = max(0, min(12, int(parsed[key])))
|
||||||
@@ -1526,6 +1768,7 @@ def build_insta_of_pair(
|
|||||||
prepend_trigger_to_prompt: bool,
|
prepend_trigger_to_prompt: bool,
|
||||||
seed_config: str | dict[str, Any] | None = None,
|
seed_config: str | dict[str, Any] | None = None,
|
||||||
options_json: str | dict[str, Any] | None = None,
|
options_json: str | dict[str, Any] | None = None,
|
||||||
|
camera_config: str | dict[str, Any] | None = None,
|
||||||
extra_positive: str = "",
|
extra_positive: str = "",
|
||||||
extra_negative: str = "",
|
extra_negative: str = "",
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
@@ -1585,6 +1828,15 @@ def build_insta_of_pair(
|
|||||||
platform_style = INSTA_OF_PLATFORM_STYLES[options["platform_style"]]
|
platform_style = INSTA_OF_PLATFORM_STYLES[options["platform_style"]]
|
||||||
soft_level = INSTA_OF_SOFT_LEVELS[options["softcore_level"]]
|
soft_level = INSTA_OF_SOFT_LEVELS[options["softcore_level"]]
|
||||||
hard_level = INSTA_OF_HARDCORE_LEVELS[options["hardcore_level"]]
|
hard_level = INSTA_OF_HARDCORE_LEVELS[options["hardcore_level"]]
|
||||||
|
hard_camera_mode = options["hardcore_camera_mode"]
|
||||||
|
if hard_camera_mode == "same_as_softcore":
|
||||||
|
hard_camera_mode = options["softcore_camera_mode"]
|
||||||
|
soft_camera_config = _camera_config_with_mode(camera_config, options["softcore_camera_mode"])
|
||||||
|
hard_camera_config = _camera_config_with_mode(camera_config, hard_camera_mode)
|
||||||
|
soft_camera_directive, soft_camera_config = _camera_directive(soft_camera_config)
|
||||||
|
hard_camera_directive, hard_camera_config = _camera_directive(hard_camera_config)
|
||||||
|
soft_camera_sentence = f"Camera control: {soft_camera_directive} " if soft_camera_directive else ""
|
||||||
|
hard_camera_sentence = f"Camera control: {hard_camera_directive} " if hard_camera_directive else ""
|
||||||
hard_scene = soft_row["scene_text"] if options["continuity"] == "same_creator_same_room" else hard_row["scene_text"]
|
hard_scene = soft_row["scene_text"] if options["continuity"] == "same_creator_same_room" else hard_row["scene_text"]
|
||||||
hard_composition = soft_row["composition"] if options["continuity"] == "same_creator_same_room" else hard_row["composition"]
|
hard_composition = soft_row["composition"] if options["continuity"] == "same_creator_same_room" else hard_row["composition"]
|
||||||
soft_cast = (
|
soft_cast = (
|
||||||
@@ -1599,6 +1851,7 @@ def build_insta_of_pair(
|
|||||||
f"Softcore setup: {soft_level}. Cast continuity: {soft_cast}. "
|
f"Softcore setup: {soft_level}. Cast continuity: {soft_cast}. "
|
||||||
f"Outfit: {soft_row['item']}. Pose: {soft_row['pose']}. Setting: {soft_row['scene_text']}. "
|
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"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. "
|
"Keep the softcore version adult-only, consensual, seductive, creator-shot, and non-explicit. "
|
||||||
f"{soft_row['positive_suffix']} Avoid: {INSTA_OF_SOFT_NEGATIVE}."
|
f"{soft_row['positive_suffix']} Avoid: {INSTA_OF_SOFT_NEGATIVE}."
|
||||||
)
|
)
|
||||||
@@ -1608,6 +1861,7 @@ def build_insta_of_pair(
|
|||||||
"Apply the shared descriptor to the most visually central woman, keeping her continuous with the softcore version. "
|
"Apply the shared descriptor to the most visually central woman, keeping her continuous with the softcore version. "
|
||||||
f"Role graph: {hard_row['role_graph']} Sexual scene: {hard_row['item']}. "
|
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"Setting: {hard_scene}. Facial expressions: {hard_row['expression']}. Composition: {hard_composition}. "
|
||||||
|
f"{hard_camera_sentence}"
|
||||||
"All participants are consenting adults 21+. "
|
"All participants are consenting adults 21+. "
|
||||||
f"{hard_row['positive_suffix']} Avoid: {INSTA_OF_NEGATIVE}."
|
f"{hard_row['positive_suffix']} Avoid: {INSTA_OF_NEGATIVE}."
|
||||||
)
|
)
|
||||||
@@ -1621,11 +1875,13 @@ def build_insta_of_pair(
|
|||||||
hard_negative = _combined_negative(INSTA_OF_NEGATIVE, extra_negative)
|
hard_negative = _combined_negative(INSTA_OF_NEGATIVE, extra_negative)
|
||||||
soft_caption = (
|
soft_caption = (
|
||||||
f"{active_trigger}, Insta/OF softcore mode, {descriptor}, {soft_level}, "
|
f"{active_trigger}, Insta/OF softcore mode, {descriptor}, {soft_level}, "
|
||||||
f"{soft_row['item']}, {soft_row['pose']}, {soft_row['scene_text']}, {soft_row['composition']}"
|
f"{soft_row['item']}, {soft_row['pose']}, {soft_row['scene_text']}, {soft_row['composition']}, "
|
||||||
|
f"{soft_camera_config['camera_mode'].replace('_', ' ')} camera"
|
||||||
)
|
)
|
||||||
hard_caption = (
|
hard_caption = (
|
||||||
f"{active_trigger}, Insta/OF hardcore mode, same primary creator descriptor, {descriptor}, "
|
f"{active_trigger}, Insta/OF hardcore mode, same primary creator descriptor, {descriptor}, "
|
||||||
f"{hard_cast}, {hard_row['role_graph']}, {hard_row['item']}, {hard_scene}, {hard_composition}"
|
f"{hard_cast}, {hard_row['role_graph']}, {hard_row['item']}, {hard_scene}, {hard_composition}, "
|
||||||
|
f"{hard_camera_config['camera_mode'].replace('_', ' ')} camera"
|
||||||
)
|
)
|
||||||
metadata = {
|
metadata = {
|
||||||
"mode": "Insta/OF",
|
"mode": "Insta/OF",
|
||||||
@@ -1641,5 +1897,9 @@ def build_insta_of_pair(
|
|||||||
"hardcore_row": hard_row,
|
"hardcore_row": hard_row,
|
||||||
"hardcore_women_count": hard_women_count,
|
"hardcore_women_count": hard_women_count,
|
||||||
"hardcore_men_count": hard_men_count,
|
"hardcore_men_count": hard_men_count,
|
||||||
|
"softcore_camera_config": soft_camera_config,
|
||||||
|
"hardcore_camera_config": hard_camera_config,
|
||||||
|
"softcore_camera_directive": soft_camera_directive,
|
||||||
|
"hardcore_camera_directive": hard_camera_directive,
|
||||||
}
|
}
|
||||||
return metadata
|
return metadata
|
||||||
|
|||||||
Reference in New Issue
Block a user