HomePilot Deploy Bot
chore(hf): sync HomePilot to HF Space
921d377
Raw
History Blame Contribute Delete
2.98 kB
"""
Primitive shot-spec dataclass + per-node shot selector.
A ``ShotSpec`` is what the asset layer (batch 5's assets/avatar.py)
turns into a generation prompt + duration budget. Phase 1 uses a
rule-based picker; phase 2 swaps to an LLM prompt generator behind
the same ``select_shot`` signature.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Dict
@dataclass(frozen=True)
class ShotSpec:
camera: str = "medium" # wide | medium | close | over_shoulder
move: str = "static" # static | dolly_in | dolly_out | pan | orbit
mood: str = "neutral" # neutral | warm | suspense | playful | dramatic
lighting: str = "natural" # natural | soft | high_contrast | low_key
duration_sec: int = 5
transition: str = "cut" # cut | crossfade | wipe
overlays: tuple = () # ordered overlays (e.g. ('title_card',))
def _mood_for_mode(mode: str) -> str:
if mode == "mature_gated":
return "playful"
if mode == "social_romantic":
return "warm"
if mode == "sfw_education":
return "neutral"
if mode == "language_learning":
return "warm"
if mode == "enterprise_training":
return "neutral"
return "neutral"
def select_shot(node_kind: str, *, mode: str, duration_sec: int = 5) -> ShotSpec:
"""Choose a shot spec for a node. Deterministic."""
base_mood = _mood_for_mode(mode)
if node_kind == "decision":
return ShotSpec(camera="medium", move="static", mood=base_mood,
lighting="soft", duration_sec=max(4, duration_sec),
transition="cut", overlays=("choice_cta",))
if node_kind == "ending":
return ShotSpec(camera="wide", move="dolly_out", mood=base_mood,
lighting="warm", duration_sec=max(4, duration_sec),
transition="crossfade", overlays=("end_card",))
if node_kind == "assessment":
return ShotSpec(camera="close", move="static", mood="neutral",
lighting="soft", duration_sec=max(4, duration_sec),
transition="cut", overlays=("quiz_prompt",))
if node_kind == "remediation":
return ShotSpec(camera="medium", move="dolly_in", mood="neutral",
lighting="soft", duration_sec=max(5, duration_sec),
transition="cut", overlays=("hint_banner",))
# default — scene / merge
return ShotSpec(camera="medium", move="static", mood=base_mood,
lighting="natural", duration_sec=duration_sec,
transition="cut")
def to_dict(spec: ShotSpec) -> Dict[str, Any]:
return {
"camera": spec.camera,
"move": spec.move,
"mood": spec.mood,
"lighting": spec.lighting,
"duration_sec": spec.duration_sec,
"transition": spec.transition,
"overlays": list(spec.overlays),
}