Add shuffle dashboard with cost tracking and daily generation limits.
Player settings panel, stats API, and README document how saved and new tracks mix under a per-day Lyria cap. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Live Ozan Radio
|
||||
|
||||
Personal AI radio — **no catalog music, ever**. DeepSeek is the DJ. Google **Lyria 3** composes every track. Spotify is read-only taste input.
|
||||
Personal AI radio — **no catalog music, ever**. DeepSeek is the DJ. Google **Lyria 3** composes every track. Taste comes from `settings.json` (and optionally Spotify).
|
||||
|
||||
Inspired by [Magenta RealTime 2](https://magenta.withgoogle.com/magenta-realtime-2) (live, ~200ms) and [Lyria 3](https://deepmind.google/models/lyria/) (full songs via Gemini API). On Mac you can layer MRT2 for true live improvisation; this repo ships the cross-platform Lyria + DeepSeek stack first.
|
||||
|
||||
@@ -8,15 +8,59 @@ Inspired by [Magenta RealTime 2](https://magenta.withgoogle.com/magenta-realtime
|
||||
|
||||
| Layer | Product | Role |
|
||||
|-------|---------|------|
|
||||
| DJ brain | DeepSeek (via Tinqs inference or BYOK) | Mood, prompts, variety |
|
||||
| DJ brain | DeepSeek (Tinqs proxy or BYOK) | Mood, prompts, chat, variety |
|
||||
| Music engine | Google Lyria 3 Pro / Clip | Generate MP3 tracks |
|
||||
| Taste | Spotify Web API (optional) | Top artists, genres — never plays Spotify |
|
||||
| Player | FastAPI + `gateway/player.html` | Stream generated queue |
|
||||
| Taste | `settings.json` + `taste_seeds.json` | Genres, mood, instruments — DJ reads every request |
|
||||
| Taste (optional) | Spotify Web API | Top artists, genres — never plays Spotify |
|
||||
| Player | FastAPI + `gateway/player.html` | Stream queue, library, chat, dashboard |
|
||||
| Live (optional) | Magenta RealTime 2 | Apple Silicon only — see below |
|
||||
|
||||
## Player dashboard
|
||||
|
||||
Open **http://127.0.0.1:8787/player** after starting the server.
|
||||
|
||||
| Feature | What it does |
|
||||
|---------|----------------|
|
||||
| **Cost dashboard** | Today's estimated spend, per-track cost, songs generated vs daily cap |
|
||||
| **Settings (gear icon)** | Shuffle mode, mix saved + new tracks, new-song chance, max songs/day |
|
||||
| **Shuffle mode** | On track end or Skip, picks a random saved song or composes a new one |
|
||||
| **Saved songs** | Click any track in the library to play |
|
||||
| **DJ chat** | Talk to DeepSeek — requests can trigger new Lyria generations |
|
||||
|
||||
### Shuffle behaviour
|
||||
|
||||
When shuffle is on (default):
|
||||
|
||||
1. If you have saved songs **and** daily quota remains, each next track has a `new_song_chance` (default 35%) of being freshly composed.
|
||||
2. Otherwise a random saved song plays (never the same track twice in a row if alternatives exist).
|
||||
3. If the library is empty, it generates until the daily cap is hit.
|
||||
|
||||
Daily generation stats live in `songs/stats.json` (gitignored, local runtime only).
|
||||
|
||||
## Taste (`settings.json`)
|
||||
|
||||
Edit `settings.json` at the repo root — the DJ reads it on every generate and chat. Default profile: **ethnic world dubtronica** (global roots + dub space + electronic groove).
|
||||
Edit `settings.json` at the repo root. The DJ reloads it on every generate and chat.
|
||||
|
||||
```json
|
||||
{
|
||||
"taste": { "summary": "...", "genres": [], "mood": [], "instruments": [] },
|
||||
"playback": {
|
||||
"shuffle": true,
|
||||
"mix_existing_and_new": true,
|
||||
"new_song_chance": 0.35
|
||||
},
|
||||
"limits": { "max_new_songs_per_day": 10 },
|
||||
"costs": {
|
||||
"lyria_pro_usd": 0.08,
|
||||
"lyria_clip_usd": 0.04,
|
||||
"deepseek_per_track_usd": 0.002
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Default taste profile: **ethnic world dubtronica** (global roots + dub space + electronic groove).
|
||||
|
||||
The player settings panel PATCHes `playback` and `limits` via `/api/settings` and writes back to this file.
|
||||
|
||||
## Saved songs
|
||||
|
||||
@@ -26,6 +70,7 @@ Every track is written to `./songs/` and **committed via Git LFS** (audio) + pla
|
||||
|------|---------|----------|
|
||||
| `{id}_{title}.mp3` | LFS | Audio |
|
||||
| `{id}.meta.json` | git | Title, mood, DJ line, prompt, lyrics, timestamp |
|
||||
| `manifest.json` | gitignored | Runtime queue index |
|
||||
|
||||
Browse in the player under **Saved songs**, or `GET /api/songs`. After clone: `git lfs install` then `git lfs pull`.
|
||||
|
||||
@@ -39,6 +84,7 @@ pip install -e .
|
||||
copy .env.example .env
|
||||
# Fill GEMINI_API_KEY + DEEPSEEK_API_KEY (and Spotify if you have them)
|
||||
|
||||
$env:DEEPSEEK_BASE_URL = "https://api.deepseek.com/v1"
|
||||
python -m ozan_radio serve
|
||||
# Open http://127.0.0.1:8787/player
|
||||
```
|
||||
@@ -54,19 +100,29 @@ python -m ozan_radio generate
|
||||
| Variable | Required | Notes |
|
||||
|----------|----------|-------|
|
||||
| `GEMINI_API_KEY` | Yes | [Google AI Studio](https://aistudio.google.com/apikey) — Lyria 3 |
|
||||
| `DEEPSEEK_API_KEY` | Yes | Tinqs proxy or DeepSeek direct |
|
||||
| `DEEPSEEK_BASE_URL` | No | Default `https://tinqs.com/api/v1/inference` |
|
||||
| `DEEPSEEK_API_KEY` | Yes | Tinqs proxy token or DeepSeek direct |
|
||||
| `DEEPSEEK_BASE_URL` | No | Default `https://api.deepseek.com/v1` |
|
||||
| `SPOTIFY_*` | No | Refresh token flow — taste only |
|
||||
| `LYRIA_MODEL` | No | `lyria-3-pro-preview` (default) or `lyria-3-clip-preview` |
|
||||
| `RADIO_OUTPUT_DIR` | No | Default `./songs` |
|
||||
|
||||
### Spotify setup (taste profile)
|
||||
### Cost estimates (defaults)
|
||||
|
||||
| Model | ~USD / track |
|
||||
|-------|----------------|
|
||||
| Lyria 3 Pro + DeepSeek | ~$0.082 |
|
||||
| Lyria 3 Clip | ~$0.04 |
|
||||
|
||||
At the default cap of 10 new songs/day with Lyria Pro, projected max spend is **~$0.82/day**. Adjust `costs` and `limits` in `settings.json` or the player settings panel.
|
||||
|
||||
### Spotify setup (optional taste)
|
||||
|
||||
1. Create an app at [Spotify Developer Dashboard](https://developer.spotify.com/dashboard).
|
||||
2. Add redirect URI `http://127.0.0.1:8888/callback`.
|
||||
3. Complete OAuth once to obtain a refresh token (scope: `user-top-read`).
|
||||
4. Paste `SPOTIFY_CLIENT_ID`, `SPOTIFY_CLIENT_SECRET`, `SPOTIFY_REFRESH_TOKEN` into `.env`.
|
||||
|
||||
The Spotify MCP / tool you wired to DeepSeek can call the same endpoints — this repo exposes them natively for the DJ loop.
|
||||
If Spotify is not configured, the DJ uses `settings.json` + `taste_seeds.json`.
|
||||
|
||||
## API
|
||||
|
||||
@@ -74,8 +130,15 @@ The Spotify MCP / tool you wired to DeepSeek can call the same endpoints — thi
|
||||
|--------|------|-------------|
|
||||
| GET | `/api/now` | Current track metadata |
|
||||
| GET | `/api/queue` | Full queue |
|
||||
| GET | `/api/stats` | Dashboard: today's spend, quota, cost estimates |
|
||||
| GET | `/api/settings` | Playback, limits, costs |
|
||||
| PATCH | `/api/settings` | Update shuffle, daily cap, etc. |
|
||||
| POST | `/api/generate` | DJ plans + Lyria renders next track |
|
||||
| POST | `/api/skip` | Advance queue |
|
||||
| POST | `/api/shuffle/next` | Smart next: library shuffle or new generation |
|
||||
| POST | `/api/skip` | Advance (uses shuffle when enabled) |
|
||||
| GET/POST | `/api/chat` | DJ chat log and messages |
|
||||
| GET | `/api/songs` | Saved library |
|
||||
| POST | `/api/songs/{id}/play` | Play a saved track |
|
||||
| GET | `/stream/{file}` | MP3 stream |
|
||||
| GET | `/player` | Web UI |
|
||||
|
||||
@@ -91,24 +154,17 @@ mrt mlx generate --prompt "disco funk" --duration 4.0 --model=mrt2_small
|
||||
|
||||
Wire MRT2 as a bridge between tracks or as a live “bed” under the Lyria queue — PRs welcome.
|
||||
|
||||
## Publish on tinqs.com (public repo)
|
||||
## Repo
|
||||
|
||||
1. On Git Studio: **+ → New Repository**
|
||||
- Owner: `tinqs`
|
||||
- Name: `live-radio`
|
||||
- Visibility: **Public**
|
||||
2. Push (with LFS for song MP3s):
|
||||
Public on Git Studio: **https://tinqs.com/tinqs/live-radio**
|
||||
|
||||
```bash
|
||||
git lfs install
|
||||
git init
|
||||
git remote add origin git@ssh.tinqs.com:tinqs/live-radio.git
|
||||
git add .
|
||||
git commit -m "Live Ozan Radio — DeepSeek DJ + Lyria 3"
|
||||
git push -u origin main
|
||||
git clone git@ssh.tinqs.com:tinqs/live-radio.git
|
||||
cd live-radio
|
||||
git lfs install && git lfs pull
|
||||
```
|
||||
|
||||
3. Preview the player: `https://tinqs.com/tinqs/live-radio/src/branch/main/gateway/player.html` (static shell; audio streams from your running server).
|
||||
Static player preview: `https://tinqs.com/tinqs/live-radio/src/branch/main/gateway/player.html` (shell only — audio streams from your running server).
|
||||
|
||||
## Agent usage
|
||||
|
||||
|
||||
Reference in New Issue
Block a user