Add song library with Git LFS, DJ chat, and tinqs/live-radio publish path.

Songs persist under songs/ (MP3 via LFS, metadata in git). Player shows saved library.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-07 14:18:17 +01:00
parent 4924db5617
commit b8ff25f370
18 changed files with 357 additions and 12 deletions
+38
View File
@@ -14,6 +14,7 @@ from ozan_radio.lyria import LyriaEngine
from ozan_radio.queue import RadioQueue
from ozan_radio.spotify import SpotifyTaste
from ozan_radio.chat_store import ChatStore
from ozan_radio.library import list_saved_songs
from ozan_radio.taste import load_taste_seeds
app = FastAPI(title="Live Ozan Radio", version="0.1.0")
@@ -86,6 +87,7 @@ async def root() -> dict:
"generate": "POST /api/generate",
"chat": "POST /api/chat",
"chat_log": "/api/chat",
"songs": "/api/songs",
"player": "/player",
},
}
@@ -154,6 +156,42 @@ async def chat_with_dj(body: ChatRequest, background: BackgroundTasks) -> dict:
return result
@app.get("/api/songs")
async def saved_songs() -> dict:
cfg = _get_config()
songs = list_saved_songs(cfg.output_dir)
return {"count": len(songs), "songs": songs, "folder": str(cfg.output_dir)}
@app.post("/api/songs/{track_id}/play")
async def play_saved(track_id: str) -> dict:
q = _get_queue()
track = q.play_id(track_id)
if not track:
cfg = _get_config()
for entry in list_saved_songs(cfg.output_dir):
if entry["id"] == track_id:
from ozan_radio.dj import TrackPlan
from ozan_radio.lyria import GeneratedTrack
plan = TrackPlan(
id=entry["id"],
title=entry.get("title", track_id),
mood=entry.get("mood", ""),
dj_line=entry.get("dj_line", ""),
lyria_prompt=entry.get("lyria_prompt", ""),
)
path = cfg.output_dir / entry["file"]
restored = GeneratedTrack(plan=plan, audio_path=path, lyrics=entry.get("lyrics", ""))
q.add(restored)
q.play_id(track_id)
np = q.now_playing()
return {"status": "ok", "track": np.__dict__ if np else None}
raise HTTPException(404, "Song not found")
np = q.now_playing()
return {"status": "ok", "track": np.__dict__ if np else None}
@app.post("/api/skip")
async def skip_track() -> dict:
q = _get_queue()