Casting about again

Except for a short period when its original developers were unable to keep up and the app suffered badly, Castro has been my podcast player for many years. Its triage system of managing podcast episodes fits well with how I listen. Unfortunately, Castro has been giving me trouble in recent weeks. It often fails to play through CarPlay. The progress bar moves along, but no sound comes out of my car’s speakers. The only way to fix this is to stop and relaunch Castro on my phone, which I can’t do safely while driving. It’s very frustrating.

I decided to use this as an opportunity to check out Apple’s Podcasts app. I used to do this every year or two to see if Apple had made it worthwhile and would immediately switch back to Castro—or Overcast or Downcast,1 depending on when the test was run—because Podcasts was just so awful. But it’s been a long time since my last experiment with it.

It should have been longer. While Podcasts has definitely improved, it demonstrated some outright bugs that I can’t live with. Most of these seem to come from my earlier use of it. For example, I couldn’t unsubscribe from a couple of podcasts that I had been subscribed to years ago. I’d choose Unfollow2 from the show’s context menu and nothing would happen. They were immovable. Similarly, the Downloads section of the app showed me several episodes that I couldn’t delete. Were those episodes actually present on my device, or was Podcasts just unable to delete their entries from its internal database? I have no idea, but it was really annoying to have shows from years ago continually appearing in the app.

These problems didn’t interfere with my ability to follow, download, and listen to new shows. If I didn’t have this history of trying out Podcasts, it’s entirely possible that I’d’ve been satisfied using it. But I just couldn’t live with them.

So I returned to Downcast, a player I used many years ago and also during that interregnum when Castro was undergoing a management change. It happily imported my subscriptions from Castro—something Podcasts apparently can’t do—and I was soon listening in my car with no hitches. I’ve since pruned the defunct podcasts that accumulated in Castro and organized what’s left into playlists of different topics. I miss the triage system, but I love being able to delete podcasts with a swipe—a standard piece of UI that Castro never adopted.

I mentioned above that I couldn’t find a way to get Podcasts to import another app’s OPML list of subscriptions. There are ways to do this through Shortcuts, but first you need a clean list of the subscription URLs. While it’s not hard to hand-edit an OPML file to strip away everything except the URLs, I wrote a very short Python script to do the job.

Here’s a shortened version of Castro’s OPML export of my subscription:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<opml version="2.0">
    <head>
        <title>Castro Podcasts OPML</title>
        <dateCreated>Sun, 16 Nov 2025 04:09:59 GMT</dateCreated>
    </head>
    <body>
        <outline text="Lions, Towers &amp; Shields" type="rss" 
            xmlUrl="https://feeds.theincomparable.com/lts" />
        <outline text="Mac Power Users" type="rss" 
            xmlUrl="https://www.relay.fm/mpu/feed" />
        <outline text="Upgrade" type="rss" 
            xmlUrl="https://www.relay.fm/upgrade/feed" />
        <outline text="99% Invisible" type="rss" 
            xmlUrl="https://feeds.simplecast.com/BqbsxVfO" />

        [etc]

    </body>
</opml>

And here’s the very short script that extracts the URLs:

python:
 1:  #!/usr/bin/env python3
 2:  
 3:  import xml.etree.ElementTree as et
 4:  import sys
 5:  
 6:  root = et.parse(sys.stdin).getroot()
 7:  
 8:  outlines = root.findall('./body/outline')
 9:  for o in outlines:
10:    print(o.attrib['xmlUrl'])

The ElementTree module is part of Python’s standard library and is very easy to use. Once you know about it and its use of XPATH expressions, the script practically writes itself. The advantage of using a script over hand-editing is that the script doesn’t make mousing or regex errors.

I ran the script this way:

python3 podcast-list.py < castro_podcasts.opml

and got this output:

https://feeds.theincomparable.com/lts
https://www.relay.fm/mpu/feed
https://www.relay.fm/upgrade/feed
https://feeds.simplecast.com/BqbsxVfO
[etc]

Although I doubt I’ll give Podcasts another try, I’ve learned to never say never. This script will help if I need that clean list of URLs again.


  1. I love punning names. 

  2. While I understand Apple’s preference for “follow/unfollow” over “subscribe/unsubscribe”—it’s natural for podcast newcomers to think subscribing means handing over money—that ship sailed long ago. And really, people are pretty flexible. The idea of a “free subscription” isn’t that hard to understand.