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.
Release files for sub2pod 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sub2pod-0.4.0.tar.gz | 11.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sub2pod-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 25.4 kB
Release files / sub2pod-0.4.0.tar.gz
| Download URL | sub2pod-0.4.0.tar.gz |
|---|---|
| Size | 11.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8e770589496f81d82cdb45d6de78ecc49a010c9f8b862977a6c996adf789ccc3
|
|
BLAKE2b-256 checksum How to use checksums |
7744dac6d4c305484951a23635c03e259132a8fd4b49745958fdffcebfff2c26
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / sub2pod-0.4.0-py3-none-any.whl
| Download URL | sub2pod-0.4.0-py3-none-any.whl |
|---|---|
| Size | 14.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2a6c64380c8fdc0d5a5b55fa38604265d50b60467ab5a94d3a5ea2b84b253cf9
|
|
BLAKE2b-256 checksum How to use checksums |
e56606e14845d15fa5b8e3d564801eb921bf5be436727239c7b6e18b5d7dc57c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|