feat: Add themed FFT waveform visualization to detail panel
Backend:
- Install ffmpeg + numpy in Docker image
- New waveform.py: FFT-based SVG generator with 64 frequency bands
- GET /api/clips/{id}/waveform — generates on demand, caches to /data/waveforms/
- DELETE /api/clips/{id} now cascade-removes waveform SVG
- Warm amber-to-orange HSL gradient (25° to 35° hue)
- Mirrored bars: top full opacity, bottom 60% opacity
Frontend:
- DetailPanel shows waveform image beneath album art (80px height)
- Only shown for clips with status='ready'
Storage: /data/waveforms/ persisted via existing ./data volume mount
This commit is contained in:
1 parent
3c6a5d1a84
commit
8f5c8a87d1
6 files changed
+330
No files matched your search
@@ -12,6 +12,7 @@ load_dotenv(Path(__file__).parent.parent / ".env")
|
||||
from models import GenerateRequest, GenerateResponse, ClipList, RenameRequest, LYRIA_MODELS, DEFAULT_MODEL
|
||||
from database import init_db, create_clip, get_clips, get_clip, delete_clip, update_clip_status, rename_clip, set_artwork, remove_artwork
|
||||
from lyria_client import generate_clip, save_clip_file
|
||||
from waveform import generate_waveform_svg, get_waveform_path
|
||||
|
||||
app = FastAPI(title="Lyria Studio", version="0.1.0")
|
||||
|
||||
@@ -124,10 +125,33 @@ async def remove_clip(clip_id: str):
|
||||
artwork_path = ARTWORK_DIR / clip["artwork_filename"]
|
||||
if artwork_path.exists():
|
||||
artwork_path.unlink()
|
||||
# Also delete waveform if it exists
|
||||
waveform_path = get_waveform_path(clip_id)
|
||||
if waveform_path and waveform_path.exists():
|
||||
waveform_path.unlink()
|
||||
await delete_clip(clip_id)
|
||||
return {"deleted": True}
|
||||
|
||||
|
||||
@app.get("/api/clips/{clip_id}/waveform")
|
||||
async def get_waveform(clip_id: str):
|
||||
clip = await get_clip(clip_id)
|
||||
if not clip:
|
||||
raise HTTPException(status_code=404, detail="Clip not found")
|
||||
# Check if waveform already exists
|
||||
svg_path = get_waveform_path(clip_id)
|
||||
if svg_path and svg_path.exists():
|
||||
return FileResponse(svg_path, media_type="image/svg+xml")
|
||||
# Generate on demand
|
||||
try:
|
||||
svg_path_str = generate_waveform_svg(clip_id, clip["filename"])
|
||||
return FileResponse(svg_path_str, media_type="image/svg+xml")
|
||||
except FileNotFoundError:
|
||||
raise HTTPException(status_code=404, detail="Audio file not found")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Waveform generation failed: {str(e)}")
|
||||
|
||||
|
||||
@app.get("/api/clips/{clip_id}/artwork")
|
||||
async def get_artwork(clip_id: str):
|
||||
clip = await get_clip(clip_id)
|
||||
|
||||
Reference in new issue
Block a user