diff --git a/README.md b/README.md index 1b8680e33..f236ced15 100644 --- a/README.md +++ b/README.md @@ -948,10 +948,12 @@ You can also fork the project on github and run your fork's [build workflow](.gi "pre_process" (after extraction), "before_dl" (before video download), "post_process" (after video download; - default) or "after_move" (after moving file - to their final locations). This option can - be used multiple times to add different - postprocessors + default), "after_move" (after moving file + to their final locations), "after_video" + (after downloading and processing all + formats of a video), or "playlist" (end of + playlist). This option can be used multiple + times to add different postprocessors ## SponsorBlock Options: Make chapter entries for, or remove various segments (sponsor, diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index 5b285e1a1..57c56f882 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -1766,6 +1766,10 @@ class YoutubeDL(object): for tmpl in self.params['forceprint'].get('playlist', []): self._forceprint(tmpl, ie_result) + + for pp in self._pps['playlist']: + ie_result = self.run_pp(pp, ie_result) + self.to_screen('[download] Finished downloading playlist: %s' % playlist) return ie_result @@ -2564,6 +2568,10 @@ class YoutubeDL(object): new_info['__original_infodict'] = info_dict new_info.update(fmt) self.process_info(new_info) + + for pp in self._pps['after_video']: + info_dict = self.run_pp(pp, info_dict) + # We update the info dict with the selected best quality format (backwards compatibility) if formats_to_download: info_dict.update(formats_to_download[-1]) diff --git a/yt_dlp/options.py b/yt_dlp/options.py index f4e5d14df..a96fb82a2 100644 --- a/yt_dlp/options.py +++ b/yt_dlp/options.py @@ -1482,8 +1482,10 @@ def parseOpts(overrideArguments=None): 'ARGS are a semicolon ";" delimited list of NAME=VALUE. ' 'The "when" argument determines when the postprocessor is invoked. ' 'It can be one of "pre_process" (after extraction), ' - '"before_dl" (before video download), "post_process" (after video download; default) ' - 'or "after_move" (after moving file to their final locations). ' + '"before_dl" (before video download), "post_process" (after video download; default), ' + '"after_move" (after moving file to their final locations), ' + '"after_video" (after downloading and processing all formats of a video), ' + 'or "playlist" (end of playlist). ' 'This option can be used multiple times to add different postprocessors')) sponsorblock = optparse.OptionGroup(parser, 'SponsorBlock Options', description=( diff --git a/yt_dlp/utils.py b/yt_dlp/utils.py index f56129aa5..1fd85de8e 100644 --- a/yt_dlp/utils.py +++ b/yt_dlp/utils.py @@ -3036,7 +3036,7 @@ def qualities(quality_ids): return q -POSTPROCESS_WHEN = {'pre_process', 'before_dl', 'after_move', 'post_process'} +POSTPROCESS_WHEN = {'pre_process', 'before_dl', 'after_move', 'post_process', 'after_video', 'playlist'} DEFAULT_OUTTMPL = { diff --git a/ytdlp_plugins/postprocessor/sample.py b/ytdlp_plugins/postprocessor/sample.py index 6891280d5..551c4bfff 100644 --- a/ytdlp_plugins/postprocessor/sample.py +++ b/ytdlp_plugins/postprocessor/sample.py @@ -14,8 +14,10 @@ class SamplePluginPP(PostProcessor): # ℹ️ See docstring of yt_dlp.postprocessor.common.PostProcessor.run def run(self, info): - filepath = info.get('filepath') - if filepath: # PP was called after download (default) + if info.get('_type', 'video') != 'video': # PP was called for playlist + self.to_screen(f'Post-processing playlist {info.get("id")!r} with {self._kwargs}') + elif info.get('filepath'): # PP was called after download (default) + filepath = info.get('filepath') self.to_screen(f'Post-processed {filepath!r} with {self._kwargs}') else: # PP was called before actual download filepath = info.get('_filename')