Last active
December 20, 2023 03:12
-
-
Save yig/4784a86c1343d2bed789dd243ce980fc to your computer and use it in GitHub Desktop.
Create a valid podcast RSS feed from a base URL and a list of files.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
''' | |
Author: Yotam Gingold <yotam (strudel) yotamgingold.com> | |
License: Public Domain [CC0](http://creativecommons.org/publicdomain/zero/1.0/) | |
On GitHub as a gist: https://gist.github.com/yig/4784a86c1343d2bed789dd243ce980fc | |
''' | |
''' | |
Example: | |
$ python3 feedgen.py -o feed.xml "https://www.website.com/secretpodcast/" file1.m4a file2.mp3 ... | |
$ scp feed.xml file1.m4a file2.mp3 ... website.com:www/secretpodcast/ | |
In your podcast app, add the feed: https://www.website.com/secretpodcast/feed.xml | |
''' | |
from pathlib import Path | |
import datetime | |
import hashlib | |
## Podcast feed requirements: | |
## https://podcasters.apple.com/support/823-podcast-requirements | |
## https://help.apple.com/itc/podcasts_connect/#/itcb54353390 | |
## Example: https://help.apple.com/itc/podcasts_connect/#/itcbaf351599 | |
HEADER = '''<?xml version="1.0" encoding="UTF-8"?> | |
<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:content="http://purl.org/rss/1.0/modules/content/"> | |
<channel> | |
<title>{title}</title> | |
<language>en-us</language> | |
<description> | |
{description} | |
</description> | |
<itunes:type>serial</itunes:type> | |
<itunes:image | |
href="https://applehosted.podcasts.apple.com/hiking_treks/artwork.png" | |
/> | |
<itunes:category text="Sports"> | |
<itunes:category text="Wilderness"/> | |
</itunes:category> | |
<itunes:explicit>false</itunes:explicit> | |
''' | |
ITEM = ''' | |
<item> | |
<itunes:episodeType>full</itunes:episodeType> | |
<itunes:season>{season}</itunes:season> | |
<itunes:episode>{episode}</itunes:episode> | |
<title>{title}</title> | |
<description> | |
{title} | |
</description> | |
<enclosure | |
length="{bytes}" | |
type="audio/x-m4a" | |
url="{URL}" | |
/> | |
<guid>{GUID}</guid> | |
<pubDate>{date}</pubDate> | |
<itunes:explicit>false</itunes:explicit> | |
</item> | |
''' | |
FOOTER = ''' | |
</channel> | |
</rss> | |
''' | |
def ItemFromPath( URL, season, episode, path ): | |
## Convert to a pathlib Path for convenience. | |
path = Path(path) | |
stat = path.stat() | |
title = path.stem | |
bytes = stat.st_size | |
mtime = stat.st_mtime | |
## RSS pubDate is defined by RFC822 | |
## https://stackoverflow.com/questions/12270531/how-to-format-pubdate-with-python | |
date = datetime.datetime.fromtimestamp(mtime).strftime("%a, %d %b %Y %H:%M:%S %z") | |
GUID = hashlib.sha256( path.name.encode('utf-8') ).hexdigest() | |
URL = URL.rstrip('/') + '/' + path.name | |
return ITEM.format( **locals() ) | |
def FeedFromPaths( url, paths, season = 1, title = 'My Podcast', description = 'My Podcast Description' ): | |
items = [ ItemFromPath( url, season, i+1, path, ) for i, path in enumerate( sorted( paths ) ) ] | |
return HEADER.format( title = title, description = description ) + ''.join( items ) + FOOTER | |
if __name__ == '__main__': | |
import argparse | |
parser = argparse.ArgumentParser( description = 'Generate a podcast feed from some files.' ) | |
parser.add_argument( 'URL', help = 'The base URL of the podcast.' ) | |
parser.add_argument( 'files', nargs='*', help = 'A list of audio files.' ) | |
parser.add_argument( '--season', type = int, default = 1, help = 'The season of the podcast.' ) | |
parser.add_argument( '--title', default = 'My Podcast', help = 'The title of the podcast.' ) | |
parser.add_argument( '--description', default = 'My Podcast Description', help = 'A description of the podcast.' ) | |
parser.add_argument( '-o', '--output', default = None, help = 'A file to save the podcast into. By default, prints to stdout.' ) | |
args = parser.parse_args() | |
feed = FeedFromPaths( args.URL, args.files, season = args.season, title = args.title, description = args.description ) | |
print( feed, file = open( args.output, 'w' ) if args.output is not None else None ) | |
print( f"In your podcast app, add the feed: {args.URL.rstrip('/')}/feed.xml" ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment