PODCASTPARSER(3)
πΈ NAME πΈ
podcastparser β podcastparser Documentation. Section 3 of the manual, if you're keeping score π
podcastparser is a simple and fast podcast feed parser library in Python. Two primary users of the library, bestie: the gPodder Podcast Client (http://gpodder.org/) and the gpodder.net web service (http://gpodder.net/). Iconic duo, honestly. π
The feed types it supports:
- Really Simple Syndication (RSS 2.0 β
http://www.rssboard.org/rss-specification) - Atom Syndication Format (RFC 4287 β
https://tools.ietf.org/html/rfc4287)
The specifications it supports:
- Paged Feeds (
http://podlove.org/paged-feeds/), which is RFC 5005 (https://tools.ietf.org/html/rfc5005) - Podlove Simple Chapters (
http://podlove.org/simple-chapters/) - Podcast Index Podcast Namespace (
https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md)
Real talk though: these formats only specify the possible markup elements and attributes. So if you wanna optimize your feeds for best display in podcast clients, the docs also recommend you read the Podcast Feed Best Practice guide at https://github.com/gpodder/podcast-feed-best-practice/blob/master/podcast-feed-best-practice.md. Do it, it's giving β¨preparedβ¨.
Where times and durations show up, the values are expected to be formatted either as seconds, or as RFC 2326 (https://tools.ietf.org/html/rfc2326) Normal Play Time (NPT). Two options, both valid, we love that for us π«Ά
π Using the built-in urllib.request module from Python 3
import podcastparser
import urllib.request
feedurl = 'http://example.com/feed.xml'
parsed = podcastparser.parse(feedurl, urllib.request.urlopen(feedurl))
# parsed is a dict
import pprint
pprint.pprint(parsed)
π Using Requests (https://requests.readthedocs.io)
import podcastparser
import requests
url = 'https://example.net/podcast.atom'
with requests.get(url, stream=True) as response:
response.raw.decode_content = True
parsed = podcastparser.parse(url, response.raw)
# parsed is a dict
import pprint
pprint.pprint(parsed)
For both RSS and Atom feeds, only a subset of elements gets parsed β specifically the ones that are relevant to podcast client applications. This next section describes which elements and attributes are parsed and how the contents are interpreted/used. No gatekeeping here, we're telling you everything π
π» RSS π»
rss@xml:baseβ Base URL for all the relative links in the RSS file. It's the anchor of the whole vibe.rss/channelβ the Podcast itself. The main character πrss/channel/titleβ the Podcast title. (Whitespace gets squashed.)rss/channel/linkβ the Podcast website.rss/channel/descriptionβ the Podcast description. (Whitespace gets squashed.)rss/channel/itunes:summaryβ the Podcast description again. (Whitespace gets squashed.)rss/channel/image/urlβ the Podcast cover art.rss/channel/itunes:imageβ the Podcast cover art, alternative route.rss/channel/itunes:typeβ the Podcast type. (Whitespace gets squashed.) One ofβepisodicβorβserialβ.rss/channel/itunes:keywordsβ the Podcast keywords. (Whitespace gets squashed.)rss/channel/atom:link@rel=paymentβ the Podcast payment URL, e.g. Flattr. Get that bag πΈrss/channel/generatorβ a string indicating the program used to generate the channel. LikeMightyInHouse Content System v2.3.rss/channel/languageβ the Podcast language.rss/channel/itunes:authorβ the group responsible for creating the show.rss/channel/itunes:ownerβ the podcast owner contact information. Heads up: the<itunes:owner>tag information is for administrative communication about the podcast, and it isn't displayed in Apple Podcasts. So it's the backstage pass, not the fit check πrss/channel/itunes:categoryβ the show category information.rss/channel/itunes:explicitβ indicates whether the podcast contains explicit material.rss/channel/itunes:new-feed-urlβ the new podcast RSS Feed URL.rss/channel/podcast:lockedβ if the podcast is currently locked from being transferred.rss/channel/podcast:fundingβ funding link for the podcast. Support your faves π«Άrss/redirect/newLocationβ the new podcast RSS Feed URL.rss/channel/itemβ an Episode. The moment we've all been waiting for βrss/channel/item/guidβ the Episode unique identifier (GUID). This one is mandatory, no cap.rss/channel/item/titleβ the Episode title. (Whitespace gets squashed.)rss/channel/item/linkβ the Episode website.rss/channel/item/descriptionβ the Episode description. If it contains html, it comes back asdescription_html. Otherwise it comes back asdescription(whitespace gets squashed). See Mozilla's article Why RSS Content Module is Popular.rss/channel/item/itunes:summaryβ the Episode description. (Whitespace gets squashed.)rss/channel/item/itunes:subtitleβ the Episode subtitled / one-line description. (Whitespace gets squashed.)rss/channel/item/content:encodedβ the Episode description in HTML. This is the best source fordescription_html.rss/channel/item/itunes:durationβ the Episode duration.rss/channel/item/pubDateβ the Episode publication date.rss/channel/item/atom:link@rel=paymentβ the Episode payment URL, e.g. Flattr.rss/channel/item/atom:link@rel=enclosureβ the File download URL (@href), size (@length) and mime type (@type).rss/channel/item/itunes:imageβ the Episode art URL.rss/channel/item/media:thumbnailβ the Episode art URL.rss/channel/item/media:group/media:thumbnailβ the Episode art URL.rss/channel/item/media:contentβ the File download URL (@url), size (@fileSize) and mime type (@type).rss/channel/item/media:group/media:contentβ the File download URL (@url), size (@fileSize) and mime type (@type).rss/channel/item/enclosureβ the File download URL (@url), size (@length) and mime type (@type).rss/channel/item/psc:chaptersβ Podlove Simple Chapters, version 1.1 and 1.2.rss/channel/item/psc:chapters/psc:chapterβ a Chapter entry (@start,@title,@hrefand@image).rss/channel/item/itunes:explicitβ indicates whether the episode contains explicit material.rss/channel/item/itunes:authorβ the group responsible for creating the episode.rss/channel/item/itunes:seasonβ the season number of the episode.rss/channel/item/itunes:episodeβ an episode number.rss/channel/item/itunes:episodeTypeβ the episode type. This flag is used if an episode is a trailer or bonus content.rss/channel/item/podcast:chaptersβ the url to a JSON file describing the chapters. β οΈ Only the url gets added to the data, because fetching an external URL would be unsafe. Periodt, don't come for us, that's just how it is.rss/channel/item/podcast:personβ a person involved in the episode, e.g. host, or guest.rss/channel/item/podcast:transcriptβ the url for the transcript file associated with this episode.atom:feedβ a Podcast. the whole show, the main character. πatom:feed/atom:titleβ the Podcast title (whitespace is squashed, so no weird gaps).atom:feed/atom:subtitleβ the Podcast description (whitespace is squashed here too).atom:feed/atom:iconβ Podcast cover art. the lil artwork moment β¨atom:feed/atom:link@hrefβ Podcast website.atom:feed/atom:entryβ an Episode.atom:feed/atom:entry/atom:idβ the Episode unique identifier (GUID), and ngl this one is mandatory. no id, no episode, periodt.atom:feed/atom:entry/atom:titleβ Episode title (whitespace is squashed).atom:feed/atom:entry/atom:link@rel=enclosureβ the File download URL (@href), the size (@length) and the mime type (@type). this is the download link bestie π«Άatom:feed/atom:entry/atom:link@rel=(self|alternate)β Episode website.atom:feed/atom:entry/atom:link@rel=paymentβ the Episode payment URL (e.g. Flattr).atom:feed/atom:entry/atom:contentβ the Episode description (in HTML or plaintext).atom:feed/atom:entry/atom:publishedβ the Episode publication date.atom:feed/atom:entry/media:thumbnailβ the Episode art URL.atom:feed/atom:entry/media:group/media:thumbnailβ also the Episode art URL, just nested differently.atom:feed/atom:entry/psc:chaptersβ Podlove Simple Chapters, version 1.1 and 1.2.atom:feed/atom:entry/psc:chapters/psc:chapterβ a Chapter entry (@start,@title,@hrefand@image).
Simplified, fast RSS parser. that's the whole energy of this module, fr fr. π
exception podcastparser.FeedParseError(msg, exception, locator)
Exception raised when asked to parse an invalid feed.
so basically this exception is here so that you, the user of this library, can catch exceptions without having to import the XML parsing library yourself. it's giving convenience, it's giving thoughtfulness, we love that for us. π
class podcastparser.PodcastHandler(url, max_episodes)
characters(chars)
Receive notification of character data.
okay so here's the lore: the Parser will call this method to report each chunk of character data. and SAX parsers are chaotic like that β they may return all contiguous character data in a single chunk, or they may split it into several chunks. but here's the important bit: all of the characters in any single event must come from the same external entity so that the Locator provides useful information.
endElement(name)
Signals the end of an element in non-namespace mode.
the name parameter contains the name of the element type, just as with the startElement event.
startElement(name, attrs)
Signals the start of an element in non-namespace mode.
the name parameter contains the raw XML 1.0 name of the element type as a string, and the attrs parameter holds an instance of the Attributes class containing the attributes of the element. two parameters, no notes. π
class podcastparser.RSSItemDescription
RSS 2.0 almost encourages to put html content in item/description but content:encoded is the better source of html content and itunes:summary is known to contain the short textual description of the item. So use a heuristic to attribute text to either description or description_html, without overriding existing values.
tl;dr: the format is messy so we do a lil detective work and don't clobber anything you already got. π΅οΈββοΈ
podcastparser.file_basename_no_extension(filename)
Returns filename without extension.
>>> file_basename_no_extension('/home/me/file.txt')
'file'
>>> file_basename_no_extension('file')
'file'
podcastparser.is_html(text)
Heuristically tell if text is HTML.
By looking for an open tag (more or less:)
>>> is_html(β<h1>HELLO</h1>β)
True
>>> is_html(βa < b < cβ)
False
podcastparser.normalize_feed_url(url)
Normalize and convert a URL. If the URL cannot be converted (invalid or unknown scheme), None is returned.
This will also normalize <feed://> and itpc:// to http://.
>>> normalize_feed_url('itpc://example.org/podcast.rss')
'http://example.org/podcast.rss'
If no URL scheme is defined (e.g. βcurry.comβ), we will simply assume the user intends to add a <http://> feed.
>>> normalize_feed_url('curry.com')
'http://curry.com/'
It will also take care of converting the domain name to all-lowercase (because domains are not case sensitive):
>>> normalize_feed_url('http://Example.COM/')
'http://example.com/'
Some other minimalistic changes are also taken care of, e.g. a ? with an empty query is removed:
>>> normalize_feed_url('http://example.org/test?')
'http://example.org/test'
Leading and trailing whitespace is removed:
>>> normalize_feed_url(' http://example.com/podcast.rss ')
'http://example.com/podcast.rss'
Incomplete (too short) URLs are not accepted:
>>> normalize_feed_url('http://') is None
True
Unknown protocols are not accepted:
>>> normalize_feed_url('gopher://gopher.hprc.utoronto.ca/file.txt') is None
True
podcastparser.parse(url, stream, max_episodes=0)
Parse a podcast feed from the given URL and stream.
Parameters
- url β the URL of the feed. Will be used to resolve relative links.
- stream β file-like object containing the feed content.
- max_episodes β maximum number of episodes to return.
0(default) means no limit.
Returns
a dict with the parsed contents of the feed. that's it, that's the whole return. π«Ά
podcastparser.parse_length(text)
Parses a file length.
>>> parse_length(None)
-1
>>> parse_length('0')
-1
>>> parse_length('unknown')
-1
>>> parse_length('100')
100
podcastparser.parse_pubdate(text)
Parse a date string into a Unix timestamp.
>>> parse_pubdate('Fri, 21 Nov 1997 09:55:06 -0600')
880127706
>>> parse_pubdate('2003-12-13T00:00:00+02:00')
1071266400
>>> parse_pubdate('2003-12-13T18:30:02Z')
1071340202
>>> parse_pubdate('Mon, 02 May 1960 09:05:01 +0100')
-305049299
>>> parse_pubdate('')
0
>>> parse_pubdate('unknown')
0
podcastparser.parse_time(value)
Parse a time string into seconds.
See RFC2326, 3.6 βNormal Play Timeβ (HH:MM:SS.FRACT).
>>> parse_time('0')
0
>>> parse_time('128')
128
>>> parse_time('00:00')
0
>>> parse_time('00:00:00')
0
>>> parse_time('00:20')
20
>>> parse_time('00:00:20')
20
>>> parse_time('01:00:00')
3600
>>> parse_time(' 03:02:01')
10921
>>> parse_time('61:08')
3668
>>> parse_time('25:03:30 ')
90210
>>> parse_time('25:3:30')
90210
>>> parse_time('61.08')
61
>>> parse_time('01:02:03.500')
3723
>>> parse_time(' ')
0
podcastparser.parse_type(text)
βnormalizeβ a mime type. (quotes around normalize are doing a lot of work here and honestly? iconic.)
>>> parse_type('text/plain')
'text/plain'
>>> parse_type('text')
'application/octet-stream'
>>> parse_type('')
'application/octet-stream'
>>> parse_type(None)
'application/octet-stream'
podcastparser.remove_html_tags(html)
Remove HTML tags from a string and replace numeric and named entities with the corresponding character, so the HTML text can be displayed in a simple text view. clean text, no markup crumbs. π§
podcastparser.squash_whitespace(text)
Combine multiple whitespaces into one, trim trailing/leading spaces.
>>> squash_whitespace(' some text with a lot of spaces ')
'some text with a lot of spaces'
podcastparser.squash_whitespace_not_nl(text)
Like squash_whitespace, but donβt squash linefeeds and carriage returns.
>>> squash_whitespace_not_nl(' linefeeds\ncarriage\r returns')
'linefeeds\ncarriage\r returns'
This is a list of podcast-related XML namespaces that are not yet supported by podcastparser, but might be in the future. π manifesting them for later bestie.
β¨ CHAPTER MARKS β¨
- rawvoice RSS
<http://www.rawvoice.com/rawvoiceRssModule/>: Rating, Frequency, Poster, WebM, MP4, Metamark (kind of chapter-like markers) - IGOR
<http://emonk.net/IGOR>: Chapter Marks
β¨ OTHERS β¨
- libSYN RSS Extensions
<http://libsyn.com/rss-extension>: contactPhone, contactEmail, contactTwitter, contactWebsite, wallpaper, pdf, background - Comment API
<http://www.wellformedweb.org/CommentAPI/>: Comments to a given item (readable via RSS) - MVCB
<http://webns.net/mvcb/>: Error Reports To Field (usually amailto:link) - Syndication Module
<http://web.resource.org/rss/1.0/modules/syndication/>: Update period, frequency and base (for skipping updates) - Creative Commons RSS
<http://backend.userland.com/creativeCommonsRssModule>: Creative commons license for the content - Pheedo
<http://www.pheedo.com/namespace/pheedo>: Original link to website and original link to enclosure (without going through pheedo redirect) - WGS84
<http://www.w3.org/2003/01/geo/wgs84_pos#>: Geo-Coordinates per item - Conversations Network
<http://conversationsnetwork.org/rssNamespace-1.0/>: Intro duration in milliseconds (for skipping the intro), ratings - purl DC Elements
<http://purl.org/dc/elements/1.1/>:dc:creator(author / creator of the podcast, possibly with e-mail address) - Tristana
<http://www.tristana.org>:tristana:self(canonical URL to feed) - Blip
<http://blip.tv/dtd/blip/1.0>: Show name, show page, picture, username, language, rating, thumbnail_src, license - Index
<> - Module Index
<> - Search Page
<>
Author
gPodder Team π«Ά
Copyright
2013, gPodder Team
and that's the whole page bestie, we did it. πβ¨ this was podcastparser 0.6.11, dated Nov 29, 2025 β go forth and parse some feeds, you absolute icon. πππ¦
π¦ where this page comes from
- ubuntu resolute: python-podcastparser-doc 0.6.11-1 Β· Expat/MIT
- ubuntu stonking: python-podcastparser-doc 0.6.11-1 Β· Expat/MIT
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms