- Go 64.6%
- HTML 22.6%
- CSS 10.9%
- Shell 1.1%
- JavaScript 0.6%
- Other 0.2%
Bump spotify to v2 (libvorbis 192k/q6) so a re-transcode re-encodes the back catalog at 192 kbps; an in-place bitrate edit (ADR-0018) never re-encoded existing renditions. ADR-0020. |
||
|---|---|---|
| .direnv | ||
| data.fresh-1782635978/catalog | ||
| deploy | ||
| design | ||
| docs | ||
| internal | ||
| .envrc | ||
| .gitignore | ||
| go.mod | ||
| go.sum | ||
| kokosbananas.db.fresh-1782635978 | ||
| main.go | ||
| README.md | ||
| shell.nix | ||
Golten Stories
Self-hosted platform for audio programming for kids (Norwegian). Holds lossless masters as the source of truth, transcodes them into platform-tailored renditions, and distributes them as podcasts via RSS to Spotify and Apple Podcasts. A PWA for direct listening and stats tracking is planned but not yet built.
The domain model and decisions: docs/CONTEXT.md and
docs/adr/. Entity reference: docs/CATALOG.md.
Why these platforms and settings: docs/FINDINGS.md.
What it does
Lydlab (/tools/audio-preview-lab)
- Upload a WAV/FLAC master (streamed straight to disk).
- Measures it (EBU R128: integrated LUFS, true peak, LRA) via ffmpeg.
- Self-hosted rendition — your tunable "high-res" master (this is literally what Apple, Overcast, and Amazon/Audible listeners hear, since they don't re-encode).
- Spotify emulation — re-encodes the self-hosted file to Ogg Vorbis ~96 kbps, gained to −14 LUFS (how Spotify normalizes at playback).
- YouTube emulation — re-encodes to Opus 128 kbps, gained to −14 LUFS.
- Each rendition gets a post-encode measurement (LUFS + true peak — peaks above −1 dBTP are flagged) and an A/B player (playing one pauses the others).
- Tune & regenerate: change codec / bitrate / target loudness / true-peak ceiling; the emulations re-derive from your new self-hosted file (htmx swap).
Katalog (/katalog)
- Manage shows, episodes, persons, and credits — the publication admin surface. Seasons and Works are dormant in v1 (one per show/episode); the creator sees shows, episodes, persons, and credits.
- Upload lossless masters (WAV/FLAC) — content-hashed and deduped; immutable.
- Transcode per encoding profile — two versioned profiles (
main-v1,spotify-v2) seeded at startup. Synchronous stopgap; a job queue + worker are deferred (ADR-0005, ADR-0018).
Feeder (/feed/…)
- Generate PSP-1 RSS feeds per
(Show × Edition × Platform)— a pure projection of canonical metadata, rendered to disk and served as a static file (ADR-0015). - Per-platform enclosures — each feed encloses the rendition from its platform profile, served from our own origin; downloads are measured at the origin, not via a third-party prefix (ADR-0017, supersedes ADR-0008).
- Manual refresh — the operator clicks "Generer/Oppdater" on the show page. Automatic regeneration on catalog change is deferred (ADR-0004 phasing).
Stack
Go (stdlib only) · ffmpeg via subprocess · htmx + tiny vanilla-JS helpers · SQLite · local-disk storage. No npm, no TypeScript, no build step. Single binary; templates/assets embedded.
internal/audio ffmpeg/ffprobe adapter (probe, measure, encode, decode) ← ADR-0005
internal/catalog domain model & store interface (Show/…/Feed) ← ADR-0003
internal/catalog/sqlite SQLite implementation of the catalog store ← ADR-0009
internal/feed PSP-1 RSS renderer (pure function, golden-file tested) ← ADR-0004/0015
internal/store one dir per upload + meta.json on disk (preview lab) ← ADR-0009
internal/pipeline master → self-hosted → emulations; profile-driven Transcode
internal/web HTTP handlers + embedded templates/static
main.go wiring + startup ffmpeg check + catalog DB + profile seed
Run
ffmpeg/ffprobe must be on PATH (or set KOKOS_FFMPEG / KOKOS_FFPROBE).
# NixOS: brings go + ffmpeg into scope
nix-shell
# build is pure-Go (no cgo / no gcc needed)
CGO_ENABLED=0 go run . # serves http://127.0.0.1:8080
# or:
CGO_ENABLED=0 go run . -addr 127.0.0.1:9000 -data ./data
# with feed generation (base URL required for absolute enclosure/feed URLs):
CGO_ENABLED=0 go run . -base-url http://localhost:8080
# live-reload (watchexec from shell.nix): rebuilds + restarts on .go/.html/.css edits.
# templates and static assets are embedded, so a restart is required to see changes.
watchexec -r -e go,html,css -- CGO_ENABLED=0 go run . -addr 127.0.0.1:9000 -base-url http://localhost:8080
| Flag | Env | Default | Purpose |
|---|---|---|---|
-addr |
— | 127.0.0.1:8080 |
listen address |
-data |
— | data |
lab upload dir |
-db |
— | kokosbananas.db |
catalog SQLite path |
-catalog-data |
— | data/catalog |
catalog media dir (masters, renditions, feeds) |
-base-url |
KOKOS_BASE_URL |
(none) | public origin for RSS enclosure/feed URLs (required to generate feeds) |
| — | KOKOS_AUTH_USER / KOKOS_AUTH_PASS |
(none) | HTTP basic auth for /katalog + /tools; public routes (/feed, /renditions, /) stay open |
| — | KOKOS_FFMPEG / KOKOS_FFPROBE |
PATH | ffmpeg/ffprobe binary paths |
Then open the address: / is the landing page, /katalog is the admin,
/tools/audio-preview-lab is the lab.
Tests / checks
CGO_ENABLED=0 go build ./...
CGO_ENABLED=0 go vet ./...
CGO_ENABLED=0 go test ./...
Known limitations
- Loudness is matched with a single-pass static gain, so output can land ~1 dB off target.
Two-pass
loudnormwould tighten it — deferred. - Emulations are approximations, not bit-exact (we don't have the platforms' exact encoder builds). They reliably surface artifacts and post-encode true-peak clipping.
- Transcoding is synchronous on request; fine for short masters. A job queue comes later (ADR-0005 phasing).
- Feeds are regenerated manually — after a metadata or rendition change, click "Oppdater" on the show page. Automatic regeneration is deferred (ADR-0004 phasing).
- nb-NO only — the path structure is language-keyed, but only Norwegian editions are wired (ADR-0012).
- No PWA, no analytics — planned (ADR-0010/0007/0008).