Skip to main content

sub2pod

Licence: AGPL-3.0-or-later Python 3.12+ Docs CI Downloads gitleaks pysentry ty complexipy Codestyle: Ruff AI-DECLARATION: copilot

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.nfo and per-episode .nfo sidecars 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.xml per channel
  • Emits correct MIME types for .opus, .mp3, and .m4a enclosures
  • Produces an opml.xml index at the root level for one-click feed import
  • Falls back to filename parsing when .nfo sidecars are absent
  • Emits Podcast 2.0 <podcast:chapters> for episodes with chapter markers (via the .info.json sidecar)

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

  1. Channel discovery — Walks the parent directory for subdirectories containing tvshow.nfo
  2. Metadata parsing — Reads channel title and genre from tvshow.nfo; reads per-episode title, description, date, runtime, and source URL from .nfo sidecars
  3. Episode discovery — Finds all date-prefixed audio files, pairs them with their .nfo sidecars, sorts newest-first
  4. Feed generation — Builds RSS 2.0 XML with iTunes podcast namespace elements (<itunes:image>, <itunes:duration>, <itunes:category>), writes feed.xml
  5. OPML index — Writes opml.xml at 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.xml is generated — sub2pod only processes subdirectories that contain a tvshow.nfo; ensure each channel directory has one.
  • Malformed base_url — sub2pod requires an http:///https:// URL with a host; invalid values fail with an actionable error.
  • Episodes won't play in Apple Podcasts.opus is unsupported by Apple; re-encode to .mp3/.m4a (see the integration guide).
  • Episode metadata missing — without a .nfo sidecar, 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.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sub2pod 0.4.1
File Size Uploaded
sub2pod-0.4.1.tar.gz 11.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sub2pod 0.4.1
File Interpreter ABI Platform
sub2pod-0.4.1-py3-none-any.whl Python 3 none any Details

Total release size: 25.4 kB

Release files / sub2pod-0.4.1.tar.gz

Download URL sub2pod-0.4.1.tar.gz
Size 11.0 kB
Tags Source
SHA-256 checksum
How to use checksums
d0a0f9a0b9c2b95709b99b7521d2c24da3d8f6ea655994df7667afc4b74aaba6
BLAKE2b-256 checksum
How to use checksums
dd6775bc2b43d61e362bd4980660fe06f0af94f52bca03f905b059519ad2070a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","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.1-py3-none-any.whl

Download URL sub2pod-0.4.1-py3-none-any.whl
Size 14.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5bab85dff7b38b9d4dbd3ce3320840fd58afdd4252b67c1fb0f31f0992d754fe
BLAKE2b-256 checksum
How to use checksums
e70161c58d4ce85ccf644579dded42bcd93fc17bf97d827833f174f50e52c278
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","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 history Release notifications | RSS feed

This release

0.4.1 This release

2 release files

0.4.0

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page