From a live X Space to a listed podcast episode with show notes, using local tools and a free media tier.
A pipeline and Astro site that turn J.T. Worldteacher's recorded X Spaces into a published podcast: download, local Whisper transcription, agent-written show notes, Cloudflare R2 audio hosting, and a validated RSS feed deployed to GitHub Pages.
- episodes published
- 24
- episodes published
- of audio transcribed and hosted
- ~66 h
- of audio transcribed and hosted
- from git push to live episode in the feed
- ~1 min
- from git push to live episode in the feed
- commits with a Claude co-author trailer
- 15 / 24
- commits with a Claude co-author trailer
The problem
J.T.'s "Astrology & Simulation Theory" show happens as live X Spaces, which are hard to find or replay once they're over. The goal was to turn each recorded Space into a permanent podcast episode with full show notes, published to a standard RSS feed, with as little manual work per episode as possible.
Constraints
- GitHub Pages can't serve large media, so audio lives on Cloudflare R2 behind its own media subdomain. That lets the storage host change later without changing any episode URL.
- Apple Podcasts requirements: 3000x3000 cover art under 500 KB, an exact byte count for every audio file, the audio/mp4 type, and audio and feed URLs that never change.
- A Space can only be downloaded if the host recorded it.
- Episodes are long. The longest runs about 7.2 hours, and the show-notes step is skipped for transcripts over 500 KB.
What I built
- Download with yt-dlp, which also writes a catalog stub for each Space. A parallel pipeline runs 4 downloads at once while transcription runs one file at a time.
- Local transcription with whisper.cpp (large-v3-turbo on Apple Silicon) after converting to 16 kHz mono, with no transcription API. ffmpeg trims hold music and dead air, and the caption timings are shifted to match.
- A Claude Code skill (add-episode) that dispatches an agent to write show notes from the transcript.
- Episode pages in an Astro content collection checked by a Zod schema, with byte counts and durations filled in by ffprobe. Audio is uploaded to R2 with rclone and served as audio/mp4.
- An RSS endpoint with the iTunes, content, Atom, and Podcasting 2.0 namespaces, including podcast:guid, podcast:locked, and full show notes in content:encoded.
- A GitHub Actions workflow that builds the site and deploys it to GitHub Pages on every push.
How it's verified
- npm run build validates every episode against the schema and regenerates the feed, and CI runs the same build before deploying.
- The feed was checked with the podba.se validator. Its only failure, byte-range support, was confirmed as a false negative: a curl range request to R2 returns 206 Partial Content.
- Release checklist: confirm the public audio URL returns 200 with the right Content-Type and Content-Length and a 206 for a range request, and don't push if that check fails.
- A timezone regression test for episode publish dates, and 28 Python tests for a separate distribution-kit script.
Results
- 24 episodes, about 66 hours of audio, are live at jtworldteacher.com with a validated RSS feed.
- Adding an episode is a single skill run plus a push. The site and feed update in about a minute.
- Six earlier episodes still carry placeholder show notes and are next in line for the notes step.
TODO (Matt): numbers to add
- TODO: Listener or download numbers, and which podcast directories list the feed.
How coding agents fit in
- The workflow is packaged as a Claude Code skill, so one agent run takes a Space from URL to transcript, notes, upload, and a ready-to-push episode, stopping at the audio check before anything goes live.