from pydantic import BaseModel, field_validator from datetime import datetime from typing import Optional # Supported models and their constraints LYRIA_MODELS = { "lyria-3-clip-preview": { "name": "Clip Preview", "max_duration": 30, "default_duration": 30, "description": "30-second clip, fastest generation", }, "lyria-3-pro-preview": { "name": "Pro", "max_duration": 184, "default_duration": 120, "description": "Full songs up to ~3 min, structure-aware", }, } DEFAULT_MODEL = "lyria-3-clip-preview" class GenerateRequest(BaseModel): prompt: str model: str = DEFAULT_MODEL duration: Optional[int] = None # defaults to model default prompt_structure: Optional[dict] = None # structured prompt fields for Pro mode @field_validator('model') @classmethod def validate_model(cls, v): v = v.strip() if v not in LYRIA_MODELS: valid = ', '.join(LYRIA_MODELS.keys()) raise ValueError(f"Unknown model '{v}'. Supported: {valid}") return v def get_effective_duration(self) -> int: """Return duration clamped to model limits, defaulting to model default if not specified.""" info = LYRIA_MODELS[self.model] dur = self.duration if self.duration is not None else info["default_duration"] return min(dur, info["max_duration"]) def build_prompt(self) -> str: """Build the final prompt from structured fields or return the simple prompt.""" if self.prompt_structure: parts = ["Generate a track with the following elements:"] field_order = [ ("style", "Style"), ("timbre", "Timbre"), ("structure", "Structure"), ("mood", "Mood"), ("tempo", "Tempo"), ("instrumentation", "Instrumentation"), ("effects", "Effects"), ("additional", "Additional Notes"), ] for key, label in field_order: if key in self.prompt_structure and self.prompt_structure[key]: parts.append(f"**{label}:** {self.prompt_structure[key]}") return "\n\n".join(parts) return self.prompt class GenerateResponse(BaseModel): id: str status: str message: str class Clip(BaseModel): id: str title: Optional[str] = None prompt: str model: str duration_seconds: int filename: str artwork_filename: Optional[str] = None file_size_bytes: Optional[int] = None status: str error_message: Optional[str] = None created_at: datetime prompt_structure: Optional[dict] = None bpm: Optional[float] = None key: Optional[str] = None artist: Optional[str] = None album: Optional[str] = None comment: Optional[str] = None class RenameRequest(BaseModel): title: str class MetadataRequest(BaseModel): artist: Optional[str] = None album: Optional[str] = None comment: Optional[str] = None class GenerateArtworkRequest(BaseModel): prompt: str class ClipList(BaseModel): clips: list[Clip] total: int