7
YTDL_OPTIONS Cookbook
Alex Shnitman edited this page 2026-08-20 16:15:13 +02:00

Here you can find various useful configurations that can be set via the YTDL_OPTIONS and YTDL_OPTIONS_FILE environment variables.

English subtitles & chapter marks, permissions, modification timestamp

Embeds English subtitles and chapter markers (for videos that have them), changes the permissions on the downloaded video, and sets the file modification timestamp to the date of when it was downloaded.

    environment:
      - 'YTDL_OPTIONS={"writesubtitles":true,"subtitleslangs":["en","-live_chat"],"updatetime":false,"postprocessors":[{"key":"Exec","exec_cmd":"chmod 0664","when":"after_move"},{"key":"FFmpegEmbedSubtitle","already_have_subtitle":false},{"key":"FFmpegMetadata","add_chapters":true}]}'

Embed metadata and a cover image in video downloads

Contributed by @PikuZheng.

NOTE: May cause errors for non-Youtube videos.

    environment:
      - 'YTDL_OPTIONS={ "ignoreerrors":true, "writethumbnail":true, "postprocessors":[  {"key":"FFmpegMetadata","add_metadata":true,"add_chapters":true,"add_infojson":"if_exists"}, {"key":"EmbedThumbnail"}]}'

⚠️ Do not apply this to audio downloads. Picking Audio in the UI already embeds a cover image, and the writethumbnail above switches that off — see What MeTube already sets for you. To use this for video only, define it as a named preset via YTDL_OPTIONS_PRESETS instead of setting it globally.

Automatically marking MeTube downloads with SponsorBlock chapters

Contributed by @loomweaver.

Mimics the command yt-dlp --sponsorblock-mark sponsor,selfpromo,intro,outro,poi_highlight.

NOTE: due to the complex usage of quotes here (both " and ' are used), it's advisable to put it in a file and point YTDL_OPTIONS_FILE to it, rather than trying to configure it via YTDL_OPTIONS.

{"postprocessors": [{"api":"https://sponsor.ajay.app/","categories": ["intro",
                                    "outro",
                                    "poi_highlight",
                                    "selfpromo",
                                    "sponsor"],
                     "key": "SponsorBlock",
                     "when": "after_filter"},
                    {"force_keyframes":false,
                     "key": "ModifyChapters",
                     "remove_chapters_patterns": [],
                     "remove_ranges": [],
                     "remove_sponsor_segments": [],
                     "sponsorblock_chapter_title": "'[SponsorBlock]: ''%(category_names)l'"},
                    {"add_chapters": true,
                     "add_infojson": "none",
                     "add_metadata": false,
                     "key": "FFmpegMetadata"}]}

Notifications on download completion

The image includes curl, and yt-dlp's Exec postprocessor runs a command after each successful download — enough for ntfy, Gotify, Pushover, Telegram, webhooks, etc.:

YTDL_OPTIONS={"postprocessors":[{"key":"Exec","exec_cmd":"curl -s -d 'Downloaded: ' -d %(title)q https://ntfy.sh/YOUR_TOPIC","when":"after_move"}]}

exec_cmd supports the full yt-dlp output-template syntax (%(title)q, %(filepath)q, ...). To make notifications opt-in per download instead of global, define it as a named preset via YTDL_OPTIONS_PRESETS.

Note: postprocessors only run on success — there is currently no hook for failed downloads.

Using --parse-metadata (MetadataParser) from JSON

yt-dlp's --parse-metadata normally builds Python tuples that JSON can't express. Use the MetadataFromField postprocessor instead — it takes plain "FROM:TO" strings and behaves identically. For example, to clear the description (the equivalent of --parse-metadata ":(?P<meta_description>)"):

YTDL_OPTIONS={"postprocessors":[{"key":"MetadataFromField","formats":[":(?P<meta_description>)"],"when":"pre_process"}]}

Each entry in formats is one FROM:TO expression, exactly as you would write it on the yt-dlp command line.

Download video and extract audio in one go

keepvideo plus FFmpegExtractAudio gives you both the video file and an audio file from a single download:

YTDL_OPTIONS={"keepvideo":true,"postprocessors":[{"key":"FFmpegExtractAudio","preferredcodec":"mp3"}]}

To convert the video container instead (e.g. everything as MKV):

YTDL_OPTIONS={"postprocessors":[{"key":"FFmpegVideoConvertor","preferedformat":"mkv"}]}

(Note: yt-dlp's option is really spelled preferedformat, with one r.) Define these as named presets via YTDL_OPTIONS_PRESETS to pick them per-download in the UI instead of applying them globally.

What MeTube already sets for you

MeTube builds part of the yt-dlp configuration itself from what you pick in the UI, then merges your YTDL_OPTIONS on top. Options it already handles are best left out of YTDL_OPTIONS — setting them again is at best redundant, and in one case actively harmful.

Picking Audio (any format except wav) makes MeTube add all of this:

Option Purpose
writethumbnail download the cover image
FFmpegExtractAudio extract to your chosen format and quality
FFmpegThumbnailsConvertor convert the cover to jpg before embedding
FFmpegMetadata write tags
EmbedThumbnail embed the cover

Picking Video adds no postprocessors at all, which is why the metadata recipe above is useful there and unnecessary for audio.

⚠️ The writethumbnail trap. MeTube adds that audio chain only when writethumbnail is absent from your options. The intent was to let you turn cover art off with "writethumbnail": false, but the check looks at whether the key is present rather than at its value — so setting it to true switches the chain off just the same:

Audio + mp3, no YTDL_OPTIONS        -> ExtractAudio, ThumbnailsConvertor, Metadata, EmbedThumbnail
Audio + mp3, "writethumbnail": true -> ExtractAudio

Without FFmpegThumbnailsConvertor the cover stays in its original format (webp, from YouTube). Embedding that into an mp3 fails with "Supported filetypes for thumbnail embedding are: mp3...", or leaves the image sitting next to the audio file as a separate .webp. If cover art has stopped working on your audio downloads, this is almost always why — remove writethumbnail from YTDL_OPTIONS.

Converting a yt-dlp command line into JSON

yt-dlp ships devscripts/cli_to_api.py, which turns command-line flags into the API options that YTDL_OPTIONS expects. It is the right starting point, but do not paste its output in wholesale — it emits everything, including the keys MeTube manages from the UI. Delete those and keep only what remains.

For example, a square-cropped cover image:

yt-dlp -x --audio-format mp3 --embed-thumbnail --convert-thumbnail jpg \
  --ppa "EmbedThumbnail+ffmpeg_o:-c:v mjpeg -vf crop=\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\""

The converter turns that into a block containing format, writethumbnail, final_ext, FFmpegExtractAudio, FFmpegThumbnailsConvertor and EmbedThumbnail — every one of which MeTube already does when you pick Audio + mp3, and one of which (writethumbnail) would break it. The only part MeTube does not provide is the postprocessor argument, so that is all you need:

{
  "postprocessor_args": {
    "embedthumbnail+ffmpeg_o": [
      "-c:v", "mjpeg",
      "-vf", "crop='if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'"
    ]
  }
}

The --ppa prefix becomes the lowercased key embedthumbnail+ffmpeg_o, and the arguments become a list rather than a single string, which removes the shell quoting problem entirely.