feat(06-02): add feature extraction and segmentation backend modules + API
- Create lightsync/audio/features.py with extract_features and extract_features_async - Create lightsync/audio/segments.py with detect_segments via agglomerative clustering - Add /api/audio/segments and /api/audio/features endpoints to audio.py - Initialize app.state.segments cache in main.py lifespan
This commit is contained in:
@@ -10,6 +10,8 @@ from fastapi.responses import StreamingResponse
|
||||
from pydantic import BaseModel
|
||||
from lightsync.audio.waveform import extract_peaks
|
||||
from lightsync.audio.beats import analyze_async
|
||||
from lightsync.audio.segments import detect_segments_async
|
||||
from lightsync.audio.features import extract_features_async
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -190,3 +192,39 @@ async def fetch_youtube(request: Request, body: YouTubeFetchRequest):
|
||||
yield f"data: {json.dumps({'type': 'error', 'msg': str(e)})}\n\n"
|
||||
|
||||
return StreamingResponse(event_stream(), media_type="text/event-stream")
|
||||
|
||||
|
||||
@router.get("/segments")
|
||||
async def get_segments(request: Request, path: str, n_segments: int | None = None):
|
||||
"""Return structural segments for an audio file. Cached in app.state.segments."""
|
||||
segments_cache: dict = getattr(request.app.state, "segments", None) or {}
|
||||
|
||||
cache_key = f"{path}:{n_segments or 'auto'}"
|
||||
if cache_key not in segments_cache:
|
||||
p = Path(path)
|
||||
if not p.exists():
|
||||
raise HTTPException(status_code=404, detail=f"File not found: {path}")
|
||||
try:
|
||||
segments = await detect_segments_async(path, n_segments)
|
||||
if not hasattr(request.app.state, "segments") or request.app.state.segments is None:
|
||||
request.app.state.segments = {}
|
||||
request.app.state.segments[cache_key] = segments
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=500, detail=f"Segmentation failed: {exc}") from exc
|
||||
else:
|
||||
segments = segments_cache[cache_key]
|
||||
|
||||
return {"segments": segments, "count": len(segments), "file": path}
|
||||
|
||||
|
||||
@router.get("/features")
|
||||
async def get_features(request: Request, path: str):
|
||||
"""Return extracted audio features (onsets, chroma, RMS) for a file."""
|
||||
p = Path(path)
|
||||
if not p.exists():
|
||||
raise HTTPException(status_code=404, detail=f"File not found: {path}")
|
||||
try:
|
||||
features = await extract_features_async(path)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=500, detail=f"Feature extraction failed: {exc}") from exc
|
||||
return {**features, "file": path}
|
||||
|
||||
Reference in New Issue
Block a user