Skip to main content
Post-processors allow you to manipulate downloaded files after they’ve been saved. Common uses include converting formats, extracting audio, embedding metadata, and more.

What is a Post-Processor?

A post-processor is a component that:
  • Runs after a video has been downloaded
  • Modifies, converts, or enhances the downloaded file
  • Can be chained with other post-processors
  • Returns information about processed files
All post-processors inherit from the PostProcessor base class.

Using Post-Processors

Via Options Dictionary

The most common way to use post-processors is through the postprocessors option:
import yt_dlp

ydl_opts = {
    'postprocessors': [{
        'key': 'FFmpegExtractAudio',
        'preferredcodec': 'mp3',
        'preferredquality': '192',
    }]
}

with yt_dlp.YoutubeDL(ydl_opts) as ydl:
    ydl.download(['https://www.youtube.com/watch?v=dQw4w9WgXcQ'])

Via add_post_processor()

You can also add post-processors programmatically:
import yt_dlp
from yt_dlp.postprocessor import FFmpegExtractAudioPP

with yt_dlp.YoutubeDL({}) as ydl:
    pp = FFmpegExtractAudioPP(
        downloader=ydl,
        preferredcodec='mp3',
        preferredquality='192'
    )
    ydl.add_post_processor(pp, when='post_process')
    ydl.download(['https://www.youtube.com/watch?v=dQw4w9WgXcQ'])

PostProcessor Base Class

All post-processors inherit from this base class.

Constructor

from yt_dlp.postprocessor.common import PostProcessor

class PostProcessor:
    def __init__(self, downloader=None):
        pass
downloader
YoutubeDL
The YoutubeDL instance that will use this post-processor

Main Method: run()

Every post-processor must implement the run() method:
def run(self, information: dict) -> tuple[list, dict]
information
dict
required
Dictionary containing video information and the ‘filepath’ key pointing to the downloaded file
return
tuple[list, dict]
Returns a tuple of (files_to_delete, updated_information)

Utility Methods

to_screen(message)
method
Print a message to the screen
report_warning(message)
method
Report a warning message
write_debug(message)
method
Write a debug message
get_param(name, default)
method
Get a parameter from the YoutubeDL instance

When to Run Post-Processors

Post-processors can run at different stages of the download process:
pre_process
string
Before any processing (very early)
after_filter
string
After video is filtered but before download
before_dl
string
Just before downloading
post_process
string
default:"true"
After download completes (default)
after_move
string
After file is moved to final location
after_video
string
After all post-processors for a video
playlist
string
After entire playlist is processed
ydl_opts = {
    'postprocessors': [
        {
            'key': 'FFmpegThumbnailsConvertor',
            'format': 'jpg',
            'when': 'before_dl',  # Convert thumbnails before download
        },
        {
            'key': 'FFmpegExtractAudio',
            'preferredcodec': 'mp3',
            # 'when' defaults to 'post_process'
        }
    ]
}

Common Post-Processors

FFmpegExtractAudio

Extract audio from video files.
ydl_opts = {
    'postprocessors': [{
        'key': 'FFmpegExtractAudio',
        'preferredcodec': 'mp3',  # mp3, aac, m4a, opus, vorbis, flac, alac, wav
        'preferredquality': '192',  # Audio quality
        'nopostoverwrites': False,  # Overwrite existing files
    }],
    'keepvideo': False,  # Delete original video file
}

FFmpegVideoConvertor

Convert video to a different format.
ydl_opts = {
    'postprocessors': [{
        'key': 'FFmpegVideoConvertor',
        'preferedformat': 'mp4',  # avi, flv, mkv, mp4, ogg, webm
    }]
}

FFmpegVideoRemuxer

Remux video to a different container without re-encoding.
ydl_opts = {
    'postprocessors': [{
        'key': 'FFmpegVideoRemuxer',
        'preferedformat': 'mp4',
    }]
}

FFmpegMetadata

Embed metadata and chapters into the video file.
ydl_opts = {
    'postprocessors': [{
        'key': 'FFmpegMetadata',
        'add_chapters': True,
        'add_metadata': True,
        'add_infojson': 'if_exists',  # Embed info.json if available
    }],
    'writethumbnail': True,
    'writeinfojson': True,
}

EmbedThumbnail

Embed thumbnail into the media file.
ydl_opts = {
    'postprocessors': [{
        'key': 'EmbedThumbnail',
        'already_have_thumbnail': False,
    }],
    'writethumbnail': True,
}

FFmpegEmbedSubtitle

Embed subtitles into the video file.
ydl_opts = {
    'postprocessors': [{
        'key': 'FFmpegEmbedSubtitle',
        'already_have_subtitle': False,
    }],
    'writesubtitles': True,
    'subtitleslangs': ['en', 'es'],
}

FFmpegSubtitlesConvertor

Convert subtitles to a different format.
ydl_opts = {
    'postprocessors': [{
        'key': 'FFmpegSubtitlesConvertor',
        'format': 'srt',  # srt, ass, vtt, lrc
        'when': 'before_dl',
    }],
    'writesubtitles': True,
}

FFmpegThumbnailsConvertor

Convert thumbnails to a different format.
ydl_opts = {
    'postprocessors': [{
        'key': 'FFmpegThumbnailsConvertor',
        'format': 'jpg',  # jpg, png, webp
        'when': 'before_dl',
    }],
    'writethumbnail': True,
}

MetadataParser

Parse and modify metadata fields.
ydl_opts = {
    'postprocessors': [{
        'key': 'MetadataParser',
        'actions': [
            ('title', '(?P<artist>.+?) - (?P<track>.+)', None),
        ],
        'when': 'pre_process',
    }]
}

Exec

Execute external commands on downloaded files.
ydl_opts = {
    'postprocessors': [{
        'key': 'Exec',
        'exec_cmd': 'echo "Downloaded: {}"',
        'when': 'after_move',
    }]
}

SponsorBlock

Remove or mark sponsored segments (YouTube only).
ydl_opts = {
    'postprocessors': [{
        'key': 'SponsorBlock',
        'categories': ['sponsor', 'intro', 'outro'],
        'api': 'https://sponsor.ajay.app',
        'when': 'after_filter',
    }]
}

ModifyChapters

Modify or remove chapter markers.
ydl_opts = {
    'postprocessors': [{
        'key': 'ModifyChapters',
        'remove_chapters_patterns': [],
        'remove_sponsor_segments': ['sponsor'],
        'remove_ranges': [],
        'sponsorblock_chapter_title': '[SponsorBlock]: %(category_names)l',
        'force_keyframes': False,
    }]
}

XAttrMetadata

Write metadata to extended file attributes.
ydl_opts = {
    'postprocessors': [{
        'key': 'XAttrMetadata',
    }]
}

MoveFilesAfterDownload

Move files to a different location after download.
ydl_opts = {
    'postprocessors': [{
        'key': 'MoveFilesAfterDownload',
    }]
}

Chaining Post-Processors

You can chain multiple post-processors together:
ydl_opts = {
    'postprocessors': [
        # First: Convert subtitles
        {
            'key': 'FFmpegSubtitlesConvertor',
            'format': 'srt',
            'when': 'before_dl',
        },
        # Second: Embed subtitles
        {
            'key': 'FFmpegEmbedSubtitle',
        },
        # Third: Add metadata
        {
            'key': 'FFmpegMetadata',
            'add_metadata': True,
        },
        # Fourth: Embed thumbnail
        {
            'key': 'EmbedThumbnail',
        },
        # Finally: Extract audio
        {
            'key': 'FFmpegExtractAudio',
            'preferredcodec': 'mp3',
        },
    ],
    'writesubtitles': True,
    'writethumbnail': True,
}

Custom Post-Processor Example

Create a custom post-processor:
from yt_dlp.postprocessor.common import PostProcessor
import os

class MyCustomPP(PostProcessor):
    def run(self, info):
        # Get the downloaded file path
        filepath = info.get('filepath')
        
        # Perform some custom processing
        self.to_screen(f'Processing: {filepath}')
        
        # Example: Rename the file
        new_name = filepath.replace('.mp4', '_processed.mp4')
        os.rename(filepath, new_name)
        
        # Update the info dict
        info['filepath'] = new_name
        
        # Return files to delete and updated info
        return [], info

# Use it
with yt_dlp.YoutubeDL({}) as ydl:
    ydl.add_post_processor(MyCustomPP())
    ydl.download(['https://www.youtube.com/watch?v=dQw4w9WgXcQ'])

Progress Hooks for Post-Processors

Monitor post-processor progress:
def pp_hook(d):
    if d['status'] == 'started':
        print(f"Started post-processing with {d['postprocessor']}")
    elif d['status'] == 'finished':
        print(f"Finished post-processing")

with yt_dlp.YoutubeDL({'postprocessors': [...]}) as ydl:
    ydl.add_postprocessor_hook(pp_hook)
    ydl.download([url])

FFmpeg Location

Many post-processors require FFmpeg. Specify its location:
ydl_opts = {
    'ffmpeg_location': '/usr/local/bin/ffmpeg',  # Path to ffmpeg binary
    'postprocessors': [{
        'key': 'FFmpegExtractAudio',
        'preferredcodec': 'mp3',
    }]
}

Post-Processor Arguments

Pass additional arguments to post-processors:
ydl_opts = {
    'postprocessor_args': {
        'ffmpeg': ['-threads', '4'],  # FFmpeg arguments
        'ffmpeg_i': ['-hwaccel', 'auto'],  # FFmpeg input arguments
    },
    'postprocessors': [{
        'key': 'FFmpegExtractAudio',
        'preferredcodec': 'mp3',
    }]
}