ozan ec4ca4b7c0 Raise daily generation limit to 100 and fix limit UI coercion.
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>
2026-06-07 16:28:19 +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

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.

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