diora-web/podcasts/feedext.py
mrwnslz f4b8bcc891 Podcasts: Kapitel, Shownotes und Transkripte für die Apps
- Feeds liefern jetzt Podlove-Kapitel (psc:chapters), Podcasting-2.0-Kapitel
  (podcast:chapters, JSON-URL) und Transkripte (podcast:transcript, auch
  mehrere); als Beschreibung wird content:encoded bevorzugt.
- Bekannte Folgen werden beim Aktualisieren nachgezogen, aber nur die Zeilen,
  die sich wirklich geändert haben.
- Neuer Endpunkt GET /podcasts/episodes/<id>/ mit bereinigten Shownotes,
  Zeitmarken, Kapiteln, Transkripten und Fortschritt. JSON-Kapitel werden
  einmal serverseitig geholt (nur öffentliche Adressen, max. 1 MB) und an
  der Folge gespeichert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 07:56:44 +02:00

43 lines
1.7 KiB
Python

"""Teach feedparser the Podcasting 2.0 tags we read.
feedparser 6 doesn't know the podcast namespace. Unknown tags still come
through, but only under whatever prefix the feed happened to declare, and an
unknown tag with attributes is stored as a single dict, so a second
<podcast:transcript> overwrites the first. Registering the namespace fixes the
prefix ("podcast_…" for every feed); the handler below keeps every
transcript in a list, entry["podcast_transcripts"].
podcast:chapters is left to feedparser's default handling, which gives
entry["podcast_chapters"] = {"url": …, "type": …} (one per item, as the spec
says).
"""
from feedparser.mixin import _FeedParserMixin as FeedParserMixin
from feedparser.util import FeedParserDict
PODCAST_NAMESPACES = (
'https://podcastindex.org/namespace/1.0',
# The URL the namespace was announced under; some early feeds still use it.
'https://github.com/podcastindex-org/podcast-namespace/blob/main/docs/1.0.md',
)
def _start_podcast_transcript(self, attrs_d):
context = self._get_context()
context.setdefault('podcast_transcripts', []).append(FeedParserDict(attrs_d))
def _end_podcast_transcript(self):
pass
def install():
"""Idempotent; called when podcasts.views is imported."""
for uri in PODCAST_NAMESPACES:
FeedParserMixin.namespaces.setdefault(uri, 'podcast')
# Filled lazily from `namespaces` by the first parser; once it has
# been, later additions have to go in here directly.
if FeedParserMixin._matchnamespaces:
FeedParserMixin._matchnamespaces.setdefault(uri.lower(), 'podcast')
FeedParserMixin._start_podcast_transcript = _start_podcast_transcript
FeedParserMixin._end_podcast_transcript = _end_podcast_transcript