Radio Archive → Podcast Feed
Build a private, subscribable podcast feed from a radio-show archive.
Sometimes a broadcaster has made a great radio show, but hasn't provided an podcast feed for it. For example the only way to listen to How To Think About Science, Part 1–24 from CBC.ca is to play each episode from the CBC.ca website. This tool is designed to let you quickly and easily create a feed for these orphaned radio shows so you can listen to them as a regular podcast.
In the best case, this tool can provide a feed URL that can be added directly to a podcast client, without needing to host the feed yourself. This is possible if the original audio files are accessible to the podcast client. In some-cases however, the original audio files may need to be re-hosted privately in order to work-around content blocking restrictions from the original publisher.
Currently built for CBC Radio archives (e.g. Ideas), whose pages expose clean schema.org AudioObject metadata.
Workflow
- Open Install a grabber, pick the grabber for your site (currenlty CBC only) and drag it to your bookmarks bar (or copy the script for the DevTools Console if your browser blocks bookmarklets).
- On each episode page: press Play (so the grabber can spot the loaded audio), click the grabber bookmark, then Download metadata.
- In the feed builder, drop in the downloaded
.jsonfiles, fill in the feed title/description/cover - When you are done, try using the
Copy feed as URLbutton and pasting this URL into your favorite podcast player. - If the podcast loads successfully, then enjoy! If not, then the most likely issue is that the original audio files are not being served reliably to the podcast client from the broadcaster's website. To solve for this, use the grabber to download each audio file and then host copies of each file at a URL of your choice. All audio files should sit in the same folder. Add this URL to the tool and then regenerate the feed.
Optional: if you are re-hosting the audio, then you can also choose to host the feed file too. Download the .xml and add this to the same folder as the hosted audio files.
How Copy feed as URL works
This feature compresses the whole feed (gzip → base64url) into a self-hosting link served by a bundled Netlify edge function:
/feed?d=<base64url(gzip(xml))>&name=<feed-name>
The function decodes it and returns a real application/rss+xml document, so you can paste the URL straight into a podcast app with no need to host the .xml elsewhere. XML compresses ~15–20×, so a 24-episode feed is well under 2 KB of URL; the builder warns if a feed grows past a safe URL length. (This removes XML hosting only — the <enclosure> audio URLs must still resolve to reachable files.)
The sidecar format
Each .json the grabber saves looks like:
{
"_tool": "radio-archive-rss",
"_schema": 1,
"title": "How To Think About Science - Part 1",
"series": "How To Think About Science",
"part": 1,
"description": "How to Think About Science begins with a conversation with Simon Schaffer…",
"date": "2010-01-13T15:34:00.000Z",
"durationSeconds": 3240,
"sourceId": "1.464989",
"mediaId": "1479821350",
"image": "https://i.cbc.ca/ais/1.464989,…/default.jpg",
"pageUrl": "https://www.cbc.ca/player/play/audio/1.464989",
"embedUrl": "https://www.cbc.ca/i/phoenix/player/syndicate/?sourceId=1.464989",
"audioUrl": "https://podcast.cbc.ca/mp3/ideas_20100113_12345.mp3",
"audioFilename": "ideas_20100113_12345.mp3"
}
audioUrl is the file's URL on the source domain and audioFilename is its original name there. Podcast apps require absolute <enclosure> URLs, so the builder uses, in order: a Feed folder URL + audioFilename if you set one (self-hosting), else the absolute audioUrl (link the original source directly), else the bare file name (only valid when the feed is served as a static .xml beside the audio). You can also hand-write or edit these files — the builder accepts a single object or an array, dropped or pasted.
When the grabber can't find a direct file (common on CBC, which streams via HLS — see below), audioUrl is left empty and audioFilename falls back to a slug of the title; set a Feed folder URL and host your own file under that name.
Notes & limitations
- The grabber is CBC-tuned. Its core reads the
schema.orgAudioObjectstandard, so it falls back to a best-effort generic mode on other sites that publish the same data — but only CBC is tested. Extraction is organised as a small registry of site adapters inbookmarklet.js, so support for another broadcaster (BBC, NPR, …) is a one-adapter addition rather than a rewrite. - Audio is best-effort. When a page only exposes a streaming manifest (HLS) rather than a single file, the grabber can't save it as one MP3 — it tells you so. You then supply the audio yourself (e.g. recorded/downloaded separately) under the same file name. The reliable part is the metadata; the feed is valid either way.
enclosure lengthis omitted when the file size isn't known (rather than a misleadinglength="0"), which is widely tolerated by podcast apps.- The builder de-duplicates by source/page/title, sorts by "Part N" then date, and remembers your work in
localStorage. - Private feed. The builder sets
<itunes:block>Yes</itunes:block>by default (toggle in Feed details), keeping these personal feeds out of public podcast directories. It doesn't affect adding the feed by URL. - Respect each source's terms of use and copyright when redistributing audio.
Known issues
Overcast shows no episodes (when enclosures point straight at CBC)
A feed whose <enclosure>s link directly to CBC's audio host (mp3.cbc.ca) loads and plays correctly in Apple Podcasts, Pocket Casts, but Overcast subscribes to the show yet lists no episodes.
What we established while debugging this:
- The feed is valid — the strict parsers (Apple, Pocket Casts) accept it, and an episode pointed at an unrelated public MP3 does appear in Overcast, so it isn't the feed structure, headers, content-type, compression, or feed-URL length.
- CBC serves the MP3 fine to a normal device/connection (
200, with range support). - The episodes are dropped only when the enclosure is the CBC host.
The most likely explanation is how Overcast fetches feeds: it crawls feeds and "downloads episodes to collect metadata" server-side, from its own infrastructure, whereas Apple/Pocket Casts/Castro fetch the audio on the device. CBC's audio host appears to refuse Overcast's server-side request (it works from your own device), so Overcast can't collect the episode and hides it. Serving the feed uncompressed and marking it private (itunes:block) were both tried and neither helped — consistent with the problem being the audio host, not the feed.
Workarounds:
- Use a client that fetches on-device — Apple Podcasts, Pocket Casts (verified working).
- Or re-host the audio on a server/CDN that isn't restricted: download the MP3s (your own connection works), upload them somewhere public, then set the builder's Feed folder URL to that location and regenerate. Self-hosted enclosures work in every app, Overcast included.
Possible future improvements
Automated audio re-hosting (to R2 / S3-compatible storage)
Re-hosting is currently manual (download the MP3s, upload them somewhere, set the Feed folder URL). A future version could automate it: given the audio URLs already captured in the sidecars, fetch each MP3 and re-upload it to object storage such as Cloudflare R2 (S3-compatible, supports range requests, free egress), then point the feed's enclosures at the re-hosted copies. This would make feeds work everywhere — including Overcast — without manual file wrangling.
Two constraints shape the design:
- The browser can't fetch the audio. CBC sends no CORS headers, so the static tool can't read the MP3 bytes — the fetch has to happen outside the browser.
- Credentials can't live in a static page, so R2 keys belong in a local script or a serverless function's environment, never in the client.
- The fetcher's network matters — CBC serves your own connection but may refuse datacenter IPs (the likely cause of the Overcast issue above), so any server-side fetcher needs verifying, not assuming.
A phased approach:
- "Copy re-host script" — the builder emits a shell script (
curleachaudioUrl, thenrclone/aws s3 cpto R2) that you run on your own connection. No new infrastructure, no IP-block risk, credentials stay local. Then set the Feed folder URL to the R2 public base and regenerate. - One-click ingest via a Cloudflare Worker + R2 — a token-protected Worker fetches each MP3 and streams it into R2 (or lazily caches on first request), so the builder can re-host with a button. Gated on a quick probe of whether Cloudflare's egress can reach CBC; if not, fall back to (1).
- Polish — capture
Content-Lengthduring ingest to emit a correct<enclosure length>, skip already-uploaded files, show per-episode progress, and generalise the uploader to any S3-compatible host (R2/S3/B2).
Tech
Pure HTML/CSS/vanilla JS on the shared design system. No build step, no dependencies. The bookmarklet (bookmarklet.js) is self-contained and also pasteable into the console.