Skip to content

ytdl-sub Integration Guide

sub2pod converts ytdl-sub output directories into podcast RSS feeds. This guide explains how to configure ytdl-sub to produce output compatible with sub2pod.

Table of Contents

Output format contract

sub2pod expects each channel directory to contain the following files:

File Required Description
tvshow.nfo Yes Kodi XML with <title> and optionally <genre>
poster.jpg or fanart.jpg No Channel artwork (used as <itunes:image>)
YYYY-MM-DD - <title>.<ext> Yes Date-prefixed audio files (.opus, .mp3, .m4a)
YYYY-MM-DD - <title>.nfo No Per-episode Kodi XML sidecar (see fields below)
YYYY-MM-DD - <title>-thumb.jpg No Per-episode thumbnail (currently reserved for future use)

Episode NFO fields read by sub2pod

NFO element sub2pod field RSS element
<title> EpisodeInfo.title <item><title>
<plot> or <description> EpisodeInfo.plot <item><description>
<aired> or <upload_date> EpisodeInfo.aired <item><pubDate>
<runtime> EpisodeInfo.runtime <itunes:duration>
<webpage_url> EpisodeInfo.original_url <item><link>

When no .nfo sidecar is present, sub2pod falls back to parsing the date and title from the filename pattern YYYY-MM-DD - <title>.<ext>.

Configuring nfo_tags for per-episode metadata

The nfo_tags output type in ytdl-sub produces Kodi-compatible NFO sidecar files. Configure it to emit the fields sub2pod needs.

Required ytdl-sub variables

Use these ytdl-sub source variables in your NFO tags:

nfo_tags:
  nfo_name: "{upload_date_standardized} - {title}.nfo"
  nfo_root: "episodedetails"
  tags:
    title: "{title}"
    description: "{description}"
    upload_date: "{upload_date_standardized}"
    webpage_url: "{webpage_url}"
Variable Maps to Notes
{title} <title> Video title from the source
{description} <description> Full video description (show notes)
{upload_date_standardized} <upload_date> Date in YYYY-MM-DD format
{webpage_url} <webpage_url> URL of the original source page

Optional: runtime

If your source provides duration metadata (e.g. YouTube), include it:

nfo_tags:
  tags:
    # ... other tags ...
    runtime: "{duration}"

Note: The <runtime> field in Kodi NFO files is measured in minutes. ytdl-sub's {duration} variable provides seconds. To use it, divide by 60: <runtime>{duration_seconds / 60}</runtime> (if your ytdl-sub version supports arithmetic expressions in tags). Otherwise, omit <runtime> — it is optional and sub2pod will still produce valid feeds without it.

Optional: genre

Include genre in tvshow.nfo for iTunes category support:

nfo_tags:
  nfo_name: "tvshow.nfo"
  nfo_root: "tvshow"
  tags:
    title: "{tv_show_name}"
    genre: "Technology"

Configuring episode thumbnails

Use ytdl-sub's episode_thumbnails output type to produce per-episode JPEG files compatible with sub2pod's naming convention:

presets:
  podcast:
    output_options:
      output_directory: "/path/to/podcast/{tv_show_name}"
      file_name: "{upload_date_standardized} - {title}.{ext}"

    episode_thumbnails:
      thumbnail_name: "{upload_date_standardized} - {title}-thumb.jpg"

Note: Episode thumbnails are currently reserved for future use in sub2pod. The RSS feed includes only channel-level artwork (<itunes:image>) via poster.jpg or fanart.jpg. Generating thumbnails now ensures your directories are forward-compatible when per-episode artwork support is added.

Configuring channel artwork

ytdl-sub can download channel artwork. Ensure it writes poster.jpg:

presets:
  podcast:
    output_options:
      output_directory: "/path/to/podcast/{tv_show_name}"

    # Download channel thumbnail as poster.jpg
    # (exact configuration depends on your ytdl-sub version and source plugin)

If ytdl-sub's channel thumbnail output produces a different filename, rename it manually or symlink it to poster.jpg.

Complete example configuration

Below is a complete ytdl-sub config.yaml for a YouTube channel converted to a sub2pod-compatible podcast directory:

configuration:
  working_directory: ".ytdl-sub"

presets:
  podcast:
    # --- YouTube source ---
    youtube:
      channel_url: "https://www.youtube.com/@Level1Techs"
      download_strategy: "audio_only"

    # --- Output file naming ---
    output_options:
      output_directory: "/home/NAS/ytdl-sub/mark/podcast/{tv_show_name}"
      file_name: "{upload_date_standardized} - {title}.{ext}"

    # --- Episode NFO sidecar ---
    nfo_tags:
      nfo_name: "{upload_date_standardized} - {title}.nfo"
      nfo_root: "episodedetails"
      tags:
        title: "{title}"
        description: "{description}"
        upload_date: "{upload_date_standardized}"
        webpage_url: "{webpage_url}"

    # --- Episode thumbnails ---
    episode_thumbnails:
      thumbnail_name: "{upload_date_standardized} - {title}-thumb.jpg"

    # --- Show-level NFO ---
    nfo_tags:
      nfo_name: "tvshow.nfo"
      nfo_root: "tvshow"
      tags:
        title: "{tv_show_name}"
        genre: "Technology"

    # --- Overrides ---
    overrides:
      tv_show_name: "Links with Friends"

Important: The tv_show_name override must match your desired channel directory name. sub2pod uses the directory name to construct enclosure URLs.

Running sub2pod: post_process hook vs cron job

sub2pod is a separate CLI tool. You need to decide when to run it relative to ytdl-sub.

Option A: ytdl-sub post_process hook (recommended for simplicity)

ytdl-sub supports post_process scripts that run after each subscription completes. This keeps your feeds up-to-date immediately after new content is downloaded.

presets:
  podcast:
    # ... other configuration ...

    post_process:
      - command: "sub2pod"
        args:
          - "/home/NAS/ytdl-sub/mark/podcast"
          - "https://podcast.example.com"

Pros: - Feeds update automatically after each ytdl-sub run - No separate scheduling needed - OPML index regenerated on every run

Cons: - sub2pod runs even if no new episodes were downloaded (though it's fast) - Requires sub2pod to be installed in the same environment as ytdl-sub - Errors in sub2pod may obscure ytdl-sub exit status

Run sub2pod on a schedule independent of ytdl-sub. This decouples download from feed generation and is easier to debug.

# Run sub2pod every 6 hours
0 */6 * * * /usr/local/bin/sub2pod /home/NAS/ytdl-sub/mark/podcast https://podcast.example.com

Pros: - ytdl-sub and sub2pod run independently; failures in one don't affect the other - Predictable feed update intervals - Easier to test and debug

Cons: - Feeds may be stale for up to the cron interval after new downloads - Needs separate scheduling configuration

Recommendation

Start with the cron job approach. It is simpler to set up, easier to debug, and avoids coupling between the download and feed-generation steps. If you need immediate feed updates after download, switch to the post_process hook later.

Opus MIME type caveat and re-encode workaround

The problem

ytdl-sub's audio_only download strategy often produces .opus files (Opus audio in an Ogg container). sub2pod emits the correct MIME type for this format:

audio/ogg; codecs=opus

However, Apple Podcasts does not support Opus audio. Episodes encoded as Opus will not play in Apple Podcasts, iTunes, or other Apple ecosystem clients.

Affected clients

Client Opus support
Apple Podcasts / iTunes ❌ Not supported
Overcast ❌ Not supported
Pocket Casts ✅ Supported
AntennaPod (Android) ✅ Supported
Podcast Addict (Android) ✅ Supported
VLC (desktop) ✅ Supported

Workaround: Re-encode to MP3

Configure ytdl-sub to download or re-encode to MP3 instead of Opus:

presets:
  podcast:
    youtube:
      channel_url: "https://www.youtube.com/@Level1Techs"
      download_strategy: "audio_only"

    # Request MP3 format from yt-dlp
    ytdl_options:
      postprocessors:
        - key: "FFmpegExtractAudio"
          preferredcodec: "mp3"
          preferredquality: "192"

This tells yt-dlp (which ytdl-sub uses internally) to extract audio and encode it as MP3 at 192 kbps. sub2pod will then emit audio/mpeg as the enclosure MIME type, which is universally supported.

Workaround: Re-encode to M4A (AAC)

For lower bitrates at equivalent quality, use AAC in an MP4 container:

presets:
  podcast:
    ytdl_options:
      postprocessors:
        - key: "FFmpegExtractAudio"
          preferredcodec: "m4a"
          preferredquality: "128"

sub2pod emits audio/mp4 for .m4a files, which is also broadly supported.

If you keep Opus

If you are targeting Android clients or accept the compatibility trade-off:

  1. Test your feed in your target podcast clients before publishing.
  2. Be aware that the file extension .opus with MIME type audio/ogg; codecs=opus is technically correct per RFC 5334 and RFC 7845, but not all feed validators and podcast clients handle it correctly.
  3. Consider providing an alternate feed with MP3-encoded episodes for iOS users.