Podcasts for Shockz OpenSwim
I absolutely love my swimming headphones. There’s just something about being able to get in the pool with a podcast on and zone out for an hour – chef’s kiss.
But they do have a bit of a setup problem, because they don’t have any nice syncing features, which means any audio you want to put on them needs to be in MP3 format. I can handle this for my MP3 music, because I have albums on my computer, but for podcasts I still use an app.
I found out Pocket Casts lets you export an OPML file of all your podcasts, so I made a short script that uses that file to create a super simple CLI for downloading podcasts to my Shockz, so I can just plug them in and download the podcasts directly to them.
#!/usr/bin/env python3
import xml.etree.ElementTree as ET
import feedparser
import requests
from time import strftime
import questionary
# Configuration
OPML_FILE = "/root/feeds/pods.opml"
def parse_opml(file):
feeds = {}
tree = ET.parse(file)
for outline in tree.findall(".//outline"):
url = outline.attrib.get("xmlUrl")
if url:
title = outline.attrib.get("title", outline.attrib.get("text", url))
feeds[title] = url
return feeds
def sanitize_filename(name):
return "".join(c for c in name if c.isalnum() or c in " ._-").rstrip()
def download(url, outpath):
try:
r = requests.get(
url,
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
},
stream=True,
timeout=30,
)
r.raise_for_status()
with open(outpath, "wb") as f:
for chunk in r.iter_content(1024 * 128):
f.write(chunk)
return {"success": True}
except requests.RequestException as e:
print("Failed:", url, str(e))
return {"success": False, "expected_size": 0, "actual_size": 0}
def get_ep_url(entry):
if entry["links"]:
audio_url = [x for x in entry["links"] if "audio" in x.get("type", "")][0].get(
"href"
)
return audio_url
def main():
while True:
feeds = parse_opml(OPML_FILE)
key = questionary.select("Which podcast?", choices=list(feeds)).ask()
if key is None:
break
feed_url = feeds[key]
parsed = feedparser.parse(feed_url).entries
ep_names = [
f"{strftime('%Y-%m-%d', x['published_parsed'])} - {x['title']}"
for x in parsed
]
episode_names = questionary.checkbox("Select episodes", ep_names).ask()
if not episode_names:
continue
for ep in episode_names:
file_name = sanitize_filename(ep)
url = get_ep_url(parsed[ep_names.index(ep)])
download(url, file_name)
exit_choice = questionary.confirm("Exit?").ask()
if exit_choice:
break
if __name__ == "__main__":
main()