feat: Add album artwork support
- Backend: artwork_filename column on clips table (auto-migration)
- Backend: PATCH /api/clips/{id}/artwork (upload image, 5MB limit)
- Backend: GET /api/clips/{id}/artwork (serve image with correct content-type)
- Backend: DELETE /api/clips/{id}/artwork (remove artwork file + null out DB)
- Backend: DELETE /api/clips/{id} now cascade-removes artwork file
- Frontend: Clip interface includes artwork_filename
- Frontend: Gallery ribbons show artwork thumbnail with hover overlay (play + upload)
- Frontend: Gallery tiles show artwork with hover overlay (play + upload)
- Frontend: DetailPanel shows full artwork with upload/change/remove controls
- Frontend: App.tsx handleUploadArtwork + handleRemoveArtwork handlers
- Storage: /data/artwork/ directory for image files
This commit is contained in:
1 parent
23672387e5
commit
8ba7133269
10 files changed
+1030
-59
No files matched your search
+42
-4
@@ -1,11 +1,44 @@
|
||||
from pydantic import BaseModel
|
||||
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 = "lyria-3-clip-preview"
|
||||
duration: int = 30
|
||||
model: str = DEFAULT_MODEL
|
||||
duration: Optional[int] = None # defaults to model default
|
||||
|
||||
@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"])
|
||||
|
||||
class GenerateResponse(BaseModel):
|
||||
id: str
|
||||
@@ -14,15 +47,20 @@ class GenerateResponse(BaseModel):
|
||||
|
||||
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
|
||||
|
||||
class RenameRequest(BaseModel):
|
||||
title: str
|
||||
|
||||
class ClipList(BaseModel):
|
||||
clips: list[Clip]
|
||||
total: int
|
||||
total: int
|
||||
Reference in new issue
Block a user