Skip to content

priithaamer/podcasts

Repository files navigation

podcasts

A small HTTP service that exposes radio shows as podcast RSS feeds. Each station has its own adapter; the included one scrapes IDA Radio. Audio is downloaded with yt-dlp on first request and cached on disk, so subsequent listens are instant.

Stack

TypeScript, Koa, yt-dlp, ffmpeg. No database — episode metadata is cached in memory (30 min TTL); audio files live on disk under data/audio/.

Routes

  • GET / — lists configured feeds
  • GET /feeds/{showId}.xml — RSS feed for a show
  • GET /audio/{showId}/{episodeId}.mp3 — cached MP3 (downloaded on first hit)

Configuration

Environment variables, all optional:

Var Default Notes
PORT 3000 HTTP port
STORAGE_DIR ./data/audio Where downloaded MP3s are cached
PUBLIC_BASE_URL http://localhost:${PORT} Used in feed enclosure URLs — set in prod

Local development

Requires Node 22+, Yarn 4, yt-dlp, and ffmpeg on PATH.

yarn install
yarn dev          # tsx watch
# or
yarn build && yarn start

Then visit http://localhost:3000/.

Adding a show

For an existing adapter, append an entry to src/shows.ts:

{ id: 'my-show', adapter: idaRadioAdapter, sourceId: 'my-show' }

The id is what appears in feed URLs; sourceId is the slug on the source site (e.g. lets-play-house for idaidaida.net/shows/lets-play-house).

Adding a station

Implement the Adapter interface in src/adapters/types.ts and register your adapter instance in src/shows.ts. Each adapter returns Show metadata and a list of Episodes, where each episode points to an audio URL that yt-dlp can download (SoundCloud, Mixcloud, Bandcamp, direct MP3, etc.).

See src/adapters/ida-radio.ts for a full example.

Docker image

The GitHub Action publishes ghcr.io/priithaamer/podcasts on every push to main and on v* tags.

Tags published:

  • latest — tip of main
  • main, sha-<short> — every push
  • v1.2.3 — semver tags

The image bundles yt-dlp (latest static binary) and ffmpeg.

If you want the package public, after the first run go to the repo's Packages → podcasts → Package settings and switch visibility.

Deploying on Synology

  1. Reverse proxy. In Control Panel → Login Portal → Advanced → Reverse Proxy, point https://podcasts.example.comhttp://localhost:3000 (or whatever host port you choose).

  2. Project folder. Via SSH or File Station:

    mkdir -p /volume1/docker/podcasts/data

    Copy docker-compose.yml into /volume1/docker/podcasts/ and edit PUBLIC_BASE_URL to match your reverse-proxy hostname.

  3. (Private package only.) Authenticate to GHCR on the NAS:

    echo <GHCR_PAT_WITH_read:packages> | sudo docker login ghcr.io -u priithaamer --password-stdin
  4. Deploy. In Container Manager → Project → Create:

    • Project name: podcasts
    • Path: /volume1/docker/podcasts
    • Source: "Use existing docker-compose.yml"
    • Click Build

    Or from SSH:

    cd /volume1/docker/podcasts
    sudo docker compose pull && sudo docker compose up -d
  5. Updates. When the Action publishes a new :latest, redeploy:

    sudo docker compose pull && sudo docker compose up -d

    In Container Manager: Project → podcasts → Action → Build.

Downloaded audio lives at /volume1/docker/podcasts/data/audio/ — browseable in File Station and persistent across container rebuilds.

What survives a restart

  • Audio files — yes, on the /data volume.
  • Episode metadata cache — no, it's in memory. The first feed request after restart re-scrapes the source page (~1s) and re-populates it.
  • Episode IDs — stable (slugs from the source), so cached MP3s match up with new feed entries automatically.

About

My podcasts generator feed.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors