No description
  • Go 68.4%
  • HTML 20.1%
  • CSS 9%
  • Nix 1.1%
  • Shell 0.9%
  • Other 0.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-06 11:46:14 +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 stats-page (#19) 2026-09-24 15:29:13 +02:00
design Katalog admin iteration: shows, persons, episodes, credits, master upload 2026-06-24 20:03:24 +02:00
docs stats-page (#19) 2026-09-24 15:29:13 +02:00
internal stats-page (#19) 2026-09-24 15:29:13 +02:00
.envrc Whole-site login via Pocket ID (OIDC), replacing basic auth (#16) 2026-09-16 13:16:07 +02:00
.gitignore Whole-site login via Pocket ID (OIDC), replacing basic auth (#16) 2026-09-16 13:16:07 +02:00
AGENTS.md nix: flake + NixOS module (container migration step 2) 2026-10-06 11:46:14 +02:00
flake.nix nix: flake + NixOS module (container migration step 2) 2026-10-06 11:46:14 +02:00
go.mod Whole-site login via Pocket ID (OIDC), replacing basic auth (#16) 2026-09-16 13:16:07 +02:00
go.sum Whole-site login via Pocket ID (OIDC), replacing basic auth (#16) 2026-09-16 13:16:07 +02:00
kokosbananas.db.fresh-1782635978 UI changes (#13) 2026-06-28 22:32:08 +02:00
main.go stats-page (#19) 2026-09-24 15:29:13 +02:00
module.nix nix: flake + NixOS module (container migration step 2) 2026-10-06 11:46:14 +02:00
README.md stats-page (#19) 2026-09-24 15:29:13 +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).
  4. Download statistics (/katalog/statistikk, authed) — renders the precomputed kloggos report (per-episode, per-week, per-platform origin fetches) fetched from the kloggos JSON API over the closed Incus bridge. kloggos owns data production; this app owns presentation. Degrades to a friendly page when kloggos is unreachable (ADR-0007).

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/stats      kloggos JSON API client (download statistics)           ← ADR-0007
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)
-kloggos-url KLOGOS_API_URL http://kloggos.incus:8080 kloggos JSON API base URL for /katalog/statistikk
— KLOGOS_API_TOKEN (none) optional Bearer token sent to the kloggos API (unused by kloggos today)
— KOKOS_OIDC_ISSUER (none) OIDC provider (e.g. https://tilgang.golten.as); when set, the whole site except /feed, /audio, /images requires login. Unset = no login (local dev)
— KOKOS_OIDC_CLIENT_ID / KOKOS_OIDC_CLIENT_SECRET (none) OIDC client credentials (secret optional for a public PKCE client)
— KOKOS_OIDC_REDIRECT_URL <base-url>/auth/callback callback URL registered at the provider
— KOKOS_SESSION_SECRET random per start HMAC key for session cookies; set it or everyone is logged out on restart
— KOKOS_FFMPEG / KOKOS_FFPROBE PATH ffmpeg/ffprobe binary paths

Then open the address: / is the landing page, /katalog is the admin, /katalog/statistikk is the download report (authed), /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).
  • Stats are read-only and sourced from kloggos (/katalog/statistikk): as fresh as kloggos' last report run, and the page degrades to a friendly note when kloggos is unreachable. The in-app analytics store (ADR-0007) and PWA (ADR-0010) are still deferred.