diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index 4330006cc..491e02dec 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -90,6 +90,7 @@ from .utils import ( args_to_str, bug_reports_message, date_from_str, + deprecation_warning, determine_ext, determine_protocol, encode_compat_str, @@ -631,7 +632,7 @@ class YoutubeDL: for msg in self.params.get('_warnings', []): self.report_warning(msg) for msg in self.params.get('_deprecation_warnings', []): - self.deprecation_warning(msg) + self.deprecated_feature(msg) self.params['compat_opts'] = set(self.params.get('compat_opts', ())) if 'list-formats' in self.params['compat_opts']: @@ -835,9 +836,11 @@ class YoutubeDL: def to_stdout(self, message, skip_eol=False, quiet=None): """Print message to stdout""" if quiet is not None: - self.deprecation_warning('"YoutubeDL.to_stdout" no longer accepts the argument quiet. Use "YoutubeDL.to_screen" instead') + self.deprecation_warning('"YoutubeDL.to_stdout" no longer accepts the argument quiet. ' + 'Use "YoutubeDL.to_screen" instead') if skip_eol is not False: - self.deprecation_warning('"YoutubeDL.to_stdout" no longer accepts the argument skip_eol. Use "YoutubeDL.to_screen" instead') + self.deprecation_warning('"YoutubeDL.to_stdout" no longer accepts the argument skip_eol. ' + 'Use "YoutubeDL.to_screen" instead') self._write_string(f'{self._bidi_workaround(message)}\n', self._out_files.out) def to_screen(self, message, skip_eol=False, quiet=None): @@ -973,11 +976,14 @@ class YoutubeDL: return self.to_stderr(f'{self._format_err("WARNING:", self.Styles.WARNING)} {message}', only_once) - def deprecation_warning(self, message): + def deprecation_warning(self, message, *, stacklevel=0): + deprecation_warning( + message, stacklevel=stacklevel + 1, printer=self.report_error, is_error=False) + + def deprecated_feature(self, message): if self.params.get('logger') is not None: - self.params['logger'].warning(f'DeprecationWarning: {message}') - else: - self.to_stderr(f'{self._format_err("DeprecationWarning:", self.Styles.ERROR)} {message}', True) + self.params['logger'].warning(f'Deprecated Feature: {message}') + self.to_stderr(f'{self._format_err("Deprecated Feature:", self.Styles.ERROR)} {message}', True) def report_error(self, message, *args, **kwargs): ''' diff --git a/yt_dlp/__init__.py b/yt_dlp/__init__.py index e9234e6f4..3dc9b6e56 100644 --- a/yt_dlp/__init__.py +++ b/yt_dlp/__init__.py @@ -63,6 +63,8 @@ from .utils import ( ) from .YoutubeDL import YoutubeDL +_IN_CLI = False + def _exit(status=0, *args): for msg in args: diff --git a/yt_dlp/__main__.py b/yt_dlp/__main__.py index ff5d71d3c..895918c27 100644 --- a/yt_dlp/__main__.py +++ b/yt_dlp/__main__.py @@ -14,4 +14,5 @@ if __package__ is None and not hasattr(sys, 'frozen'): import yt_dlp if __name__ == '__main__': + yt_dlp._IN_CLI = True yt_dlp.main() diff --git a/yt_dlp/downloader/common.py b/yt_dlp/downloader/common.py index 4962c0cf8..9ade4269e 100644 --- a/yt_dlp/downloader/common.py +++ b/yt_dlp/downloader/common.py @@ -92,6 +92,7 @@ class FileDownloader: for func in ( 'deprecation_warning', + 'deprecated_feature', 'report_error', 'report_file_already_downloaded', 'report_warning', diff --git a/yt_dlp/downloader/fragment.py b/yt_dlp/downloader/fragment.py index b1d3127c3..a5d70d0d4 100644 --- a/yt_dlp/downloader/fragment.py +++ b/yt_dlp/downloader/fragment.py @@ -65,8 +65,8 @@ class FragmentFD(FileDownloader): """ def report_retry_fragment(self, err, frag_index, count, retries): - self.deprecation_warning( - 'yt_dlp.downloader.FragmentFD.report_retry_fragment is deprecated. Use yt_dlp.downloader.FileDownloader.report_retry instead') + self.deprecation_warning('yt_dlp.downloader.FragmentFD.report_retry_fragment is deprecated. ' + 'Use yt_dlp.downloader.FileDownloader.report_retry instead') return self.report_retry(err, count, retries, frag_index) def report_skip_fragment(self, frag_index, err=None): diff --git a/yt_dlp/extractor/common.py b/yt_dlp/extractor/common.py index 6337a13a4..f950d28ed 100644 --- a/yt_dlp/extractor/common.py +++ b/yt_dlp/extractor/common.py @@ -1766,9 +1766,8 @@ class InfoExtractor: if field not in self.settings: if key in ('forced', 'priority'): return False - self.ydl.deprecation_warning( - f'Using arbitrary fields ({field}) for format sorting is deprecated ' - 'and may be removed in a future version') + self.ydl.deprecated_feature(f'Using arbitrary fields ({field}) for format sorting is ' + 'deprecated and may be removed in a future version') self.settings[field] = {} propObj = self.settings[field] if key not in propObj: @@ -1853,9 +1852,8 @@ class InfoExtractor: if self._get_field_setting(field, 'type') == 'alias': alias, field = field, self._get_field_setting(field, 'field') if self._get_field_setting(alias, 'deprecated'): - self.ydl.deprecation_warning( - f'Format sorting alias {alias} is deprecated ' - f'and may be removed in a future version. Please use {field} instead') + self.ydl.deprecated_feature(f'Format sorting alias {alias} is deprecated and may ' + 'be removed in a future version. Please use {field} instead') reverse = match.group('reverse') is not None closest = match.group('separator') == '~' limit_text = match.group('limit') diff --git a/yt_dlp/extractor/youtube.py b/yt_dlp/extractor/youtube.py index 0498f980d..ee9cce16e 100644 --- a/yt_dlp/extractor/youtube.py +++ b/yt_dlp/extractor/youtube.py @@ -2959,8 +2959,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor): # YouTube comments have a max depth of 2 max_depth = int_or_none(get_single_config_arg('max_comment_depth')) if max_depth: - self._downloader.deprecation_warning( - '[youtube] max_comment_depth extractor argument is deprecated. Set max replies in the max-comments extractor argument instead.') + self._downloader.deprecated_feature('[youtube] max_comment_depth extractor argument is deprecated. ' + 'Set max replies in the max-comments extractor argument instead') if max_depth == 1 and parent: return diff --git a/yt_dlp/options.py b/yt_dlp/options.py index a0db9bc02..e66738448 100644 --- a/yt_dlp/options.py +++ b/yt_dlp/options.py @@ -25,6 +25,7 @@ from .utils import ( OUTTMPL_TYPES, POSTPROCESS_WHEN, Config, + deprecation_warning, expand_path, format_field, get_executable_path, @@ -1864,7 +1865,6 @@ def create_parser(): def _hide_login_info(opts): - write_string( - 'DeprecationWarning: "yt_dlp.options._hide_login_info" is deprecated and may be removed in a future version. ' - 'Use "yt_dlp.utils.Config.hide_login_info" instead\n') + deprecation_warning(f'"{__name__}._hide_login_info" is deprecated and may be removed ' + 'in a future version. Use "yt_dlp.utils.Config.hide_login_info" instead') return Config.hide_login_info(opts) diff --git a/yt_dlp/postprocessor/common.py b/yt_dlp/postprocessor/common.py index 20d890df0..44feda427 100644 --- a/yt_dlp/postprocessor/common.py +++ b/yt_dlp/postprocessor/common.py @@ -7,10 +7,10 @@ from ..utils import ( PostProcessingError, RetryManager, _configuration_args, + deprecation_warning, encodeFilename, network_exceptions, sanitized_Request, - write_string, ) @@ -73,10 +73,14 @@ class PostProcessor(metaclass=PostProcessorMetaClass): if self._downloader: return self._downloader.report_warning(text, *args, **kwargs) - def deprecation_warning(self, text): + def deprecation_warning(self, msg): + warn = getattr(self._downloader, 'deprecation_warning', deprecation_warning) + return warn(msg, stacklevel=1) + + def deprecated_feature(self, msg): if self._downloader: - return self._downloader.deprecation_warning(text) - write_string(f'DeprecationWarning: {text}') + return self._downloader.deprecated_feature(msg) + return deprecation_warning(msg, stacklevel=1) def report_error(self, text, *args, **kwargs): self.deprecation_warning('"yt_dlp.postprocessor.PostProcessor.report_error" is deprecated. ' diff --git a/yt_dlp/postprocessor/ffmpeg.py b/yt_dlp/postprocessor/ffmpeg.py index a1f367ae4..76f9d29c5 100644 --- a/yt_dlp/postprocessor/ffmpeg.py +++ b/yt_dlp/postprocessor/ffmpeg.py @@ -15,6 +15,7 @@ from ..utils import ( Popen, PostProcessingError, _get_exe_version_output, + deprecation_warning, detect_exe_version, determine_ext, dfxp2srt, @@ -30,7 +31,6 @@ from ..utils import ( traverse_obj, variadic, write_json_file, - write_string, ) EXT_TO_OUT_FORMATS = { @@ -187,8 +187,8 @@ class FFmpegPostProcessor(PostProcessor): else: self.probe_basename = basename if basename == self._ffmpeg_to_avconv[kind]: - self.deprecation_warning( - f'Support for {self._ffmpeg_to_avconv[kind]} is deprecated and may be removed in a future version. Use {kind} instead') + self.deprecated_feature(f'Support for {self._ffmpeg_to_avconv[kind]} is deprecated and ' + f'may be removed in a future version. Use {kind} instead') return version @functools.cached_property @@ -1064,7 +1064,7 @@ class FFmpegThumbnailsConvertorPP(FFmpegPostProcessor): @classmethod def is_webp(cls, path): - write_string(f'DeprecationWarning: {cls.__module__}.{cls.__name__}.is_webp is deprecated') + deprecation_warning(f'{cls.__module__}.{cls.__name__}.is_webp is deprecated') return imghdr.what(path) == 'webp' def fixup_webp(self, info, idx=-1): diff --git a/yt_dlp/update.py b/yt_dlp/update.py index e82cdf451..026bc12aa 100644 --- a/yt_dlp/update.py +++ b/yt_dlp/update.py @@ -14,6 +14,7 @@ from .compat import compat_realpath, compat_shlex_quote from .utils import ( Popen, cached_method, + deprecation_warning, shell_quote, system_identifier, traverse_obj, @@ -302,11 +303,8 @@ def run_update(ydl): def update_self(to_screen, verbose, opener): import traceback - from .utils import write_string - - write_string( - 'DeprecationWarning: "yt_dlp.update.update_self" is deprecated and may be removed in a future version. ' - 'Use "yt_dlp.update.run_update(ydl)" instead\n') + deprecation_warning(f'"{__name__}.update_self" is deprecated and may be removed ' + f'in a future version. Use "{__name__}.run_update(ydl)" instead') printfn = to_screen diff --git a/yt_dlp/utils.py b/yt_dlp/utils.py index 957c7eaa7..da2d042cb 100644 --- a/yt_dlp/utils.py +++ b/yt_dlp/utils.py @@ -828,8 +828,8 @@ def escapeHTML(text): def process_communicate_or_kill(p, *args, **kwargs): - write_string('DeprecationWarning: yt_dlp.utils.process_communicate_or_kill is deprecated ' - 'and may be removed in a future version. Use yt_dlp.utils.Popen.communicate_or_kill instead') + deprecation_warning(f'"{__name__}.process_communicate_or_kill" is deprecated and may be removed ' + f'in a future version. Use "{__name__}.Popen.communicate_or_kill" instead') return Popen.communicate_or_kill(p, *args, **kwargs) @@ -1934,7 +1934,7 @@ class DateRange: def platform_name(): """ Returns the platform name as a str """ - write_string('DeprecationWarning: yt_dlp.utils.platform_name is deprecated, use platform.platform instead') + deprecation_warning(f'"{__name__}.platform_name" is deprecated, use "platform.platform" instead') return platform.platform() @@ -1980,6 +1980,23 @@ def write_string(s, out=None, encoding=None): out.flush() +def deprecation_warning(msg, *, printer=None, stacklevel=0, **kwargs): + from . import _IN_CLI + if _IN_CLI: + if msg in deprecation_warning._cache: + return + deprecation_warning._cache.add(msg) + if printer: + return printer(f'{msg}{bug_reports_message()}', **kwargs) + return write_string(f'ERROR: {msg}{bug_reports_message()}\n', **kwargs) + else: + import warnings + warnings.warn(DeprecationWarning(msg), stacklevel=stacklevel + 3) + + +deprecation_warning._cache = set() + + def bytes_to_intlist(bs): if not bs: return [] @@ -4862,8 +4879,8 @@ def decode_base_n(string, n=None, table=None): def decode_base(value, digits): - write_string('DeprecationWarning: yt_dlp.utils.decode_base is deprecated ' - 'and may be removed in a future version. Use yt_dlp.decode_base_n instead') + deprecation_warning(f'{__name__}.decode_base is deprecated and may be removed ' + f'in a future version. Use {__name__}.decode_base_n instead') return decode_base_n(value, table=digits) @@ -5332,8 +5349,8 @@ def traverse_obj( def traverse_dict(dictn, keys, casesense=True): - write_string('DeprecationWarning: yt_dlp.utils.traverse_dict is deprecated ' - 'and may be removed in a future version. Use yt_dlp.utils.traverse_obj instead') + deprecation_warning(f'"{__name__}.traverse_dict" is deprecated and may be removed ' + f'in a future version. Use "{__name__}.traverse_obj" instead') return traverse_obj(dictn, keys, casesense=casesense, is_user_input=True, traverse_string=True)