Milestone 1: Liquidsoap broadcasting skeleton
Liquidsoap (v2.4.5) container that plays the /cache directory in random order and broadcasts it over HTTP at :8000/radio.mp3 (MP3 192 kbps). mksafe guarantees a continuous stream (silence when the cache is empty). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
commit
29ab0be7cb
6 changed files with 129 additions and 0 deletions
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Fichiers audio et état du cache (ne pas versionner)
|
||||
/cache/*
|
||||
!/cache/.gitkeep
|
||||
|
||||
# État de l'ingestion (jalons suivants)
|
||||
*.db
|
||||
*.db-journal
|
||||
*.db-wal
|
||||
|
||||
# Python (jalons suivants)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.venv/
|
||||
73
README.md
Normal file
73
README.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# radieo
|
||||
|
||||
A personal music radio: an always-on HTTP audio stream, automatically fed from
|
||||
several sources and broadcast with [Liquidsoap](https://www.liquidsoap.info/).
|
||||
|
||||
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.
|
||||
|
||||
## How it works
|
||||
|
||||
radieo is built as two layers, each running in its own Docker container and
|
||||
sharing a cache volume:
|
||||
|
||||
- **`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. *(planned — see roadmap below)*
|
||||
- **`stream`** (Liquidsoap) — deliberately dumb. It broadcasts the audio over
|
||||
HTTP and never goes silent thanks to a local fallback.
|
||||
|
||||
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.
|
||||
|
||||
```sh
|
||||
# Drop some .mp3 files into the cache directory
|
||||
cp /path/to/music/*.mp3 cache/
|
||||
|
||||
# Build and start the stream
|
||||
docker compose up -d
|
||||
|
||||
# Listen (VLC, a browser, any audio player)
|
||||
# http://localhost:8000/radio.mp3
|
||||
```
|
||||
|
||||
Stop it with `docker compose down`.
|
||||
|
||||
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).
|
||||
|
||||
## Current status
|
||||
|
||||
**Milestone 1 — broadcasting skeleton: done.**
|
||||
|
||||
- Liquidsoap (v2.4.5) container plays the `cache/` directory in random order.
|
||||
- HTTP stream served at `http://localhost:8000/radio.mp3` (MP3, 192 kbps).
|
||||
- Continuous output guaranteed: silence rather than a crash when the cache is
|
||||
empty (`mksafe`).
|
||||
- Multiple simultaneous listeners supported.
|
||||
|
||||
At this stage the playlist is filled manually; the automatic ingestion layer is
|
||||
not implemented yet.
|
||||
|
||||
## Roadmap
|
||||
|
||||
1. ✅ **Broadcasting skeleton** — Liquidsoap serving the cache directory.
|
||||
2. **Ingestion daemon** — Python daemon exposing `GET /next`; Liquidsoap
|
||||
switches to a `request.dynamic` source with the cache as fallback.
|
||||
3. **Navidrome provider** — play from an OpenSubsonic playlist, with caching,
|
||||
LRU retention and play history.
|
||||
4. **yt-dlp provider** — fetch tracks from a maintained URL/artist list; weighted
|
||||
mixing between sources.
|
||||
5. **Canonicalizer** — ListenBrainz MBID lookup for source-agnostic
|
||||
de-duplication.
|
||||
6. **ListenBrainz provider** — parse the RSS suggestions feed and resolve each
|
||||
one to Navidrome or yt-dlp.
|
||||
7. **Polish** — crossfade, robustness, optional web player, config file.
|
||||
0
cache/.gitkeep
vendored
Normal file
0
cache/.gitkeep
vendored
Normal file
9
docker-compose.yml
Normal file
9
docker-compose.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
services:
|
||||
stream:
|
||||
build: ./stream
|
||||
image: radieo-stream
|
||||
ports:
|
||||
- "8000:8000" # flux HTTP : http://localhost:8000/radio.mp3
|
||||
volumes:
|
||||
- ./cache:/cache:ro # jalon 1 : lecture seule, rempli à la main
|
||||
restart: unless-stopped
|
||||
5
stream/Dockerfile
Normal file
5
stream/Dockerfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
FROM savonet/liquidsoap:v2.4.5
|
||||
|
||||
COPY radio.liq /etc/liquidsoap/radio.liq
|
||||
|
||||
CMD ["/etc/liquidsoap/radio.liq"]
|
||||
29
stream/radio.liq
Normal file
29
stream/radio.liq
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/liquidsoap
|
||||
|
||||
# radieo — couche diffusion (jalon 1)
|
||||
# Joue le dossier /cache en boucle aléatoire et le diffuse en HTTP.
|
||||
# Les jalons suivants remplaceront la source par un request.dynamic piloté
|
||||
# par le daemon d'ingestion, en gardant ce dossier comme secours.
|
||||
|
||||
# --- Journalisation : tout sur la sortie standard (pratique en conteneur) ---
|
||||
settings.log.stdout := true
|
||||
settings.log.file := false
|
||||
settings.log.level := 3
|
||||
|
||||
# --- Harbor : écoute sur toutes les interfaces du conteneur ---
|
||||
settings.harbor.bind_addrs := ["0.0.0.0"]
|
||||
|
||||
# --- Source : le dossier de cache, rechargé quand son contenu change ---
|
||||
radio = playlist(mode="randomize", reload_mode="watch", "/cache")
|
||||
|
||||
# mksafe garantit un flux continu : si la source échoue ou est vide,
|
||||
# Liquidsoap émet du silence plutôt que de planter.
|
||||
radio = mksafe(radio)
|
||||
|
||||
# --- Sortie : flux MP3 sur http://<hote>:8000/radio.mp3 ---
|
||||
output.harbor(
|
||||
%mp3(bitrate=192),
|
||||
port=8000,
|
||||
mount="radio.mp3",
|
||||
radio
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue