0 was misread as zero allowed on older servers; player no longer falls back to 10 when saving limits. Co-authored-by: Cursor <cursoragent@cursor.com>
Live Ozan Radio
Personal AI radio — no catalog music, ever. DeepSeek is the DJ. Google Lyria 3 composes every track. Taste comes from settings.json + taste_seeds.json — usually built from Spotify screenshots in Cursor (no Spotify API).
Inspired by Magenta RealTime 2 (live, ~200ms) and Lyria 3 (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.
Stack
| Layer | Product | Role |
|---|---|---|
| DJ brain | DeepSeek (Tinqs proxy or BYOK) | Mood, prompts, chat, variety |
| Music engine | Google Lyria 3 Pro / Clip | Generate MP3 tracks |
| Taste | settings.json + taste_seeds.json |
Profile from screenshots or manual edit — see below |
| 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):
- If you have saved songs and daily quota remains, each next track has a
new_song_chance(default 35%) of being freshly composed. - Otherwise a random saved song plays (never the same track twice in a row if alternatives exist).
- 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 from Spotify screenshots (recommended)
No Spotify API keys. Screenshot your library (Home, Daily Mixes, playlists), open this repo in Cursor, attach images, and paste the prompt from docs/TASTE-FROM-SCREENSHOTS.md. The agent updates settings.json and taste_seeds.json.
Share that doc with friends — same flow for group taste (WhatsApp links + screenshots).
Taste (settings.json)
Edit settings.json at the repo root, or let Cursor fill it from screenshots. The DJ reloads it on every generate and chat.
{
"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
}
}
Example profile (Ozan): checked-in settings.json + taste_seeds.json — Anadolu psych + ethnic dubtronica from Spotify screenshots. Copy the structure for your own taste.
The player settings panel PATCHes playback and limits via /api/settings and writes back to this file.
Saved songs
Every track is written to ./songs/ and committed via Git LFS (audio) + plain git (metadata):
| File | Storage | Contents |
|---|---|---|
{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.
Quick start (Forge / Windows)
cd live-ozan-radio
python -m venv .venv
.venv\Scripts\activate
pip install -e .
copy .env.example .env
# Fill GEMINI_API_KEY + DEEPSEEK_API_KEY
# Taste: docs/TASTE-FROM-SCREENSHOTS.md (Cursor + Spotify screenshots)
$env:DEEPSEEK_BASE_URL = "https://api.deepseek.com/v1"
python -m ozan_radio serve
# Open http://127.0.0.1:8787/player
One-shot track (no server):
python -m ozan_radio generate
Environment
| Variable | Required | Notes |
|---|---|---|
GEMINI_API_KEY |
Yes | Google AI Studio — Lyria 3 |
DEEPSEEK_API_KEY |
Yes | Tinqs proxy token or DeepSeek direct |
DEEPSEEK_BASE_URL |
No | Default https://api.deepseek.com/v1 |
LYRIA_MODEL |
No | lyria-3-pro-preview (default) or lyria-3-clip-preview |
RADIO_OUTPUT_DIR |
No | Default ./songs |
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.
API
| Method | Path | Description |
|---|---|---|
| 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/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 |
Magenta RealTime 2 (optional live layer)
On Apple Silicon (Kraken), install magenta-rt for sub-second live generation:
uv pip install "magenta-rt[mlx]"
mrt models init && mrt models download
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.
Repo
Public on Git Studio: https://tinqs.com/tinqs/live-radio
git clone git@ssh.tinqs.com:tinqs/live-radio.git
cd live-radio
git lfs install && git lfs pull
Public auto-play (Git Studio / tinqs.com)
Share this link — opens in browser and starts shuffling saved tracks from Git LFS (no local server):
https://tinqs.com/tinqs/live-radio/src/branch/main/gateway/index.html
Click Preview or the expand icon on that file in Git Studio. If the browser blocks autoplay, tap Start Live Ozan Radio once.
After generating new tracks locally, refresh the public page:
python -m ozan_radio export-web
git add gateway/index.html gateway/playlist.json
git commit -m "Update public radio playlist"
git push
New generations auto-update gateway/index.html when the local server saves a track (queue.add).
Full dashboard (gateway/player.html or http://127.0.0.1:8787/player) still needs the FastAPI server for DJ chat, Lyria compose, and settings.
Agent usage
DeepSeek (Pi, Cursor, Claude Code) can operate the station via HTTP or the skill in .cursor/skills/ozan-radio/SKILL.md.
License
Apache 2.0 — same spirit as Magenta RealTime 2 open weights.