AI Radio is a tool that uses AI to convert your Music Assistant playlists to a radio station.
This project takes a source playlist, injects AI-generated moderator sections (intros, transitions, weather, news, fun bits), converts them to speech, and publishes a fresh target playlist in Music Assistant. Alternatively, it can run in dynamic generation mode, where sections are generated on the fly and queued for immediate playback without creating a target playlist.
- 5-step pipeline with clear boundaries and debug output
- Music Assistant integration via
music-assistant-clientSDK - Rule-based section scheduling (
MUST,ALTERNATIVE,OPTIONAL+ guards) - Dynamic generation mode with live queue updates (
--dynamic-generation) - Optional weather context (Open-Meteo)
- Optional news context with OpenAI web search
- OpenAI or ElevenLabs TTS generation for all sections
- Optional cover image generation per section (OpenAI Images)
- Optional HTTP daemon to trigger runs via API (
dynamic_daemon.py)
-
step1_connect.pyConnect to Music Assistant and verify access. -
step2_gather_playlist.pyLoad source playlist + tracks (optional random subset bymax_duration). -
step3_generate_sections.pyEvaluatesection_order, resolve placeholders, fetch optional context, generate section text. -
step4_tts_sections.pyConvert text to MP3, set metadata, embed covers. -
step5_update_playlist.pySync section provider, create target playlist, add tracks + generated sections.
main.py orchestrates all steps.
flowchart LR
A[Config YAML + .env] --> B[Step 1 Connect]
B --> C[Step 2 Gather Playlist]
C --> D[Step 3 Plan and Generate Sections]
D --> E[Step 4 TTS + ID3 + Covers]
E --> F[Step 5 Sync + Publish Playlist]
main.py: pipeline orchestratorstep1_connect.py...step5_update_playlist.py: pipeline stepsstep_generate_covers.py: one-shot section cover generationdynamic_daemon.py: HTTP daemon to start/stop dynamic generation runslib/: shared modules (ma_client, providers, helpers)config/sample_config.yaml: fully documented reference config.tmp/: runtime artifacts (ignored by git)
- Python 3.12+
- A Music Assistant instance with API access
- OpenAI API key for LLM (and optional images/news search)
- TTS API key for selected provider (
OPENAI_API_KEYorELEVENLABS_API_KEY) - A way to share generated MP3 files with Music Assistant (e.g. local filesystem, SMB/NFS share, etc.)
- Create and activate virtual environment
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt- Create
.envin repository root
OPENAI_API_KEY=...
OPENAI_ADMIN_KEY=... # optional (cost endpoint)
ELEVENLABS_API_KEY=... # optional (only if TTS provider is ElevenLabs)
MUSIC_ASSISTANT_API_KEY=...- Create local config
cp config/sample_config.yaml config/local_config.yaml- Edit
config/local_config.yaml
- set Music Assistant
base_url - set source
playlist_id - set local
section_store_path - set provider ids/domains for your MA setup
- Ensure Music Assistant can read generated section files
- MA must have filesystem access to
general.section_store_path(or the same share/path exposed by your MA filesystem provider). - Recommended: run this pipeline on the same host as MA, or use a shared SMB/NFS path mounted on both systems.
- Why: step 5 triggers MA sync, then searches MA library for indexed section tracks and adds them by URI (not by your local host file path).
Run full pipeline:
python3 main.py -c config/local_config.yamlRun from a specific step:
python3 main.py -c config/local_config.yaml --from-step 3Generate covers only:
python3 main.py -c config/local_config.yaml --generate-covers-onlyStep 5 billing check only:
python3 step5_update_playlist.py -c config/local_config.yaml --only-oai-checkDynamic generation mode (no target playlist creation, queue playback directly):
python3 main.py -c config/non_techno_playlist.yml --dynamic-generation 1 --playback-device ap8aedae8231f2Dynamic daemon (HTTP start/stop for dynamic mode):
python3 dynamic_daemon.py --port 8787 --auth-token YOUR_TOKENStart dynamic generation:
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://127.0.0.1:8787/start-dynamic?playback-id=ap8aedae8231f2&generate_count=1&config=config/non_techno_playlist.yml"Stop current dynamic run:
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://127.0.0.1:8787/stop-dynamic"All options are documented in config/sample_config.yaml.
Important groups:
general: naming, timezone, host style prompt, output pathsproviders: credentials + provider-specific behaviorsections: generated content blocks (ai_text) and optional merger (ai_meta)section_order: placement rules and guard constraints
Per section, you can define:
name: human-readable metadata title basecover_image: image filename located ingeneral.covers_path
For the target playlist, you can define:
general.playlist_cover_image: playlist cover image (filename, absolute path, or URL)
For merged multi-section outputs (multi_*), the system falls back to the ai_meta section cover/name when available.
Target playlist:
AI Radio: <general.name> (<weekday>. <dd.mm.>) [<run_id>]
Generated section track metadata:
- title:
<sections[*].name> [<run_id>] - artist:
AI Radio
.envis loaded automatically from repository root.- Runtime artifacts are written to
.tmp/. - If a section cover is missing, MP3 generation still succeeds.
- This project is an independent community project and is not affiliated with, endorsed by, or officially connected to the Music Assistant project or the Open Home Foundation.
- "Music Assistant", "Open Home Foundation", and related names, logos, and marks are the property of their respective owners.
- Any reference to third-party names/logos in documentation or examples is for identification, compatibility, or commentary purposes only (nominative fair use/fair usage context).
