diff --git a/backend/src/database.py b/backend/src/database.py index 108f667..ee186f6 100644 --- a/backend/src/database.py +++ b/backend/src/database.py @@ -98,6 +98,11 @@ async def init_db(): await db.execute("ALTER TABLE clips ADD COLUMN comment TEXT") except Exception: pass # Column already exists + # Migration: add genre column if it doesn't exist + try: + await db.execute("ALTER TABLE clips ADD COLUMN genre TEXT") + except Exception: + pass # Column already exists await db.commit() async def create_clip(prompt: str, model: str, duration: int, prompt_structure: str = None) -> str: @@ -176,11 +181,11 @@ async def rename_clip(clip_id: str, title: str): ) await db.commit() -async def update_metadata(clip_id: str, artist: str = None, album: str = None, comment: str = None): +async def update_metadata(clip_id: str, artist: str = None, album: str = None, comment: str = None, genre: str = None): async with aiosqlite.connect(DB_PATH) as db: await db.execute( - "UPDATE clips SET artist = ?, album = ?, comment = ? WHERE id = ?", - (artist, album, comment, clip_id) + "UPDATE clips SET artist = ?, album = ?, comment = ?, genre = ? WHERE id = ?", + (artist, album, comment, genre, clip_id) ) await db.commit() diff --git a/backend/src/main.py b/backend/src/main.py index da164a3..7334f47 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -135,7 +135,7 @@ async def stream_audio(clip_id: str): return FileResponse(file_path, media_type="audio/mpeg", filename=clip["filename"]) from mutagen.mp3 import MP3 -from mutagen.id3 import ID3, TIT1, TIT2, TPE1, TALB, COMM, TBPM, TKEY, APIC, PictureType +from mutagen.id3 import ID3, TIT1, TIT2, TPE1, TALB, COMM, TBPM, TKEY, APIC, PictureType, TCON @app.get("/api/clips/{clip_id}/download") async def download_clip(clip_id: str): @@ -197,6 +197,10 @@ async def download_clip(clip_id: str): if clip.get("key"): audio.tags["TKEY"] = TKEY(encoding=3, text=clip["key"]) + # Genre + if clip.get("genre"): + audio.tags["TCON"] = TCON(encoding=3, text=clip["genre"]) + # Album art if clip.get("artwork_filename"): artwork_path = ARTWORK_DIR / clip["artwork_filename"] @@ -496,7 +500,7 @@ async def update_clip_metadata(clip_id: str, request: MetadataRequest): clip = await get_clip(clip_id) if not clip: raise HTTPException(status_code=404, detail="Clip not found") - await update_metadata(clip_id, request.artist, request.album, request.comment) + await update_metadata(clip_id, request.artist, request.album, request.comment, request.genre) updated = await get_clip(clip_id) return updated diff --git a/backend/src/models.py b/backend/src/models.py index 58f8194..5afa13d 100644 --- a/backend/src/models.py +++ b/backend/src/models.py @@ -92,6 +92,7 @@ class MetadataRequest(BaseModel): artist: Optional[str] = None album: Optional[str] = None comment: Optional[str] = None + genre: Optional[str] = None class GenerateArtworkRequest(BaseModel): prompt: str diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 32d1ef0..fb53a93 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -190,12 +190,12 @@ function App() { } } - const handleUpdateMetadata = async (id: string, artist: string, album: string, comment: string) => { + const handleUpdateMetadata = async (id: string, artist: string, album: string, genre: string, comment: string) => { try { const res = await fetch(`/api/clips/${id}/metadata`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ artist, album, comment }), + body: JSON.stringify({ artist, album, genre, comment }), }) if (res.ok) { const updated = await res.json() diff --git a/frontend/src/components/DetailPanel.tsx b/frontend/src/components/DetailPanel.tsx index a17e116..e5ed9df 100644 --- a/frontend/src/components/DetailPanel.tsx +++ b/frontend/src/components/DetailPanel.tsx @@ -17,6 +17,7 @@ interface Clip { prompt_structure?: Record | null artist?: string | null album?: string | null + genre?: string | null comment?: string | null created_at: string } @@ -33,7 +34,7 @@ interface Props { onPlay?: (clip: Clip) => void onTogglePlay?: () => void onGenerateArtwork?: (clip: Clip) => void - onUpdateMetadata?: (id: string, artist: string, album: string, comment: string) => void + onUpdateMetadata?: (id: string, artist: string, album: string, genre: string, comment: string) => void } const modelName = (modelId: string) => { @@ -383,25 +384,31 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o - {/* Metadata Fields (Artist, Album, Comment) */} + {/* Metadata Fields (Artist, Album, Genre, Comment) */}
onUpdateMetadata?.(clip.id, val, clip.album || '', clip.comment || '')} + onSave={(val) => onUpdateMetadata?.(clip.id, val, clip.album || '', clip.genre || '', clip.comment || '')} /> onUpdateMetadata?.(clip.id, clip.artist || '', val, clip.comment || '')} + onSave={(val) => onUpdateMetadata?.(clip.id, clip.artist || '', val, clip.genre || '', clip.comment || '')} + /> + onUpdateMetadata?.(clip.id, clip.artist || '', clip.album || '', val, clip.comment || '')} /> onUpdateMetadata?.(clip.id, clip.artist || '', clip.album || '', val)} + onSave={(val) => onUpdateMetadata?.(clip.id, clip.artist || '', clip.album || '', clip.genre || '', val)} />