Turns a ytdl-sub output directory into a valid, servable podcast RSS feed.
- Python 98.1%
- Just 1.9%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
/ pr-review (push) Has been skipped
/ gitleaks (push) Successful in 17s
/ checks (push) Successful in 1m38s
/ publish (push) Has been skipped
/ pr-review (release) Has been skipped
/ gitleaks (release) Successful in 15s
/ deploy-docs (push) Successful in 1m7s
/ checks (release) Successful in 1m39s
/ deploy-docs (release) Has been skipped
/ publish (release) Successful in 27s
|
||
| .forgejo/workflows | ||
| docs | ||
| src/sub2pod | ||
| tests | ||
| .editorconfig | ||
| .gitignore | ||
| .gitleaks.toml | ||
| .pre-commit-config.yaml | ||
| .python-version | ||
| AGENTS.md | ||
| AI-DECLARATION.md | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| docs_hooks.py | ||
| justfile | ||
| LICENSE.md | ||
| mkdocs.yml | ||
| noxfile.py | ||
| pylock.toml | ||
| pyproject.toml | ||
| README.md | ||
| Release-Notes.md | ||
| Release-Notes.md.template | ||
| ruff.toml | ||
| uv.lock | ||
sub2pod
Convert ytdl-sub output directories into
standards-compliant podcast RSS feeds. Point it at a directory full of downloaded
audio, and it writes feed.xml into each channel subdirectory — ready to serve or
submit to podcast directories.
What it does
- Reads Kodi
tvshow.nfoand per-episode.nfosidecars produced by ytdl-sub - Discovers date-prefixed audio files (
YYYY-MM-DD - <title>.<ext>) in each channel directory - Generates RSS 2.0 + iTunes namespace
feed.xmlper channel - Emits correct MIME types for
.opus,.mp3, and.m4aenclosures - Produces an
opml.xmlindex at the root level for one-click feed import - Falls back to filename parsing when
.nfosidecars are absent - Emits Podcast 2.0
<podcast:chapters>for episodes with chapter markers (via the.info.jsonsidecar)
What it does not do
- No download or subscription management (that's ytdl-sub's job)
- No web server — feed files are static XML; serve them however you like
- No podcast client or player
Installation
From PyPI
pip install sub2pod
From source
git clone https://forge.marvin8.zone/marvin8/sub2pod.git
cd sub2pod
uv sync
uv run sub2pod --help
Usage
sub2pod /path/to/ytdl-sub/output https://podcast.example.com
This walks the parent directory looking for subdirectories containing tvshow.nfo
and generates:
| Output | Location | Description |
|---|---|---|
feed.xml |
Each channel subdirectory | RSS 2.0 + iTunes podcast feed |
opml.xml |
Parent directory root | OPML index of all feeds |
Options
--dry-run Print paths without writing any files
--verbose Log per-episode details during generation
Example
# After ytdl-sub downloads new episodes:
sub2pod /home/NAS/ytdl-sub/mark/podcast https://podcasts.example.com
# Preview without writing:
sub2pod /home/NAS/ytdl-sub/mark/podcast https://podcasts.example.com --dry-run
# See what's being generated:
sub2pod /home/NAS/ytdl-sub/mark/podcast https://podcasts.example.com --verbose
How it works
- Channel discovery — Walks the parent directory for subdirectories containing
tvshow.nfo - Metadata parsing — Reads channel title and genre from
tvshow.nfo; reads per-episode title, description, date, runtime, and source URL from.nfosidecars - Episode discovery — Finds all date-prefixed audio files, pairs them with their
.nfosidecars, sorts newest-first - Feed generation — Builds RSS 2.0 XML with iTunes podcast namespace elements (
<itunes:image>,<itunes:duration>,<itunes:category>), writesfeed.xml - OPML index — Writes
opml.xmlat the root listing every channel feed
Input format
Each channel directory is expected to contain:
Links with Friends/
├── tvshow.nfo ← Channel metadata (title, genre)
├── poster.jpg ← Podcast artwork → <itunes:image>
├── 2026-05-19 - Episode Title.opus ← Audio file
├── 2026-05-19 - Episode Title.nfo ← Episode metadata sidecar
├── 2026-05-19 - Episode Title.info.json ← yt-dlp metadata sidecar (chapters source, optional)
└── 2026-05-19 - Episode Title-thumb.jpg ← Episode thumbnail
Example feed
A trimmed feed.xml for a channel with one episode:
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
<channel>
<title>Links with Friends</title>
<link>https://podcasts.example.com</link>
<description>Links with Friends</description>
<language>en</language>
<itunes:image href="https://podcasts.example.com/Links%20with%20Friends/poster.jpg"/>
<item>
<title>Episode Title</title>
<enclosure url="https://podcasts.example.com/Links%20with%20Friends/2026-05-19%20-%20Episode%20Title.opus" type="audio/ogg; codecs=opus" length="12345678"/>
<guid isPermaLink="false">https://podcasts.example.com/Links%20with%20Friends/2026-05-19%20-%20Episode%20Title.opus</guid>
<pubDate>Tue, 19 May 2026 00:00:00 +0000</pubDate>
</item>
</channel>
</rss>
Troubleshooting
- No
feed.xmlis generated — sub2pod only processes subdirectories that contain atvshow.nfo; ensure each channel directory has one. - Malformed
base_url— sub2pod requires anhttp:///https://URL with a host; invalid values fail with an actionable error. - Episodes won't play in Apple Podcasts —
.opusis unsupported by Apple; re-encode to.mp3/.m4a(see the integration guide). - Episode metadata missing — without a
.nfosidecar, sub2pod falls back to parsing the filename; malformed sidecars are skipped with a warning.
Documentation
- ytdl-sub Integration Guide — Configuring ytdl-sub to produce sub2pod-compatible output: NFO tags, episode thumbnails, trigger strategies, and Opus MIME type guidance.
Licence
Copyright (C) 2026 Marvin8
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.