From 97684b0f2c6f7b067e78b90e12986d5570c89e01 Mon Sep 17 00:00:00 2001 From: coletdjnz Date: Sun, 20 Oct 2024 14:06:33 +1300 Subject: [PATCH] Add --no-plugins --- README.md | 6 ++++++ test/test_plugins.py | 31 +++++++++++++++++++++++++++++-- yt_dlp/YoutubeDL.py | 6 +++++- yt_dlp/__init__.py | 7 ++++++- yt_dlp/_globals.py | 2 ++ yt_dlp/options.py | 7 +++++++ yt_dlp/plugins.py | 17 +++++++++++++++-- 7 files changed, 70 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1cafe51d5..875450516 100644 --- a/README.md +++ b/README.md @@ -371,6 +371,12 @@ If you fork the project on GitHub, you can run your fork's [build workflow](.git sequences). Use "auto-tty" or "no_color-tty" to decide based on terminal support only. Can be used multiple times + --plugin-dirs PATH Directory to search for plugins. Can be used + multiple times to add multiple directories. + Add "no-external" to disable searching + default external plugin directories (outside + of python environment) + --no-plugins Do not load plugins --compat-options OPTS Options that can help keep compatibility with youtube-dl or youtube-dlc configurations by reverting some of the diff --git a/test/test_plugins.py b/test/test_plugins.py index 00a4c6506..19d4c9a81 100644 --- a/test/test_plugins.py +++ b/test/test_plugins.py @@ -5,7 +5,8 @@ import sys import unittest from pathlib import Path import yt_dlp._globals -from yt_dlp.plugins import set_plugin_dirs, add_plugin_dirs, PluginDirs +from yt_dlp.plugins import set_plugin_dirs, add_plugin_dirs, PluginDirs, disable_plugins +from yt_dlp.utils import YoutubeDLError sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) TEST_DATA_DIR = Path(os.path.dirname(os.path.abspath(__file__)), 'testdata') @@ -13,7 +14,7 @@ sys.path.append(str(TEST_DATA_DIR)) importlib.invalidate_caches() from yt_dlp.plugins import PACKAGE_NAME, PluginSpec, directories, load_plugins, load_all_plugins, register_plugin_spec -from yt_dlp._globals import extractors, postprocessors, plugin_dirs, plugin_ies, plugin_pps, all_plugins_loaded, plugin_specs +from yt_dlp._globals import extractors, postprocessors, plugin_dirs, plugin_ies, plugin_pps, all_plugins_loaded, plugin_specs, plugins_enabled EXTRACTOR_PLUGIN_SPEC = PluginSpec( @@ -41,6 +42,7 @@ class TestPlugins(unittest.TestCase): plugin_dirs.set((PluginDirs.DEFAULT_EXTERNAL,)) plugin_specs.set({}) all_plugins_loaded.set(False) + plugins_enabled.set(True) importlib.invalidate_caches() # Clearing override plugins is probably difficult for module_name in tuple(sys.modules): @@ -199,6 +201,31 @@ class TestPlugins(unittest.TestCase): self.assertIn(f'{PACKAGE_NAME}.extractor.package', sys.modules.keys()) self.assertIn('PackagePluginIE', plugin_ies.get()) + def test_disable_plugins(self): + disable_plugins() + ies = load_plugins(EXTRACTOR_PLUGIN_SPEC) + self.assertEqual(ies, {}) + self.assertNotIn(f'{PACKAGE_NAME}.extractor.normal', sys.modules.keys()) + self.assertNotIn('NormalPluginIE', plugin_ies.get()) + + pps = load_plugins(POSTPROCESSOR_PLUGIN_SPEC) + self.assertEqual(pps, {}) + self.assertNotIn(f'{PACKAGE_NAME}.postprocessor.normal', sys.modules.keys()) + self.assertNotIn('NormalPluginPP', plugin_pps.get()) + + def test_disable_plugins_already_loaded(self): + register_plugin_spec(EXTRACTOR_PLUGIN_SPEC) + register_plugin_spec(POSTPROCESSOR_PLUGIN_SPEC) + load_all_plugins() + + with self.assertRaises(YoutubeDLError): + disable_plugins() + + self.assertTrue(plugins_enabled.get()) + + ies = load_plugins(EXTRACTOR_PLUGIN_SPEC) + self.assertIn('NormalPluginIE', ies) + if __name__ == '__main__': unittest.main() diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index 94b62138a..65675e859 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -40,6 +40,7 @@ from ._globals import ( plugin_overrides, plugin_pps, all_plugins_loaded, + plugins_enabled, ) from .minicurses import format_text from .networking import HEADRequest, Request, RequestDirector @@ -4088,8 +4089,11 @@ class YoutubeDL: continue write_debug(f'{plugin_type} Plugins: {", ".join(sorted(display_list))}') + if not plugins_enabled.get(): + write_debug('Plugins are disabled') + plugin_dirs = plugin_directories() - if plugin_dirs: + if plugin_dirs and plugins_enabled.get(): write_debug(f'Plugin directories: {plugin_dirs}') # Not implemented diff --git a/yt_dlp/__init__.py b/yt_dlp/__init__.py index f7948a233..080ca72e3 100644 --- a/yt_dlp/__init__.py +++ b/yt_dlp/__init__.py @@ -23,6 +23,7 @@ from .networking.impersonate import ImpersonateTarget from ._globals import IN_CLI as _IN_CLI from .options import parseOpts from .plugins import load_all_plugins as _load_all_plugins +from .plugins import disable_plugins as _disable_plugins from .plugins import PluginDirs as _PluginDirs from .plugins import set_plugin_dirs as _set_plugin_dirs from .postprocessor import ( @@ -989,7 +990,11 @@ def _real_main(argv=None): # load all plugins into the global lookup _set_plugin_dirs(*opts.plugin_dirs) - _load_all_plugins() + + if not opts.plugins_enabled: + _disable_plugins() + else: + _load_all_plugins() with YoutubeDL(ydl_opts) as ydl: pre_process = opts.update_self or opts.rm_cachedir diff --git a/yt_dlp/_globals.py b/yt_dlp/_globals.py index 4eae01fb6..968e3e48a 100644 --- a/yt_dlp/_globals.py +++ b/yt_dlp/_globals.py @@ -17,6 +17,8 @@ plugin_specs = ContextVar('plugin_specs', default={}) # Whether plugins have been loaded once all_plugins_loaded = ContextVar('all_plugins_loaded', default=False) +plugins_enabled = ContextVar('plugins_enabled', default=True) + plugin_dirs = ContextVar('plugin_dirs', default=('external', )) plugin_ies = ContextVar('plugin_ies', default={}) plugin_overrides = ContextVar('plugin_overrides', default=defaultdict(list)) diff --git a/yt_dlp/options.py b/yt_dlp/options.py index 43c2760ca..a7cce67bc 100644 --- a/yt_dlp/options.py +++ b/yt_dlp/options.py @@ -475,6 +475,13 @@ def create_parser(): 'Add "no-external" to disable searching default external plugin directories (outside of python environment)' ), ) + general.add_option( + '--no-plugins', + dest='plugins_enabled', + action='store_false', + default=True, + help='Do not load plugins', + ) general.add_option( '--compat-options', metavar='OPTS', dest='compat_opts', default=set(), type='str', diff --git a/yt_dlp/plugins.py b/yt_dlp/plugins.py index 952816a62..3d6d776d0 100644 --- a/yt_dlp/plugins.py +++ b/yt_dlp/plugins.py @@ -20,6 +20,7 @@ from ._globals import ( plugin_dirs, all_plugins_loaded, plugin_specs, + plugins_enabled, ) from .compat import functools # isort: split @@ -29,7 +30,7 @@ from .utils import ( get_user_config_dirs, merge_dicts, orderedSet, - write_string, + write_string, YoutubeDLError, ) PACKAGE_NAME = 'yt_dlp_plugins' @@ -46,6 +47,7 @@ __all__ = [ 'register_plugin_spec', 'add_plugin_dirs', 'set_plugin_dirs', + 'disable_plugins', 'PluginDirs', 'get_plugin_spec', 'PACKAGE_NAME', @@ -197,7 +199,7 @@ def get_regular_classes(module, module_name, suffix): def load_plugins(plugin_spec: PluginSpec): name, suffix = plugin_spec.module_name, plugin_spec.suffix regular_classes = {} - if os.environ.get('YTDLP_NO_PLUGINS'): + if os.environ.get('YTDLP_NO_PLUGINS') or plugins_enabled.get() is False: return regular_classes for finder, module_name, _ in iter_modules(name): @@ -268,3 +270,14 @@ def set_plugin_dirs(*paths): def get_plugin_spec(module_name): return plugin_specs.get().get(module_name) + + +def disable_plugins(): + if ( + all_plugins_loaded.get() + or any(len(plugin_spec.plugin_destination.get()) != 0 for plugin_spec in plugin_specs.get().values()) + ): + # note: we can't detect all cases when plugins are loaded (e.g. if spec isn't registered) + raise YoutubeDLError('Plugins have already been loaded. Cannot disable plugins after loading plugins.') + + plugins_enabled.set(False)