J.T. Worldteacher: X Spaces to Podcast Pipeline
Back to Projects
Active Case study Featured

J.T. Worldteacher: X Spaces to Podcast Pipeline

Astro TypeScript Whisper RSS Cloudflare R2 GitHub Actions Claude Code

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.