diff --git a/prompt_builder.py b/prompt_builder.py index 4ed7778..0030a79 100644 --- a/prompt_builder.py +++ b/prompt_builder.py @@ -3517,82 +3517,102 @@ def _coworking_location_profile(scene_text: Any) -> dict[str, str]: text = str(scene_text or "").lower() if "business cafe" in text or "work cafe" in text or "cafe" in text: return { + "layout_label": "Business cafe camera layout", "place": "business cafe coworking counter", - "foreground": "counter edge, small plant, laptop corner, and polished phone-check surface", - "midground": "bar stools, warm desk lamps, coffee counter, and laptop users' empty work spots", + "foreground": "counter edge, laptop corner, and small plant", + "midground": "bar stools, warm desk lamps, and coffee-counter work spots", "background": "plants, mirror strip, menu wall, and repeated cafe work tables", } if "corporate office" in text or "office after hours" in text or "copier" in text: return { + "layout_label": "Office camera layout", "place": "empty after-hours office", - "foreground": "copier alcove edge, office chair backs, and the nearest desk corner", - "midground": "repeating desks, glass partition seams, blinds, and muted monitor glow", + "foreground": "copier alcove edge, chair backs, and nearest desk corner", + "midground": "repeating desks, glass partition seams, and muted monitor glow", "background": "rows of empty workstations, city-light windows, and quiet office depth", } return { + "layout_label": "Coworking camera layout", "place": "coworking lounge", - "foreground": "nearest desk edge, laptop corner, chair back, and polished tabletop line", - "midground": "warm work desks, laptop tables, glass partition seams, and open walking aisle", + "foreground": "near desk edge, laptop corner, and chair back", + "midground": "warm work desks, laptop tables, and glass partition seams", "background": "tall windows, repeated desk rows, plants, and soft shared-office depth", } +def _coworking_subject_terms(subject_kind: str, pov_labels: list[str] | None = None) -> tuple[str, str]: + if pov_labels: + return "the visible partner", "them" + if subject_kind == "woman": + return "the woman", "her" + if subject_kind == "man": + return "the man", "him" + if subject_kind == "couple": + return "the couple", "them" + return "the subjects", "them" + + def _coworking_direction_detail( direction: str, profile: dict[str, str], pov_labels: list[str] | None = None, + subject_kind: str = "subjects", ) -> str: direction = str(direction or "").strip().lower() foreground = profile["foreground"] midground = profile["midground"] background = profile["background"] + subject, pronoun = _coworking_subject_terms(subject_kind, pov_labels) if pov_labels: if "right side" in direction: - return f"the visible partner is in right-side profile across the lower foreground: {foreground}; behind them, {midground} runs horizontally toward {background}" + return f"{subject} is in right-side profile across the lower foreground ({foreground}); {midground} run horizontally behind {pronoun} toward {background}" if "left side" in direction: - return f"the visible partner is in left-side profile across the lower foreground: {foreground}; behind them, {midground} runs horizontally toward {background}" + return f"{subject} is in left-side profile across the lower foreground ({foreground}); {midground} run horizontally behind {pronoun} toward {background}" if "back-right" in direction or "back-left" in direction: - return f"the viewer sees the visible partner from a rear-quarter angle, turning back over one shoulder; {foreground} sits at the lower edge while {midground} leads into {background}" + return f"{subject} is seen from a rear-quarter angle, turning back over one shoulder; {foreground} sits low in frame while {midground} lead into {background}" if direction == "back view": - return f"the viewer looks past the visible partner's back toward {midground}, then into {background}, with foreground body cues low in frame" + return f"the viewer looks past {subject}'s back toward {midground}, then into {background}, with POV body cues low in frame" if "front-right" in direction or "front-left" in direction: - return f"the visible partner is close in a front-quarter view over the lower foreground: {foreground}; {midground} recede diagonally into {background}" - return f"the visible partner faces the viewer over the lower foreground: {foreground}; {midground} stacks clearly in front of {background}" + return f"{subject} is close in a front-quarter view over the lower foreground ({foreground}); {midground} recede diagonally behind {pronoun} toward {background}" + return f"{subject} faces the viewer over the lower foreground ({foreground}); {midground} sit between {pronoun} and {background}" if "right side" in direction or "left side" in direction: - return f"the cast is held in clean side profile along the foreground anchor: {foreground}; {midground} creates horizontal perspective lines, with {background} still visible" + return f"{subject} is held in side profile along the {foreground}; {midground} run laterally behind {pronoun}, with {background} still readable" if "back-right" in direction or "back-left" in direction: - return f"the cast is viewed from a rear-quarter angle, partly turning back toward the camera; {foreground} stays low in frame while {midground} leads into {background}" + return f"{subject} is viewed from a rear-quarter angle, partly turning back toward camera; the {foreground} stays low in frame while {midground} lead into {background}" if direction == "back view": - return f"the cast is seen from behind with {foreground} at the camera side, facing into {midground} and {background}" + return f"{subject} is seen from behind with the {foreground} at camera side, facing into {midground} and {background}" if "front-right" in direction or "front-left" in direction: - return f"the cast is placed beside the foreground anchor: {foreground}; {midground} recede diagonally into {background}" - return f"the cast faces the camera beside the foreground anchor: {foreground}; {midground} is layered between the cast and {background}" + return f"{subject} is placed beside the {foreground}; {midground} recede diagonally behind {pronoun} toward {background}" + return f"{subject} faces camera beside the {foreground}; {midground} sit between {pronoun} and {background}" -def _coworking_distance_detail(distance: str, profile: dict[str, str]) -> str: +def _coworking_distance_detail(distance: str, profile: dict[str, str], subject_kind: str, pov_labels: list[str] | None = None) -> str: distance = str(distance or "").strip().lower() + subject, _pronoun = _coworking_subject_terms(subject_kind, pov_labels) if "wide" in distance or "full-body" in distance or "full body" in distance: - return f"Keep full bodies plus floor aisle, table rows, and enough {profile['background']} to read the whole {profile['place']}." + return f"Wide crop keeps {subject}, floor aisle, table rows, and {profile['background']} readable." if "close" in distance: - return f"Crop close, but keep one concrete location anchor visible: {profile['foreground']} or a slice of {profile['midground']}." - return f"Use a medium crop: bodies stay dominant, but the foreground anchor ({profile['foreground']}) and one midground layer ({profile['midground']}) remain visible." + return f"Close crop keeps {subject} dominant while retaining one location anchor: the {profile['foreground']} or a slice of {profile['midground']}." + return f"Medium crop keeps {subject} dominant while retaining the {profile['foreground']} and one midground layer of {profile['midground']}." -def _coworking_elevation_detail(elevation: str, profile: dict[str, str]) -> str: +def _coworking_elevation_detail(elevation: str, profile: dict[str, str], subject_kind: str, pov_labels: list[str] | None = None) -> str: elevation = str(elevation or "").strip().lower() + _subject, pronoun = _coworking_subject_terms(subject_kind, pov_labels) if "low-angle" in elevation: - return f"Low viewpoint: let {profile['foreground']} loom at the lower edge while windows and partitions rise behind the bodies." + return f"Low angle lets the {profile['foreground']} sit low and close while windows and partition seams rise behind {pronoun}." if "elevated" in elevation: - return f"Elevated viewpoint: show tabletop surfaces, laptop rectangles, chair positions, and the walking aisle around the bodies." + return f"Elevated angle shows tabletop surfaces, laptop rectangles, chair positions, and the walking aisle around {pronoun}." if "high-angle" in elevation: - return f"High viewpoint: look down over the grid of desks, chairs, floor aisle, and body placement so the room layout is explicit." - return f"Eye-level viewpoint: keep tabletop lines and glass seams straight enough to make the {profile['place']} believable." + return f"High angle looks down over the desk grid, chairs, floor aisle, and placement of {pronoun}." + return f"Eye-level angle keeps tabletop lines and glass seams straight enough to sell the {profile['place']}." def _coworking_camera_scene_directive( scene_text: Any, parsed: dict[str, Any], pov_labels: list[str] | None = None, + subject_kind: str = "subjects", ) -> str: if not _is_coworking_scene(scene_text): return "" @@ -3606,18 +3626,18 @@ def _coworking_camera_scene_directive( if not any((direction, elevation, distance, custom_prompt)): return "" profile = _coworking_location_profile(scene_text) - direction_detail = _coworking_direction_detail(direction, profile, pov_labels) - distance_detail = _coworking_distance_detail(distance, profile) - elevation_detail = _coworking_elevation_detail(elevation, profile) + direction_detail = _coworking_direction_detail(direction, profile, pov_labels, subject_kind) + distance_detail = _coworking_distance_detail(distance, profile, subject_kind, pov_labels) + elevation_detail = _coworking_elevation_detail(elevation, profile, subject_kind, pov_labels) if pov_labels: return ( - f"From the POV participant's position inside the {profile['place']}, {direction_detail}. " - f"{distance_detail} {elevation_detail} Use the multiangle camera only as spatial geometry for what the viewer can see." + f"{profile['layout_label']} from POV: {direction_detail}. " + f"{distance_detail} {elevation_detail} Use the multiangle camera only as first-person spatial geometry." ) geometry = _camera_geometry_phrase(parsed) - geometry_clause = f" from a {geometry}" if geometry else "" + geometry_clause = f" ({geometry})" if geometry else "" return ( - f"In the {profile['place']}{geometry_clause}, {direction_detail}. " + f"{profile['layout_label']}{geometry_clause}: {direction_detail}. " f"{distance_detail} {elevation_detail}" ) @@ -3627,11 +3647,32 @@ def _camera_scene_directive_for_context( composition: Any, camera_config: str | dict[str, Any] | None, pov_labels: list[str] | None = None, + subject_kind: str = "subjects", ) -> tuple[str, dict[str, Any]]: parsed = _parse_camera_config(camera_config) if parsed["camera_detail"] == "off" or parsed["camera_mode"] == "disabled": return "", parsed - return _coworking_camera_scene_directive(scene_text, parsed, pov_labels), parsed + return _coworking_camera_scene_directive(scene_text, parsed, pov_labels, subject_kind), parsed + + +def _row_camera_subject_kind(row: dict[str, Any]) -> str: + subject_type = str(row.get("subject_type") or row.get("primary_subject") or "").lower() + if subject_type in ("woman", "adult woman") or subject_type == "single_any": + return "woman" + if subject_type in ("man", "adult man"): + return "man" + try: + women_count = int(row.get("women_count") or 0) + men_count = int(row.get("men_count") or 0) + except (TypeError, ValueError): + women_count = men_count = 0 + if women_count == 1 and men_count == 0: + return "woman" + if women_count == 0 and men_count == 1: + return "man" + if women_count + men_count == 2: + return "couple" + return "subjects" def _apply_camera_config(row: dict[str, Any], camera_config: str | dict[str, Any] | None) -> dict[str, Any]: @@ -3647,6 +3688,7 @@ def _apply_camera_config(row: dict[str, Any], camera_config: str | dict[str, Any row.get("composition") or row.get("source_composition"), parsed, pov_labels, + _row_camera_subject_kind(row), ) row["camera_config"] = parsed row["camera_scene_directive"] = scene_directive @@ -8201,12 +8243,14 @@ def build_insta_of_pair( soft_row.get("composition"), soft_camera_config, soft_pov_camera_labels, + "woman" if options["softcore_cast"] == "solo" else "subjects", ) hard_camera_scene_directive, hard_camera_config = _camera_scene_directive_for_context( hard_scene, hard_composition, hard_camera_config, pov_character_labels, + "couple" if hard_women_count + hard_men_count == 2 else "subjects", ) if soft_pov_camera_labels: soft_camera_directive = ""