ozan 76e12ace55 Curate library to two keeper tracks — Caravan and Desert Mirage.
Remove Sahara Moon, Dune Chant, and both dervish generations; sync manifest to the slimmed catalog.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 14:52:45 +01:00

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):

  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).

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

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

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.

S
Description
AI radio - DeepSeek DJ + Google Lyria 3
Readme 40 MiB
Languages
Python 57.8%
HTML 42.2%