I wrote a tiny script that creates an RSS feed for all the audio files it finds in a folder.
I call it derb.

My mother gets devotional songs and sermons on cds, which I rip to MP3 files and then dump on her phone for her.1
She listens to them all the time, and now three of her friends want to do the same too.
I thought of just sticking them in my self hosted Jellyfin instance,2 but then I realised, all of them have erratic, slow internet. So the idea of self hosting a podcast feed really appealed to me.

So I quickly used Feedgenerator in conjunction with Tinytag, to whip up a script that’d help me do just that. The code’s up on Github, if you want to go install and play with it yourselves.

Here’s a quick walk through derb.py.3

Setting up house

We set up a place to accept a path containing the files. The feed will ultimately be placed as a feed.xml in the same folder as well We then walk through the folder (after a really basic sanity check) and gather all the files into a list.
The base_url is where the feeds (along with the audio) will be served from.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
base_url = "https://ab.janusworx.com"

book_folder = input("Paste path to audiobook folder here: ")
book_out_path = base_url + (book_folder.split("/")[-1])
file_list = os.walk(book_folder)

# Do a basic check on the validity of the path we get, 
# before we build the audio file list.
try:
    all_files = (list(file_list)[0][2])
except IndexError as e:
    print(f"\n\n"
          f"---\n"
          f"ERROR!: {e}\n"
          f"Have you typed in the right path?\n"
          f"---\n")
    sys.exit("Quitting script!")
audio_files = []
for each_file in all_files:
    each_file = Path(each_file)
    if each_file.suffix in ['.mp3', '.m4a', '.m4b']:
        audio_files.append(str(each_file))
audio_files = sorted(audio_files)

Creating a feed

We now go about the business of setting up the feed proper.
To begin with, we grab the first file we can get our grubby paws on, and create a TinyTag object that’ll give us a lot of metadata. (If there isn’t any, we quit.)
Oh, and by the way, how do I know what data I’d need to create a feed? I just cribbed everything from the Feedgenerator’s excellent documentation. I also looked at the widely linked to, RSS reference page for clarification if I got confused.

We then, instantiate create a feedgenerator object along with the podcast extension.
Following which, we supply the feed a title, an id4, feed author details, a language, podcast category and description.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Setup a feed
## Grab feed metadata from the first audio file
feed_metadata_file = TinyTag.get(Path(book_folder, audio_files[0]))

## Grab title from metadata file.
## At the same time, break out if there isn’t any.
if not feed_metadata_file.album:
    sys.exit("\n---\nStopping feed creation.\n
		Setup audio file metadata with a tag editor")
feed_title = feed_metadata_file.album

## Creating feed instance
audio_book_feed = FeedGenerator()
audio_book_feed.load_extension("podcast")

## Setting up more stuff on the feed proper
audio_book_feed.id(base_url)
audio_book_feed.title(feed_title)
audio_book_feed.author({"name": "Jason Braganza", "email": "feedback@janusworx.com"})
audio_book_feed.link(href=f'{book_out_path}', rel='self')
audio_book_feed.language('en')
audio_book_feed.podcast.itunes_category('Private')
audio_book_feed.description(feed_title)

Adding episodes and writing out the file

After which it’s then a matter of looping through that audio file list we created and adding them as entries to the feed object we created.
Once again we grab the metadata from each file, using Tinytag, and then set each feed entry’s details (title, id and enclosure).
I’ve hardcoded the mime types, since I know I only have two basic type of audio files. If you don’t know what kind of audio, you might be serving, the mimetypes-magic package should help.
Finally we write it all out to a file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Loop the file list and create entries in the feed

for each_file in audio_files:
    each_file_metadata = TinyTag.get(Path(book_folder, each_file))
    episode_file_path = Path(each_file)
    episode_suffix = episode_file_path.suffix
    episode_mime_type = 'audio/mpeg' if episode_suffix == '.mp3' else 'audio/x-m4a'
    episode_title = each_file_metadata.title
    episode_size = str(each_file_metadata.filesize)
    episode_link = f"{book_out_path}/{each_file}"
    
	audio_episode = audio_book_feed.add_entry()
    audio_episode.title(episode_title)
    audio_episode.id(episode_link)
    audio_episode.enclosure(episode_link, episode_size, episode_mime_type)

# Write the rss feed to the same folder as the source audio files
audio_book_feed.rss_file(f"{book_folder}/feed.xml")

Serving

Once done, I moved it all to my trusty Pi, which runs barebones Nginx with a single bare page, protected by basic auth.

I decided not to publish the feeds publicly.
Rather I’m going to just set it up in their podcast players, when I meet them, or pass it over to their kids over Signal.5
It’s already worked with three of them, so everyone’s happy and here’s me hoping, fingers crossed, it’ll be easy to support in the long run.

Update (2023-09-15): Guess I’m using it for myself too, now 😂


Feedback on this post? Mail me at feedback@janusworx.com

P.S. Subscribe to my mailing list!
Forward these posts and letters to your friends and get them to subscribe!
P.P.S. Feed my insatiable reading habit.



  1. Yes, while it’s slowly moving to Youtube, that world still mostly depends on CDs and USB sticks. ↩︎

  2. the audio files, not my mother’s friends. ↩︎

  3. Large parts are elided. Please look at Github for the actual file. ↩︎

  4. normally, the site it’ll be served from ↩︎

  5. Why should I be the only one doing family tech support? ↩︎