80 lines
3.4 KiB
Markdown
80 lines
3.4 KiB
Markdown
# denpa-radio
|
||
|
||
a tiny independent multi-station internet radio. cassette-deck aesthetic, real audio, no algorithms.
|
||
|
||
live at **<https://denpa.femboy.page>**.
|
||
|
||
## what's playing
|
||
|
||
| station | description | mp3 | opus |
|
||
| --------- | ---------------------------------------- | --------------------------------------------------- | ----------------------------------------------------- |
|
||
| minecraft | C418, Lena Raine, Aaron Cherof — vol α+β | <https://denpa.femboy.page/minecraft.mp3> (192k) | <https://denpa.femboy.page/minecraft.opus> (96k) |
|
||
|
||
drop a stream URL into VLC, mpv, foobar, or any internet-radio app.
|
||
|
||
## endpoints
|
||
|
||
- `/<station>.mp3` — MP3 192 kbps stream
|
||
- `/<station>.opus` — Opus 96 kbps stream
|
||
- `/now-playing/<station>.json` — currently playing (artist, title, album, duration, started_at)
|
||
- `/now-playing/<station>.history.json` — last 50 tracks for the station
|
||
- `/api/stations.json` — list of all stations with metadata
|
||
- `/api/stations/<station>/cover` — cover art (when present)
|
||
- `/status-json.xsl` — raw Icecast status (listener counts, mount info)
|
||
|
||
CORS is open on all of the above; build your own scrobbler / dashboard / overlay.
|
||
|
||
## architecture
|
||
|
||
```
|
||
Caddy (host) ─┬─ /<station>.{mp3,opus}, /status-json.xsl → icecast (libretime/icecast)
|
||
├─ /now-playing/*.json → caddy file_server
|
||
├─ /api/* → frontend (astro 5 + react)
|
||
└─ / → frontend
|
||
▲
|
||
└─ liquidsoap (savonet/liquidsoap)
|
||
│ reads station folders
|
||
│ writes now-playing JSON
|
||
▼
|
||
Hetzner Storage Box (CIFS, mounted RO)
|
||
```
|
||
|
||
three docker containers (icecast, liquidsoap, frontend) and a Caddy host service. each station is a folder on the storage box with a `_meta.yml`, a `tracks/` subdir, and an optional `cover.{jpg,png,webp}`. the frontend reads `_meta.yml` files at request time, so adding a station does not require a rebuild.
|
||
|
||
## adding a station
|
||
|
||
on the storage box (mounted at `/mnt/trashbox/denpa-radio/library/` on summer):
|
||
|
||
```
|
||
mkdir -p <station-id>/{tracks,jingles}
|
||
cat > <station-id>/_meta.yml <<EOF
|
||
name: My Station
|
||
description: short description
|
||
color: '#5cae34'
|
||
tags: [tag1, tag2]
|
||
EOF
|
||
# drop audio files into <station-id>/tracks/
|
||
# subdirs supported: tracks/Volume Alpha/, tracks/Singles/, etc — Liquidsoap recurses
|
||
# (optional) drop cover.jpg into <station-id>/ (NOT inside tracks/ — would fail to decode)
|
||
```
|
||
|
||
then on summer:
|
||
|
||
```
|
||
# duplicate the existing minecraft station block in /srv/denpa-radio/config/liquidsoap.liq
|
||
# rename references to your new id, then:
|
||
docker compose -f /srv/denpa-radio/docker-compose.yml restart liquidsoap
|
||
```
|
||
|
||
the frontend picks up the new station within ~30s. no Caddy reload, no Icecast restart.
|
||
|
||
station ids must match `[a-z0-9-]+`.
|
||
|
||
## tags
|
||
|
||
- **v0.1.0** — radio backend live (Minecraft station, MP3 + Opus).
|
||
- **v0.2.0** — frontend live (cassette deck UI, real WebAudio spectrum, recently-played).
|
||
|
||
## working on this repo
|
||
|
||
see [`CLAUDE.md`](./CLAUDE.md) for the full operator's manual: SSH conventions, deploy quirks, frontend stack notes, Liquidsoap gotchas, and the things we've already learned the hard way.
|