Turns a ytdl-sub output directory into a valid, servable podcast RSS feed.
  • Python 97.5%
  • Just 2.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Marvin8 7628f0365d
All checks were successful
/ pr-review (push) Has been skipped
/ gitleaks (push) Successful in 16s
/ pr-review (release) Has been skipped
/ gitleaks (release) Successful in 17s
/ checks (push) Successful in 1m50s
/ publish (push) Has been skipped
/ checks (release) Successful in 1m48s
/ deploy-docs (release) Has been skipped
/ publish (release) Successful in 29s
/ deploy-docs (push) Successful in 1m9s
🔖 bump: version 0.3.2 → 0.3.3
2026-08-07 07:22:32 +10:00
.forgejo/workflows 🚚 migrate: Codeberg → forge.marvin8.zone 2026-08-07 07:12:09 +10:00
docs 📝 set up MkDocs docs deployment to grebedoc 2026-07-10 08:49:36 +10:00
src/sub2pod 💡 docs: add docstrings for _populate_channel_metadata and _append_episode_item 2026-07-20 09:13:49 +10:00
tests 🔧 chore: add ruff.toml (TID252, expanded rules), LICENSE.md, README badges; bump ty→0.0.59 2026-07-20 07:24:29 +10:00
.editorconfig 🔧 chore: align .editorconfig with mature project standard (indent_size, py max_line_length, json/rst/html sections) 2026-07-20 09:09:17 +10:00
.gitignore 🙈 added site folder to gitignore 2026-07-10 09:35:27 +10:00
.gitleaks.toml 🔧 chore: scaffolding alignment — gitleaks.toml title, ruff target-version py312, .editorconfig, uv_build>=0.11.0 2026-07-19 18:17:18 +10:00
.pre-commit-config.yaml 🚚 migrate: Codeberg → forge.marvin8.zone 2026-08-07 07:12:09 +10:00
.python-version 🐛 parse <description> and <upload_date> from ytdl-sub episode NFOs 2026-06-18 12:38:53 +10:00
AGENTS.md 📝 docs: fix AGENTS.md heading to match filename; make CLAUDE.md a symlink 2026-07-19 18:17:41 +10:00
AI-DECLARATION.md 📝 fix AI-DECLARATION.md — copilot level, DeepSeek tool, deployment none 2026-07-20 06:45:55 +10:00
CHANGELOG.md 📝 docs: update changelog for 0.3.3 2026-08-07 07:22:25 +10:00
CLAUDE.md 📝 docs: fix AGENTS.md heading to match filename; make CLAUDE.md a symlink 2026-07-19 18:17:41 +10:00
concept.md Initial commit with concept 2026-06-17 09:52:09 +10:00
docs_hooks.py 📝 set up MkDocs docs deployment to grebedoc 2026-07-10 08:49:36 +10:00
improvements.md 💡 docs: add module/class/function docstrings to all source files; replace bare except-pass with logging.warning in discovery 2026-07-20 07:42:27 +10:00
justfile 🚚 migrate: Codeberg → forge.marvin8.zone 2026-08-07 07:12:09 +10:00
LICENSE.md 🔧 chore: add ruff.toml (TID252, expanded rules), LICENSE.md, README badges; bump ty→0.0.59 2026-07-20 07:24:29 +10:00
mkdocs.yml 🚚 migrate: Codeberg → forge.marvin8.zone 2026-08-07 07:12:09 +10:00
noxfile.py 🔧 bootstrap: add pyproject.toml, noxfile.py, cliff.toml, uv venv 2026-06-17 10:22:48 +10:00
pylock.toml 🚚 migrate: Codeberg → forge.marvin8.zone 2026-08-07 07:12:09 +10:00
pyproject.toml 🔖 bump: version 0.3.2 → 0.3.3 2026-08-07 07:22:32 +10:00
README.md 🚚 migrate: Codeberg → forge.marvin8.zone 2026-08-07 07:12:09 +10:00
Release-Notes.md 📝 reset Release-Notes.md for next release 2026-07-10 10:30:02 +10:00
Release-Notes.md.template 🔍 change tags for release notes 2026-07-10 09:27:13 +10:00
ruff.toml 🔧 chore: add ruff.toml (TID252, expanded rules), LICENSE.md, README badges; bump ty→0.0.59 2026-07-20 07:24:29 +10:00
uv.lock 🔖 bump: version 0.3.2 → 0.3.3 2026-08-07 07:22:32 +10:00

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

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-thumb.jpg  ← Episode thumbnail

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.