Full FastAPI backend for the AI music continuation editor:
Services
- chain.py: chain + node CRUD, commit/discard, recursive CTE spine query
- musicgen.py: MusicGenClient with cf-orch allocation + mock mode (CF_MUSICGEN_MOCK=1)
- stems.py: Demucs 4-stem separation subprocess wrapper + mock mode
- export.py: ffmpeg concat demuxer to stitch committed spine into WAV/MP3
API endpoints
- chains: CRUD, multipart audio upload (WAV/MP3/FLAC/OGG/M4A/AIFF)
- nodes: branch creation (202 + BackgroundTasks), commit, discard, audio stream
- gpu: cf-orch capacity status; session allocation stubbed pending cf-orch#43
- stems: Paid-tier stem separation (Demucs, gated via tiers.py)
- export: POST /{chain_id}/export → FileResponse download
- events: SSE stream (node-status events) per chain via asyncio Queue pub/sub
Infrastructure
- lifespan: reads SPARROW_DB_PATH/DATA_DIR at startup (not import time)
- events_store: subscribe/unsubscribe/broadcast pattern for SSE
- CORS: open in dev, SPARROW_CORS_ORIGINS in production
- Background generation opens its own DB connection (WAL-safe)
Tests: 30/30 passing across service units and API integration
34 lines
1,006 B
Python
34 lines
1,006 B
Python
# tests/test_export_service.py — unit tests for export stitch service
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stitch_single_file(tmp_path):
|
|
"""Single file: should copy without invoking ffmpeg."""
|
|
from app.services.export import stitch
|
|
|
|
src = tmp_path / "segment.wav"
|
|
src.write_bytes(b"WAV DATA")
|
|
out = str(tmp_path / "output.wav")
|
|
|
|
result = await stitch([str(src)], out)
|
|
assert result == out
|
|
assert open(out, "rb").read() == b"WAV DATA"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stitch_empty_raises(tmp_path):
|
|
from app.services.export import stitch
|
|
|
|
with pytest.raises(ValueError, match="No audio paths"):
|
|
await stitch([], str(tmp_path / "out.wav"))
|
|
|
|
|
|
def test_make_export_path():
|
|
from app.services.export import make_export_path
|
|
|
|
p = make_export_path("data", "chain-abc", "mp3")
|
|
assert p == "data/chains/chain-abc/export.mp3"
|
|
assert make_export_path("data", "chain-abc", "wav").endswith(".wav")
|