Podcasting 2.0 in MinusPod
MinusPod sits between the original podcast feed and your player. It downloads each episode, removes ad segments, re-encodes the audio, and serves a modified RSS feed pointing at the processed files.
MinusPod is not the podcast’s publisher, so the Podcast Namespace (the
podcast: tags often called “Podcasting 2.0”) needs care: it is a proxy
that rewrites someone else’s feed.
Almost none of this is configurable. Two things are: each feed can
choose how its podcast:chapters tag is populated (see “Chapter
modes” below), and Podping listening is an opt-in, instance-wide
toggle (see “Podping” below). Everything else in this document is
automatic for every feed MinusPod serves.
This document is about that proxy case: a feed MinusPod re-serves on someone else’s behalf. A local feed is different. MinusPod is the publisher there, not a proxy, so there is no upstream tag to pass through, strip, or regenerate against. See Local Feeds > Podcasting 2.0 fields for what a local feed emits instead.
Chapters and transcript for the cut audio, the served feed’s namespace tags, and the chapter settings. Portrait cut and the file itself in Videos.
The rule
Section titled “The rule”Every Podcast Namespace tag falls into one of four buckets:
- Pass through: the tag is still true after ad removal, so the upstream value is copied into the served feed unchanged.
- Regenerate: the tag describes the audio, so MinusPod produces its own correct version for the processed file.
- Strip: the tag asserts something about the original audio that is false for the re-cut file, so emitting it would be a lie or would break players. It is removed.
- Always emit: MinusPod adds the tag regardless of the upstream feed.
“Strip” is not a feature gap. Passing these tags through would make the feed describe audio that no longer exists.
What MinusPod supports
Section titled “What MinusPod supports”Always emitted
Section titled “Always emitted”| Tag | Behavior |
|---|---|
podcast:guid |
MinusPod mints its own feed GUID. See “Feed identity” below. |
podcast:txt (purpose="ai-content") |
Always set to true. MinusPod algorithmically re-cuts the audio, and the served feed discloses that. Any upstream ai-content value is replaced, because once MinusPod has processed the file the upstream claim is no longer accurate. |
Passed through from the original feed
Section titled “Passed through from the original feed”These remain accurate after ad removal and are copied verbatim:
| Tag | Why it survives |
|---|---|
podcast:funding |
The creator’s donation links are unaffected by ad removal. |
podcast:locked |
Reflects the creator’s intent about feed re-import. If the original feed has no locked tag, MinusPod emits locked as yes, because a private re-feed should not be imported into public directories. |
podcast:podroll |
Show recommendations remain valid and useful for discovery. |
podcast:value, podcast:valueRecipient, podcast:valueTimeSplit |
Value-for-value payment information is passed through untouched so listener support still reaches the original creator. MinusPod does not insert itself into the payment split. |
podcast:license |
Content license is metadata, not tied to the audio timeline. |
podcast:medium |
Describes the kind of content, unaffected. |
podcast:person |
Host and guest credits are unaffected by cutting. |
podcast:updateFrequency |
The publishing schedule does not change. |
podcast:season, podcast:episode |
Structural numbering, no timeline dependency. |
podcast:trailer |
A trailer is its own separate file, not an offset into the episode. |
podcast:image |
Artwork sources improve display and are unaffected. The deprecated plural podcast:images, if present upstream, is also passed through untouched. |
podcast:socialInteract |
Comment and discussion links are unaffected. |
podcast:block |
The publisher’s directory-block hints. Multiple instances are allowed, optionally scoped to one platform with an id attribute (e.g. id="apple"). MinusPod copies each one verbatim. Stripping these would silently expose the re-feed in directories the publisher chose to keep it out of. |
podcast:complete |
Boolean for “no more episodes are coming”. Applies to the show, not the audio timeline; ad removal does not change whether the run is over. |
podcast:txt (general, no verify purpose) |
Free-form metadata, harmless to carry. |
Regenerated for the processed audio
Section titled “Regenerated for the processed audio”MinusPod already produces these for the cut file. The upstream versions are discarded because their timestamps point into the original, uncut audio, and because subscribers to a MinusPod feed must never be sent to a publisher URL: the proxy serves everything or nothing. Until MinusPod finishes processing an episode, the served feed carries no transcript or chapter URL for it; the audio enclosure returns 503 with a JIT-triggered processing job behind it.
| Tag | Why it is regenerated |
|---|---|
podcast:transcript |
MinusPod generates a transcript aligned to the processed audio. An upstream transcript would be offset by the length of every removed ad, and would also point subscribers at the publisher’s CDN. The upstream tag is still read internally, never served, to diff against MinusPod’s own transcript for ad evidence; see Upstream Transcript Differential. |
podcast:chapters |
MinusPod serves its own chapters JSON because upstream timestamps point into the original, uncut audio. Its contents depend on the effective chapter mode; see “Chapter modes” below. |
itunes:duration |
Recomputed from the processed file’s actual length. |
enclosure length |
The processed file’s byte size. An unprocessed episode passes through upstream’s length when declared, omitting the attribute otherwise. |
Pass-through episodes are the exception: nothing is cut, so the publisher’s timestamps stay valid. At processing time MinusPod imports the item’s podcast:chapters and podcast:transcript and serves them from its own URLs, never the publisher’s. A timed transcript (VTT, SRT or JSON) is stored as the episode transcript with a VTT when VTT transcripts are enabled; an untimed one (HTML or plain text) appears in the Transcript card only, with no VTT. Once the feed leaves pass-through, the next reprocess transcribes the episode with Whisper; the publisher transcript is not used for detection.
Chapter modes
Section titled “Chapter modes”Settings > Transcripts & Chapters sets the chapter mode: Auto (the default), Always generate, or Off. New feeds and feeds set to Inherit use that choice; an explicit Feed Settings value overrides it. During normal processing, the mode is applied only when Generate Chapters is on.
Encrypted, compressed, restricted, or malformed ID3 chapter tags stop processing before existing audio and chapter data are replaced.
The cut step remaps embedded ID3 chapters and preserves their images and links. Auto keeps surviving ID3 publisher chapters, including a single chapter. When preserving publisher chapters, recuts recover their metadata from the retained original and remap it to the new cut timeline.
Chapter images are served locally when their JPEG or PNG data fits the artwork size limit. Other embedded image formats remain in the ID3 tags. Local image URLs retain the validated feed key supplied with the chapter request.
When usable embedded chapters are unavailable, MinusPod checks whether the feed
declares its chapters in a separate podcast:chapters JSON file, captured at the
last feed refresh. If it does, MinusPod fetches that file, remaps its timestamps
the same way, and uses the result when at least two chapters survive. A failed or
malformed fetch counts as unknown, not as “no chapters”, and it falls
back to generation rather than losing chapters altogether.
Only when neither source yields usable chapters does MinusPod fall back to AI-generated chapters. If the embedded probe itself fails (for example a transient ffprobe error), MinusPod treats that as unknown too, and skips the chapter step for that run rather than risk overwriting the ID3 frames the cut step already wrote correctly.
Always generate skips the probe and always produces AI chapters.
Off stops chapter work for episodes processed from then on: no
probing, no generation, and nothing saved, so those episodes carry no
podcast:chapters tag. Episodes processed before the switch keep the
chapters they already have until they are reprocessed.
Auto is the default because it keeps the publisher’s chapters whenever they hold up across the cut, rather than replacing accurate ones with generated chapters.
Chapters in episode descriptions
Section titled “Chapters in episode descriptions”Most apps only show podcast:chapters once playback starts. To make the
list readable while browsing, turn on List chapters in episode
descriptions under Settings > Transcripts & Chapters (chaptersInNotes,
off by default). Each episode’s description in the served feed, and on the
episode page, then ends with a Chapters block: one mm:ss Title line per
chapter, h:mm:ss past an hour. It is built from the same generated
chapters the JSON carries. The block is rendered when the description is served and
is never written into the stored description, so turning the setting off
removes it and regenerating chapters updates it. Each feed can override the
global value from its Feed Settings page (chaptersInNotes: on, off,
or unset to follow the global setting).
Review links in episode descriptions
Section titled “Review links in episode descriptions”To jump from a podcast app to an episode in MinusPod, turn on Add review
links to episode descriptions under Settings > Transcripts & Chapters
(reviewLinksInNotes, off by default). The description of every episode
MinusPod has seen then ends with two links: Review in MinusPod opens the
episode page, and Rate 5 stars opens it with ?rate=5, which applies a
5-star rating as soon as the page loads, confirms it with an Undo button and
removes the parameter from the address bar. The links are added as soon as
MinusPod has the episode, including while it is still processing, because some
apps store show notes the first time they see an episode and never refresh
them. Items the upstream feed lists but MinusPod has not discovered yet get no
links. The rating link is a page address, not an API call, because podcast
apps and link previews fetch URLs from show notes on their own. It needs a
browser logged in to MinusPod and goes through the login page first when it is
not. The links are built from UI_BASE_URL, or BASE_URL when that is unset.
Each feed can override the global value from its Feed Settings page
(reviewLinksInNotes: on, off, or unset to follow the global setting).
Ad chapters
Section titled “Ad chapters”A segment kept in the audio by its category action still sits in the file. Set a category’s segment action to Mark (Settings > Segment actions, or a feed’s own override; see Configuration > Segment categories) and MinusPod adds a chapter at the start of each marked break and a resume chapter at its end, so an app that skips by chapter can jump past it. Keep leaves the segment in the audio but publishes no chapter for it. Chapters have no end time, which is why the resume chapter exists. When a generated chapter already starts within two seconds of the end, that chapter is the resume point instead. A feed whose chapters mode is Off still gets no chapters, Mark or not.
A marked segment needs a detection confidence of at least Minimum confidence (Settings > Transcripts & Chapters, 0.9 by default). Turn on Include segments waiting for review to chapter held segments too, when the category they are held under resolves to Mark on that feed. They use the waiting-for-review title format, and rejecting one removes its chapter without a recut. Confirming one queues a recut, which rebuilds the chapter list when it runs.
Titles come from Chapter title (Ad: {label} by default), Title
while waiting for review (Possible ad: {label}), and Resume title
(Show). {label} is the category name, such as Sponsor. {category} is
its id, such as sponsor, for players that match chapter titles by
keyword. Switching a category between Keep and Mark, globally or on one
feed, takes effect the next time that feed’s chapters are rebuilt:
Regenerate Chapters on an episode, or Re-render episodes with current
segment actions on the feed. Existing episodes are not backfilled
automatically.
A category’s segment action is the only chapter switch. The deprecated
adChaptersEnabled and adChapterCategories API fields map onto it:
adChaptersEnabled reads true when any category is Mark, and false
sets every Mark to Keep (true has no effect); a true or false entry
in adChapterCategories sets that category to Mark or Keep. New
integrations should set segmentCategoryActions to mark directly.
Deliberately stripped
Section titled “Deliberately stripped”These describe the original audio’s timeline or bytes. After ad removal they are false. Two of them can make a conformant player refuse to play the episode.
| Tag | Why it is removed |
|---|---|
podcast:soundbite |
startTime and duration point into the original timeline. After cutting, that offset is wrong, so a “highlight” would play the wrong audio, possibly mid-sentence or inside a removed ad. |
podcast:integrity |
A cryptographic hash of the original audio bytes. The processed file will never match it. A player that verifies integrity will reject the episode outright. |
podcast:alternateEnclosure |
Points at other copies of the original, un-stripped audio. Following it bypasses ad removal entirely. |
podcast:source |
Describes sources of the original audio, not the processed file MinusPod serves. |
podcast:liveItem |
A live stream. By the time MinusPod has downloaded and processed recorded audio, the live event is over. It has no meaning in a re-cut feed. |
podcast:txt (purpose="verify" or purpose="applepodcastsverify") |
An ownership-verification token a hosting platform issued to the original publisher. It proves nothing about a MinusPod re-feed, and carrying it forward leaks the original owner’s token into a feed they don’t control. |
Podping
Section titled “Podping”Podping is a Hive blockchain notification bus that podcast hosts use to announce “this feed just updated” the moment they publish, instead of waiting for aggregators to poll. MinusPod does one half of that: it can listen for these notifications about the feeds it re-serves, but it never sends any of its own.
MinusPod listens (opt-in)
Section titled “MinusPod listens (opt-in)”Settings > Global Defaults has a Podping notifications
toggle, off by default. When enabled, MinusPod runs a background
thread that polls public Hive API nodes roughly every 3 seconds for
new blocks, reads the podping and pp_-prefixed custom JSON
operations in them, and checks each announced feed URL against your
configured feeds. On a match, MinusPod stamps that feed’s “last
podping” time and triggers an immediate refresh of that one feed,
instead of waiting for the next scheduled RSS poll.
When Podping is enabled, open Podping servers beneath the toggle to edit the Hive RPC node list. Add between 1 and 20 HTTP(S) endpoint URLs, remove or reorder them, then save. MinusPod normalizes and deduplicates the list, and rotates through it for listener traffic and health checks. Reset defaults restores the shipped node list. These RPC endpoints are separate from the observed feed hosts shown in System Health.
Which senders count is up to each feed. A feed can name the accounts
allowed to podping it with <podcast:hiveAccount account="..."> inside
its <podcast:podping> tag, and MinusPod ignores podpings for that feed
from anyone else. A feed carrying usesPodping="false" is asking to be
polled, so MinusPod skips podping refreshes for it entirely. The tag is
optional, and a feed that says nothing accepts any sender. Scheduled
polling runs either way, so the worst a declaration can do is leave a
feed on the schedule.
No Hive account, keys, or wallet are required. The listener only reads the public chain; it never writes to it. Requests go to a small, built-in list of public Hive API nodes over outbound HTTPS, roughly two requests every 3 seconds while the toggle is on (a head poll plus a block fetch each tick). If a node is unreachable, the listener rotates to the next one and backs off.
Settings > System Health lists each node’s endpoint, latest HTTP status,
and last successful response time. It identifies the node carrying listener
traffic. While the listener is enabled, MinusPod checks every node at startup
and every five minutes. Check now runs the same check on demand, even when
the listener is off. These checks do not change the active node. The same
summary and check progress are available from GET /api/v1/system/status.
Podping is an accelerator, not a replacement for polling. The scheduled RSS refresh (see How It Works > Processing Queue and the Feed Refresh and Podping settings) keeps running unchanged, so a missed or delayed Podping notification costs nothing: the feed is picked up on its next regular poll either way.
Coverage depends on the host. Hosts sending Podping notifications include Spreaker, Buzzsprout, RSS.com, PodServe, Transistor, RedCircle, Captivate, Podigee, Fireside, PRX Dovetail, Blubrry, Podhome, and Alitu, alongside a long tail of self-hosted and WordPress feeds. Many large hosts, including Acast, Megaphone, Libsyn, Simplecast, and Omny, do not. Enabling the toggle only speeds up episodes on feeds whose host pings; everything else keeps its existing polling cadence.
That list is a snapshot from three days of Podping traffic on the Hive chain in July 2026, and hosts adopt Podping over time. Use the Podping line below to see what a specific feed actually receives.
With the listener on, the feed detail page and the feed lists show one
line per feed. It reads Podping: last ping at <time> once a
notification has arrived for that feed. A feed whose own tag declares
usesPodping="true" but has not been pinged yet reads
Podping: enabled, none received yet. Everything else reads
Podping: never. The line is hidden when the listener is off, because
that is an instance-wide setting rather than anything about the feed.
The API carries more detail than the line does. podpingCoverage on
the feeds endpoints separates the reasons a feed reads as never: the
publisher declined Podping, the publisher opted in but nothing has
arrived yet, the host was seen pinging other feeds in the last 30 days,
or nothing is known. It also returns the parsed declaration as
podpingUses and podpingHiveAccounts. GET /api/v1/podping/hosts
lists every domain the listener has seen announcing feeds, which is the
way to confirm the listener is receiving traffic at all. A feed that
stays on never while /podping/hosts shows its host pinging usually
means MinusPod’s stored source URL does not match what the host
announces.
MinusPod does not emit Podping
Section titled “MinusPod does not emit Podping”MinusPod does not send Podping notifications for the feeds it serves,
and has no podcast:podping tag or setting to turn that on.
This follows from what MinusPod is. It is a proxy that re-serves someone else’s feed with ads removed, not the publisher of that content. The feeds MinusPod serves change only when the upstream feed changes, and players already discover that on the upstream publisher’s own schedule (directly, or through the upstream host’s own Podping notifications). MinusPod announcing its own re-feed as a new publish event would tell the ecosystem about a private, ad-stripped derivative of content you do not own, which is not what Podping is for.
Feed identity
Section titled “Feed identity”MinusPod mints its own podcast:guid rather than reusing the original
feed’s GUID.
The GUID is the stable identifier other tools use to recognize a feed. A MinusPod feed is a modified derivative served at a different URL. If it claimed the original’s GUID, aggregators could treat the two feeds as the same feed, which they are not.
The GUID is computed the way the
Podcast Namespace guid spec
specifies: a deterministic UUIDv5 over the served feed URL, using the
namespace’s fixed constant. The same feed URL always produces the same
GUID, so the identity is stable for as long as the feed is served at
the same address. If you move MinusPod to a different domain, the
served URL changes and so does the GUID, which is correct, because at
that point it is genuinely a feed at a new address.
Upstream namespace URIs
Section titled “Upstream namespace URIs”The Podcast Namespace spec treats several xmlns URIs as equivalent, and
real-world feeds use them interchangeably. The reference pc20.xml feed,
for example, declares the GitHub-blob form rather than the
podcastindex.org form. MinusPod accepts all of these on parse:
https://podcastindex.org/namespace/1.0https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.mdhttps://github.com/Podcastindex-org/podcast-namespace/blob/master/docs/1.0.md- The
http://variants of each
The served feed always declares the canonical https://podcastindex.org/namespace/1.0
URI on its <rss> root, no matter which form the upstream feed used.
Passthrough tags are re-emitted under the canonical podcast: prefix.
Compatibility with older players
Section titled “Compatibility with older players”Every tag MinusPod adds is in the podcast: namespace. A player that
does not understand a tag ignores it; the standard RSS and itunes:
elements it relies on are untouched. A basic podcast app sees a normal
feed and plays the audio. Podcasting 2.0 apps see the additional tags.
Nothing about this support breaks older clients.
Example served feed
Section titled “Example served feed”docs/examples/served-feed.xml is a sanitized
excerpt of a real feed served by a MinusPod instance, with the host,
feed key, and episode identifiers replaced by placeholders. It shows the
podcast:guid, podcast:locked, podcast:funding, podcast:txt,
podcast:transcript, and podcast:chapters tags as MinusPod emits them.
References
Section titled “References”External Podcasting 2.0 documentation:
- Podcasting 2.0 documentation home
- Podcast Namespace introduction
- RSS 2.0 namespace declaration
- Full tag reference (sidebar lists every tag)
- Podcast Namespace source repository
- Podcasting 2.0 apps and players
- Publishing tools and hosts
Per-tag specifications are linked inline from the tables above.