diff --git a/.env.example b/.env.example index 7987060..c033ba4 100644 --- a/.env.example +++ b/.env.example @@ -1,14 +1,14 @@ # radieo — configuration locale. Copier en `.env` et remplir. # docker compose lit automatiquement `.env` pour ces variables. -# --- Source OpenSubsonic (Navidrome, Gonic, Airsonic…) --- +# --- Source Navidrome / OpenSubsonic --- # URL de base de ton serveur (sans /rest). Laisser les champs vides désactive # la source : le stream joue alors uniquement les fichiers déjà dans cache/. -RADIEO_SUBSONIC_URL=https://subsonic.example.org -RADIEO_SUBSONIC_USER=monuser -RADIEO_SUBSONIC_PASSWORD=monmotdepasse +RADIEO_NAVIDROME_URL=https://navidrome.example.org +RADIEO_NAVIDROME_USER=monuser +RADIEO_NAVIDROME_PASSWORD=monmotdepasse # Nom OU identifiant de la playlist à diffuser. -RADIEO_SUBSONIC_PLAYLIST=Radio +RADIEO_NAVIDROME_PLAYLIST=Radio # --- Source yt-dlp --- # La liste d'URL se met dans config/urls.txt (copier config/urls.txt.example). @@ -18,12 +18,12 @@ RADIEO_SUBSONIC_PLAYLIST=Radio # Feed Atom de recommandations. URL http(s) du feed de syndication, ou chemin # local sous /config (ex. /config/recommendations.xml) pour tester. Vide = off. # ListenBrainz ne fait que *nommer* des morceaux : chacun est résolu vers -# la bibliothèque OpenSubsonic puis, à défaut, yt-dlp. +# Navidrome puis, à défaut, yt-dlp. RADIEO_LISTENBRAINZ_URL=https://listenbrainz.org/syndication-feed/user/monuser/recommendations/weekly-exploration # --- Dosage du mix entre sources (optionnel) --- # Poids relatifs de tirage de chaque source (0 désactive la source). -RADIEO_WEIGHT_SUBSONIC=3 +RADIEO_WEIGHT_NAVIDROME=3 RADIEO_WEIGHT_YTDLP=1 RADIEO_WEIGHT_LISTENBRAINZ=2 diff --git a/README.md b/README.md index b9cc519..aaadaf4 100644 --- a/README.md +++ b/README.md @@ -7,140 +7,145 @@ The goal is a hassle-free stream that always has something playing, where the next track is picked automatically. It is meant for personal use (a couple of simultaneous listeners), not for public broadcasting. -## Features +## How it works -- **Always-on stream**: MP3 at 192 kbps over HTTP, several simultaneous - listeners. -- **Automatic programming from mixable sources**, drawn at weighted random: - - a playlist from any [OpenSubsonic](https://opensubsonic.netlify.app/)-compatible - server ([Navidrome](https://www.navidrome.org/), Gonic, Airsonic…); - - a hand-maintained list of [yt-dlp](https://github.com/yt-dlp/yt-dlp) URLs - (Bandcamp, SoundCloud, YouTube…); playlist/album/label/artist URLs are - expanded and one track is picked at random each round; - - a [ListenBrainz](https://listenbrainz.org/) recommendations feed, whose - suggestions are resolved to a real file (Subsonic first, then yt-dlp). -- **Cross-source de-duplication**: each track is canonicalized to a MusicBrainz - recording MBID (no API key), so the same song from two sources collapses to - one; a recent-plays window prevents repeats. -- **Push-with-cache**: tracks are downloaded *ahead* of playback into a local - cache with LRU retention; if the pipeline ever runs dry the stream falls back - to the tracks already aired, and stays silent at a cold start rather than - looping. -- **Jingles**: station jingles inserted every two songs, plus time-of-day - jingles (noon, snack time, ...) played once when their time comes. -- **Smooth playback**: a 3 s crossfade between tracks. -- **Built-in web player** at `http://localhost:8000/`: now playing (linked to - its source page), track history, skip button, per-track download, volume - memory, live auto-reconnect, prefetch progress, and a synthwave look. -- **Robust in a container**: Docker healthcheck, graceful shutdown, retries on - transient HTTP errors. +radieo is built as two layers, each running in its own Docker container and +sharing a cache volume: -## Getting started +- **`ingest`** (Python) — the brain. It decides what to play next, resolves and + downloads tracks into a local cache, keeps a pre-filled queue, and exposes the + next track over HTTP at `GET /next`. *(currently it only serves the cache + directory; the download providers come in later milestones — see roadmap)* +- **`stream`** (Liquidsoap) — deliberately dumb. It pulls the next track from + the `ingest` daemon, broadcasts the audio over HTTP, and falls back to the + already-aired tracks (via `/fallback.m3u`) if the daemon has nothing ready. + +Playback sources (planned): a [Navidrome](https://www.navidrome.org/) library +via the OpenSubsonic API, arbitrary tracks fetched with +[yt-dlp](https://github.com/yt-dlp/yt-dlp) (Bandcamp, SoundCloud, YouTube…), and +listening suggestions from a ListenBrainz RSS feed. + +## Usage Requirements: Docker with Compose v2. -### 1. Jingles (optional) +```sh +# Drop some .mp3 files into the cache directory +cp /path/to/music/*.mp3 cache/ -Drop `.mp3` files into `jingles/`: they rotate in every two songs. For -time-of-day jingles, add files to these subfolders (each played once per day -just after its slot): +# Build and start the stream +docker compose up -d -| Folder | Plays around | -| ----------------- | ------------ | -| `jingles/midi/` | 11:00 | -| `jingles/moment/` | 15:00, 21:00 | -| `jingles/gouter/` | 16:30 | +# Listen (VLC, a browser, any audio player) +# http://localhost:8000/radio.mp3 +``` -An empty folder simply means no jingle, the music plays through. +Stop it with `docker compose down`. -### 2. Configure the sources (`.env`) +The stream is MP3 at 192 kbps. Multiple clients can listen at the same time. +New files dropped into `cache/` are picked up automatically (the playlist is +reloaded when the directory changes). + +## Configuration + +Copy `.env.example` to `.env` and fill in your Navidrome details: ```sh cp .env.example .env +# edit .env: RADIEO_NAVIDROME_URL / USER / PASSWORD / PLAYLIST ``` -Fill in `.env`: +If the Navidrome variables are left empty, the source is simply disabled and +the stream plays whatever is already in `cache/` (the milestone-1/2 behaviour). -- **OpenSubsonic server**: `RADIEO_SUBSONIC_URL` / `USER` / `PASSWORD` and the - playlist to broadcast in `RADIEO_SUBSONIC_PLAYLIST` (name or id). Works with - any OpenSubsonic-compatible server (Navidrome, Gonic, Airsonic…). Leave empty - to disable this source. -- **Mix**: `RADIEO_WEIGHT_SUBSONIC` / `RADIEO_WEIGHT_YTDLP` / - `RADIEO_WEIGHT_LISTENBRAINZ` set the relative draw weight of each source - (`0` disables one). -- Optional: `RADIEO_RETENTION_KEEP` (cached tracks kept on disk), - `RADIEO_CANONICAL_ENABLED`, `RADIEO_USER_AGENT`. +For the yt-dlp source, list the URLs to draw from in `config/urls.txt` (copy +`config/urls.txt.example`). Each line is either a direct track URL or a +container URL (playlist, album, label, artist page) from which one track is +picked at random. -### 3. yt-dlp URL list (`config/urls.txt`) +For the ListenBrainz source, set `RADIEO_LISTENBRAINZ_URL` to your +recommendations feed (the Atom syndication URL, e.g. +`https://listenbrainz.org/syndication-feed/user//recommendations/weekly-exploration`, +or a local file path under `config/` for testing). ListenBrainz only *names* +tracks, so each suggestion is resolved to a real file: Navidrome first +(a `search3` lookup), then yt-dlp (`ytsearch1:`) as a fallback. The +MusicBrainz recording MBID that the feed already carries is used as the +track's canonical identity (no extra lookup needed). -```sh -cp config/urls.txt.example config/urls.txt -``` +The relative mix between sources is set by `RADIEO_WEIGHT_NAVIDROME` / +`RADIEO_WEIGHT_YTDLP` / `RADIEO_WEIGHT_LISTENBRAINZ` (a weight of 0 disables a +source); an empty URL / missing file also disables the corresponding source. -Add one URL per line: a single track, or a playlist/album/label/artist page to -pick from. The file is mounted read-only, so you can edit it without rebuilding. -A missing file just disables the yt-dlp source. +Open the player at `http://localhost:8000/` — a small web page with an +`