No description
  • Go 64.6%
  • HTML 22.6%
  • CSS 10.9%
  • Shell 1.1%
  • JavaScript 0.6%
  • Other 0.2%
Find a file
Arne Skaar Fismen 9adcde3066 Bump spotify profile to v2 (192 kbps re-encode) (#14)
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.
2026-06-29 09:50:26 +02:00
.direnv UI changes (#13) 2026-06-28 22:32:08 +02:00
data.fresh-1782635978/catalog UI changes (#13) 2026-06-28 22:32:08 +02:00
deploy feed: measure downloads at origin; drop OP3 prefix (ADR-0017) (#9) 2026-06-26 12:46:22 +02:00
design Katalog admin iteration: shows, persons, episodes, credits, master upload 2026-06-24 20:03:24 +02:00
docs Bump spotify profile to v2 (192 kbps re-encode) (#14) 2026-06-29 09:50:26 +02:00
internal Bump spotify profile to v2 (192 kbps re-encode) (#14) 2026-06-29 09:50:26 +02:00
.envrc chore: direnv dev shell with C compiler so cgo tests run (#12) 2026-06-28 10:16:56 +02:00
.gitignore catalog-transcoding-encoding-profiles (#7) 2026-06-26 12:28:36 +02:00
go.mod metadata_catalog (#3) 2026-06-22 15:29:59 +02:00
go.sum metadata_catalog (#3) 2026-06-22 15:29:59 +02:00
kokosbananas.db.fresh-1782635978 UI changes (#13) 2026-06-28 22:32:08 +02:00
main.go feed: measure downloads at origin; drop OP3 prefix (ADR-0017) (#9) 2026-06-26 12:46:22 +02:00
README.md Bump spotify profile to v2 (192 kbps re-encode) (#14) 2026-06-29 09:50:26 +02:00
shell.nix chore: direnv dev shell with C compiler so cgo tests run (#12) 2026-06-28 10:16:56 +02:00

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)

  1. Upload a WAV/FLAC master (streamed straight to disk).
  2. Measures it (EBU R128: integrated LUFS, true peak, LRA) via ffmpeg.
  3. 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).
  4. Spotify emulation — re-encodes the self-hosted file to Ogg Vorbis ~96 kbps, gained to 14 LUFS (how Spotify normalizes at playback).
  5. YouTube emulation — re-encodes to Opus 128 kbps, gained to 14 LUFS.
  6. 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).
  7. Tune & regenerate: change codec / bitrate / target loudness / true-peak ceiling; the emulations re-derive from your new self-hosted file (htmx swap).

Katalog (/katalog)

  1. 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.
  2. Upload lossless masters (WAV/FLAC) — content-hashed and deduped; immutable.
  3. 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/…)

  1. 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).
  2. 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).
  3. 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 loudnorm would 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).