Add librosa beat/mood analysis and an Ambience background driven by it
The background worker now runs a real librosa analyzer (tempo, beat grid, per-second energy/valence curves) instead of only the null baseline, kept behind build_analyzer() so a plain checkout without the analysis extra still runs fine. The web player reads that per-track analysis and drives a new animated "Ambience" background (bubbles, colour, current) that reacts to the beat and the mood curve as the track plays, plus a debug overlay for tuning it. Also adds a one-off script to backfill podcast cover art from iTunes.
This commit is contained in:
@@ -34,7 +34,6 @@ from musicmouse.events import (
|
||||
from musicmouse.services.web.hub import StateHub
|
||||
from musicmouse.services.web.schemas import (
|
||||
AlbumOut,
|
||||
BeatsOut,
|
||||
HaConfigOut,
|
||||
HaDeviceOut,
|
||||
LibraryOut,
|
||||
@@ -43,6 +42,8 @@ from musicmouse.services.web.schemas import (
|
||||
SeekIn,
|
||||
SettingsIn,
|
||||
SettingsOut,
|
||||
TrackCurvesOut,
|
||||
TrackDetailOut,
|
||||
VolumeIn,
|
||||
)
|
||||
from musicmouse.services.web.settings import (
|
||||
@@ -86,11 +87,16 @@ def build_router(
|
||||
)
|
||||
|
||||
@router.get("/tracks/{album_id}/{index}/analysis")
|
||||
def get_track_analysis(album_id: str, index: int) -> BeatsOut:
|
||||
def get_track_analysis(album_id: str, index: int) -> TrackDetailOut:
|
||||
grid = app.library.beats(album_id, index)
|
||||
if grid is None:
|
||||
curve = app.library.curve(album_id, index)
|
||||
if grid is None and curve is None:
|
||||
raise HTTPException(status_code=404, detail="not analyzed")
|
||||
return BeatsOut(times=list(grid.times), strengths=list(grid.strengths))
|
||||
return TrackDetailOut(
|
||||
times=list(grid.times) if grid else [],
|
||||
strengths=list(grid.strengths) if grid else [],
|
||||
curve=TrackCurvesOut(**curve.to_json()) if curve else None,
|
||||
)
|
||||
|
||||
@router.post("/library/refresh", status_code=202)
|
||||
async def refresh_library() -> Response:
|
||||
|
||||
@@ -15,7 +15,6 @@ from musicmouse.library.analysis import TrackAnalysis
|
||||
|
||||
__all__ = [
|
||||
"AlbumOut",
|
||||
"BeatsOut",
|
||||
"HaConfigOut",
|
||||
"HaDeviceOut",
|
||||
"LibraryOut",
|
||||
@@ -24,6 +23,8 @@ __all__ = [
|
||||
"SeekIn",
|
||||
"SettingsIn",
|
||||
"SettingsOut",
|
||||
"TrackCurvesOut",
|
||||
"TrackDetailOut",
|
||||
"TrackOut",
|
||||
"VolumeIn",
|
||||
]
|
||||
@@ -34,6 +35,8 @@ class AnalysisOut(BaseModel):
|
||||
energy: float | None = None
|
||||
valence: float | None = None
|
||||
brightness: float | None = None
|
||||
#: 0..1 confidence that `tempo` is an audible, steady beat - see `TrackAnalysis`.
|
||||
pulse: float | None = None
|
||||
beats: bool = False
|
||||
|
||||
@classmethod
|
||||
@@ -45,6 +48,7 @@ class AnalysisOut(BaseModel):
|
||||
energy=analysis.energy,
|
||||
valence=analysis.valence,
|
||||
brightness=analysis.brightness,
|
||||
pulse=analysis.pulse,
|
||||
beats=analysis.beats,
|
||||
)
|
||||
|
||||
@@ -99,9 +103,23 @@ class LibraryOut(BaseModel):
|
||||
albums: list[AlbumOut]
|
||||
|
||||
|
||||
class BeatsOut(BaseModel):
|
||||
class TrackCurvesOut(BaseModel):
|
||||
hop_seconds: float
|
||||
energy: list[float]
|
||||
valence: list[float]
|
||||
drive: list[float]
|
||||
|
||||
|
||||
class TrackDetailOut(BaseModel):
|
||||
"""Beats + mood/drive curves for the one track currently playing - fetched
|
||||
together since both are per-track detail the library payload never carries.
|
||||
``curve`` is ``None`` only when the whole track failed analysis; ``times``/
|
||||
``strengths`` are empty (not ``None``) for a track with no reliable beat, since a
|
||||
free-tempo or spoken-word track still has real energy/valence/drive curves."""
|
||||
|
||||
times: list[float]
|
||||
strengths: list[float]
|
||||
curve: TrackCurvesOut | None = None
|
||||
|
||||
|
||||
class ConnectionOut(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user