Add DJ curation metadata, public auto-play radio, and Lyria web controls.
Extensive per-track meta feeds DeepSeek planning. Caravan of the Night kept with electric guitar marked disliked. Sahara Saz remains gold standard. Gateway index.html auto-plays on tinqs.com. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import random
|
||||
from pathlib import Path
|
||||
|
||||
@@ -19,6 +20,8 @@ from ozan_radio.library import list_saved_songs
|
||||
from ozan_radio.lyria import GeneratedTrack
|
||||
from ozan_radio.radio_settings import load_radio_settings, save_radio_settings_patch
|
||||
from ozan_radio.stats import cost_per_track, record_generation, today_stats
|
||||
from ozan_radio.lyria_capabilities import probe_lyria_api, static_capabilities
|
||||
from ozan_radio.settings import load_lyria_settings
|
||||
from ozan_radio.taste import load_taste_seeds
|
||||
|
||||
app = FastAPI(title="Live Ozan Radio", version="0.1.0")
|
||||
@@ -44,6 +47,7 @@ class SettingsPatch(BaseModel):
|
||||
playback: dict | None = None
|
||||
limits: dict | None = None
|
||||
costs: dict | None = None
|
||||
lyria: dict | None = None
|
||||
|
||||
|
||||
def _can_generate_today(cfg: Config) -> tuple[bool, dict]:
|
||||
@@ -175,6 +179,7 @@ async def root() -> dict:
|
||||
"songs": "/api/songs",
|
||||
"stats": "/api/stats",
|
||||
"settings": "/api/settings",
|
||||
"lyria": "/api/lyria",
|
||||
"shuffle": "POST /api/shuffle/next",
|
||||
"player": "/player",
|
||||
},
|
||||
@@ -212,6 +217,26 @@ async def dashboard_stats() -> dict:
|
||||
return _dashboard_stats(cfg)
|
||||
|
||||
|
||||
@app.get("/api/lyria")
|
||||
async def lyria_capabilities() -> dict:
|
||||
cfg = _get_config()
|
||||
caps = static_capabilities()
|
||||
api_status = probe_lyria_api(cfg.gemini_api_key)
|
||||
active = load_lyria_settings().to_public_dict()
|
||||
available = set(api_status.get("models_available") or [])
|
||||
models = []
|
||||
for m in caps["models"]:
|
||||
entry = dict(m)
|
||||
entry["available"] = not available or m["id"] in available
|
||||
models.append(entry)
|
||||
caps["models"] = models
|
||||
return {
|
||||
"capabilities": caps,
|
||||
"active": active,
|
||||
"api": api_status,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/settings")
|
||||
async def get_settings() -> dict:
|
||||
cfg = _get_config()
|
||||
@@ -310,6 +335,34 @@ async def chat_with_dj(body: ChatRequest, background: BackgroundTasks) -> dict:
|
||||
return result
|
||||
|
||||
|
||||
def _vocal_cues_path() -> Path:
|
||||
return Path(__file__).resolve().parents[2] / "vocal_cues.json"
|
||||
|
||||
|
||||
def _load_vocal_cues() -> dict:
|
||||
path = _vocal_cues_path()
|
||||
if not path.exists():
|
||||
return {"tracks": {}}
|
||||
try:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except (json.JSONDecodeError, OSError):
|
||||
return {"tracks": {}}
|
||||
|
||||
|
||||
@app.get("/api/vocal-cues")
|
||||
async def vocal_cues_all() -> dict:
|
||||
data = _load_vocal_cues()
|
||||
return {"tracks": list(data.get("tracks", {}).keys()), "count": len(data.get("tracks", {}))}
|
||||
|
||||
|
||||
@app.get("/api/vocal-cues/{track_id}")
|
||||
async def vocal_cues_for_track(track_id: str) -> dict:
|
||||
cues = _load_vocal_cues().get("tracks", {}).get(track_id)
|
||||
if not cues:
|
||||
raise HTTPException(404, "No vocal cues for this track")
|
||||
return {"track_id": track_id, **cues}
|
||||
|
||||
|
||||
@app.get("/api/songs")
|
||||
async def saved_songs() -> dict:
|
||||
cfg = _get_config()
|
||||
@@ -370,6 +423,9 @@ async def _background_compose(request: str | None = None) -> None:
|
||||
result = await _compose_track(request)
|
||||
if result.get("status") == "error":
|
||||
_chat.add("dj", f"Couldn't finish that track: {result.get('message', 'unknown error')}")
|
||||
elif result.get("status") == "ok" and result.get("track"):
|
||||
t = result["track"]
|
||||
_chat.add("dj", f"Fresh cut ready: {t.get('title', 'new track')} — hitting the stream now.")
|
||||
|
||||
|
||||
async def _autofill_queue(target: int = 2) -> None:
|
||||
|
||||
Reference in New Issue
Block a user