Eigen podcast-feed op basis van audio-bestanden
Hoewel ik niet per se last heb van echte insomnia, heb ik soms wel moeite om (terug) in slaap te komen.
Soms heb ik gewoon moeite om m’n gedachten stil te zetten, met name als ik ineens ’s nachts wakker word. Wat ik geleerd heb van mezelf, is dat het helpt als er dan iets is wat me nét genoeg afleid dat ik niet aan andere dingen denk, maar niet interessant genoeg is dat het me wakker houdt.
Zo heb ik nog nooit een minuut World of Warcraft gespeeld1, maar heb ik deze video over WoW villains al héél vaak (deels) gehoord. Meestal niet meer dan de eerste 5-10 minuten (de video duurt 2 uur), omdat ik dan slaap. Het werkt, voor mij.
Goed, wat heeft dit te maken met automatisering?
Het idee: een eigen podcast feed
Ik wilde een manier om dit makkelijk te kunnen starten, ook bijvoorbeeld vanaf mijn Apple Watch, zodat ik niet naar m’n telefoon hoef te kijken als ik dit midden in de nacht doe.
Omdat ik dit niet wilde in mijn gebruikelijke podcast app, kon ik ook geen gebruik maken van de mogelijkheid om daar eigen audio-bestanden te uploaden.
Ik wil dus een semi-publieke podcast feed, waarvan ik de link weet, en die invoeren in een andere app. (Ik heb gekozen voor Pocket Casts, maar dat is hier verder niet relevant.)
De podcast feed is eigenlijk gewoon een bestand waarin staat welke audio-bestanden je aanbiedt. In dit geval zijn dat dus audio-bestanden van YouTube-video’s.
Vervolgens wil ik…
- Een YouTube-video kunnen downloaden in audio-formaat
- Dat bestand ergens plaatsen zodat de podcast app er bij kan
- In de podcast feed aangeven dat dat bestand er is
De video’s downloaden als audio
Voor het downloaden heb je veel opties. yt-dlp is fijn als je je comfortabel voelt met de terminal, Downie als je een Mac app wilt, en MeTube is ideaal als je het op een webserver draait.
Omdat we in dit geval de bestanden en podcast feed ook via enige vorm van een webserver moeten aanbieden is MeTube eigenlijk ideaal.
Kopieer gewoon de video-link, ga naar je eigen MeTube-omgeving, plak de link, en laat downloaden.
Je kunt een aparte download-map opgeven, dus als je MeTube voor meer dingen gebruikt laat je in dit geval downloaden naar je speciale podcasts-map.
Het proces
“Speciale podcasts-map?”, denk je wellicht.
Ja, wat we willen is een aparte map voor de “binnenkomende” podcast-bestanden. We draaien een script dat in de gaten houdt of er bestanden in die map staan (gebruik bijvoorbeeld Hazel, of laat gewoon elke zoveel tijd een script draaien via bijvoorbeeld een cron job), voegen het toe aan de podcast feed, en zetten de bestanden in een publieke map.
De hele mappenstructuur ziet er bijvoorbeeld zo uit:
/
/build-podcast.py
/incoming-audio
/public/podcast.xml
/public/2-hours-of-wow-lore-about-villains-to-fall-asleep-to.m4a
/public is wat we beschikbaar willen maken met een webserver (tip: cd public && python3 -m http.server), en /incoming-audio is waar de audio-bestanden terecht komen.
Maar hoe ziet build-podcast.py er uit?
De feed maken op basis van de bestanden
Een podcast feed, in z’n meest simpele vorm, ziet er bijvoorbeeld zo uit:
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
<channel>
<title>Titel van je podcast</title>
<description>Omschrijving van je podcast</description>
<language>English</language>
<itunes:explicit>No</itunes:explicit>
<item>
<title>Titel van aflevering 2</title>
<itunes:author>Host van de aflevering</itunes:author>
<enclosure url="https://example.com/bestand-2.m4a" type="audio/x-m4a"/>
<pubDate>Tue, 08 May 2024 09:18:01 +0000</pubDate>
</item>
<item>
<title>Titel van aflevering 1</title>
<itunes:author>Host van de aflevering</itunes:author>
<enclosure url="https://example.com/bestand-1.mp3" type="audio/mpeg"/>
<pubDate>Tue, 07 May 2024 08:16:04 +0000</pubDate>
</item>
</channel>
</rss>
Niet heel ingewikkeld dus.
Daarom kunnen we dit eenvoudig opbouwen met een Python script als dit:
#!/usr/bin/env python3
import sys
import shutil
import os
import xml.etree.ElementTree as ET
import re
public_folder = os.path.join(os.path.dirname(os.path.realpath(__file__)), "public")
feed_xml_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "public", "podcast.xml")
public_url = 'https://example.com'
class AudioFile:
def __init__(self, filename):
self.filename = filename
self.new_filename = filename
def cleanup_filename(self):
base, extension = os.path.splitext(os.path.basename(self.filename))
new_base = re.sub(r'\W+', '-', base)
self.new_filename = new_base + extension
def move_file_to_public_folder(self, cleanup_filename = True):
if(cleanup_filename):
self.cleanup_filename()
destination = os.path.join(public_folder, os.path.basename(self.new_filename))
shutil.move(self.filename, destination)
def add_to_podcast_feed(self, feed_xml_file):
current_xml = ET.parse(feed_xml_file)
root = current_xml.getroot()
new_item = ET.Element('item')
title = ET.SubElement(new_item, 'title')
title.text = os.path.basename(self.new_filename)
link = ET.SubElement(new_item, 'link')
link.text = public_url + '/' + os.path.basename(self.new_filename)
channel = root.find('channel')
channel.append(new_item)
try:
current_xml.write(feed_xml_file, encoding='utf-8', xml_declaration=True)
except Exception as e:
print(f"An error occurred: {e}")
sys.exit(1)
if __name__ == "__main__":
filename = sys.argv[1]
audio_file = AudioFile(filename)
audio_file.add_to_podcast_feed(feed_xml_file)
audio_file.move_file_to_public_folder()
En dat geeft ons een heel simpele podcast feed.
Zet natuurlijk wel even de juiste URL bovenin het script, tenzij je je podcast gaat hosten op example.com, maar dan kun je dit zo gebruiken.
Het script gebruiken
Zet eerst een soort lege template klaar in public/podcast.xml:
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
<channel>
<title>Titel van je podcast</title>
<description>Omschrijving van je podcast</description>
<language>English</language>
<itunes:explicit>No</itunes:explicit>
</channel>
</rss>
Dit is waar de rest dan in terecht komt.
Roep daarna het script simpelweg aan met ./build-podcast.py [path naar audio-bestand]. Vanuit Hazel bijvoorbeeld build-podcast.py "$1", of in een cron job: find incoming-audio -type f -print0 | xargs -0 ./build-podcast.py.
Dan doet het script de rest, en bouwt zo de podcast feed voor je op.
Slaap lekker. 😉
Niet dat ik geen ontzettende nerd ben hoor, en ik heb waarschijnlijk duizenden uren in de eerste twee Diablo-games, maar WoW specifiek heb ik gewoon nooit gespeeld. 🤷🏻♂️ ↩︎