denpa-radio/README.md

3.4 KiB
Raw Permalink Blame History

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 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.