from uuid import UUID, uuid4 from datetime import datetime, timezone from typing import Any, Literal from pydantic import BaseModel, Field from lightsync.models.device import DeviceConfig class AudioRef(BaseModel): source_type: Literal["file", "youtube"] = "file" path: str | None = None yt_url: str | None = None duration_seconds: float | None = None class CueModel(BaseModel): id: UUID = Field(default_factory=uuid4) timestamp: float duration: float = 4.0 # block length in seconds; defaults for old files mode: Literal["animation", "frame_sequence"] = "animation" animation: str | None = None params: dict[str, Any] = Field(default_factory=dict) class TrackModel(BaseModel): device_id: UUID cues: list[CueModel] = Field(default_factory=list) class AnalysisBlock(BaseModel): analysed_at: datetime | None = None tempo_bpm: float | None = None beat_times: list[float] = Field(default_factory=list) onset_times: list[float] = Field(default_factory=list) calibration_offset: float = 0.03 # seconds; compensates librosa latency bias class ShowModel(BaseModel): schema_version: int = 1 id: UUID = Field(default_factory=uuid4) name: str created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) audio: AudioRef = Field(default_factory=AudioRef) devices: list[DeviceConfig] = Field(default_factory=list) analysis: AnalysisBlock = Field(default_factory=AnalysisBlock) tracks: list[TrackModel] = Field(default_factory=list) ai_sequences: list[Any] = Field(default_factory=list)