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.0

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.0
File Size Uploaded
sub2pod-0.4.0.tar.gz 11.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sub2pod 0.4.0
File Interpreter ABI Platform
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}

Release history Release notifications | RSS feed

0.4.1

2 release files

This release

0.4.0 This release

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