diff --git a/backend/src/main.py b/backend/src/main.py index 34528cc..af5c231 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -113,6 +113,31 @@ async def stream_audio(clip_id: str): raise HTTPException(status_code=404, detail="Audio file not found") return FileResponse(file_path, media_type="audio/mpeg", filename=clip["filename"]) +@app.get("/api/clips/{clip_id}/download") +async def download_clip(clip_id: str): + clip = await get_clip(clip_id) + if not clip: + raise HTTPException(status_code=404, detail="Clip not found") + file_path = CLIPS_DIR / clip["filename"] + if not file_path.exists(): + raise HTTPException(status_code=404, detail="Audio file not found") + + # Build download filename from title or prompt + base_name = clip.get("title") or clip["prompt"] + # Sanitize for filesystem + safe_name = "".join(c for c in base_name if c.isalnum() or c in " -_").strip() + safe_name = safe_name[:60] # Limit length + if not safe_name: + safe_name = clip_id + download_filename = f"{safe_name}.mp3" + + return FileResponse( + file_path, + media_type="audio/mpeg", + filename=download_filename, + content_disposition_type="attachment" + ) + @app.delete("/api/clips/{clip_id}") async def remove_clip(clip_id: str): clip = await get_clip(clip_id) diff --git a/frontend/src/components/DetailPanel.tsx b/frontend/src/components/DetailPanel.tsx index cdef05a..54fa4bc 100644 --- a/frontend/src/components/DetailPanel.tsx +++ b/frontend/src/components/DetailPanel.tsx @@ -302,7 +302,17 @@ export default function DetailPanel({ clip, onClose, onRename, onDelete, onUploa {/* Actions */} -
+
+ + + + + Download MP3 + + +