def test_006_all_types(self):
        class TestClip(Clip):
            pass

        class T1(object):
            pass

        class T2(object):
            pass

        class TS1(object):
            pass

        class TS2(object):
            pass

        sr = Registry()
        sr.register(TestClip, TS1)
        sr.register(TestClip, TS2)

        self.registry.register(TestClip, T1)
        self.registry.register(TestClip, T2)
        self.assertEqual(list(self.registry.all_types()), [T1, T2])

        self.registry.add_subregistry(sr)
        self.assertEqual(list(self.registry.all_types()), [T1, T2, TS1, TS2])

        self.registry.remove_subregistry(sr)
        self.assertEqual(list(self.registry.all_types()), [T1, T2])
    def test_005_subregistry(self):
        class TestClip(Clip):
            pass

        subreg = Registry()
        subreg.register(TestClip, list)

        self.registry.add_subregistry(subreg)
        self.assertIs(self.registry.get_clip_type_for([]), TestClip)
        self.registry.remove_subregistry(subreg)
        self.assertIsNone(self.registry.get_clip_type_for([]))
class VapourSynth(Extension):
    """
    Entry-Point for VapourSynth support of Yuuno
    """
    _name = "VapourSynth"

    hook_messages: bool = CBool(
        True,
        help=
        """Redirect the message handler to this extension so other parts of Yuuno can handle it.
    
Note that this feature is disabled on vsscript-environments (vsedit, vspipe, etc.)""",
        config=True)
    yuv_matrix: str = Unicode(
        "709",
        help="The YUV-Matrix to use when converting to RGB",
        config=True)
    prefer_props: bool = CBool(
        True,
        help="If set, the data of the video node will be preferred.",
        config=True)
    merge_bands: bool = CBool(
        False,
        help=
        "Manually extract the planes and merge them using PIL. Defaults to automatically detecting the correct choice.",
        config=True)

    post_processor = Union(
        [DottedObjectName(), Callable()],
        allow_none=True,
        default_value=None,
        help=
        "Define a post-processor function. It gets an RGB24 clip and returns an RGB24 clip.",
        config=True)
    resizer: TUnion[str, TCallable] = Union(
        [DottedObjectName(), Callable()],
        default_value="resize.Spline36",
        help="""Defines the resizer to use when converting from YUV to RGB.
It is essentially a function which takes the same arguments as a VapourSynth internal
resizer. The clip is passed as the first argument.

Yuuno will first try to match it to a VapourSynth-function defined by a plugin before
attempting to import it from a module and treat it as a normal function.

If the passed object is a callable, it will just use the callable.
        """,
        config=True)

    push_values: bool = CBool(
        True,
        help=
        """Push vs and the current core instance onto the current environment.""",
        config=True)

    core_num_threads: int = CInt(
        -1,
        help=
        """The number of concurrent threads used by the core. Can be set the change the number.
Settings to a value less than one makes it default to the number of hardware threads.
    """,
        config=True)
    core_add_cache: bool = CBool(
        True,
        help=
        "For debugging purposes only. When set to `False` no caches will be automatically inserted between filters.",
        config=True)
    core_accept_lowercase: bool = CBool(
        False,
        help=
        "When set to `True` function name lookups in the core are case insensitive. Don't distribute scripts that need it to be set.",
        config=True)
    core_max_cache_size: int = CBool(
        None,
        allow_none=True,
        help=
        "Set the upper framebuffer cache size after which memory is aggressively freed. The value is in mediabytes.",
        config=True)

    vsscript_environment_wrap: bool = CBool(
        True,
        help=
        "Allow Yuuno to automatically wrap the internal frame-extractor into the current environment. Do not disable while running multiple cores at once.",
        config=True)
    raw_force_compat: bool = CBool(
        True, "In raw image exports, force Planar RGB output", config=True)

    log_handlers: TList[TCallable[[int, str], None]] = List(Callable())

    @default("log_handlers")
    def _default_log_handlers(self):
        return []

    def _update_core_values(name=None):
        def _func(self, change=None):
            core = get_proxy_or_core()

            if name is None:
                core.num_threads = self.core_num_threads
                core.add_cache = self.core_add_cache

                if hasattr(core, 'accept_lowercase'):
                    core.accept_lowercase = self.core_accept_lowercase

                # There is no obvious default for max_cache_size
                if self.core_max_cache_size is not None:
                    core.max_cache_size = self.core_max_cache_size

            elif hasattr(core, name):
                setattr(core, name, change.new)

        return _func

    update_core_values = _update_core_values()

    _observe_num_threads = observe("core_num_threads")(
        _update_core_values("num_threads"))
    _observe_add_cache = observe("core_add_cache")(
        _update_core_values("add_cache"))
    _observe_accept_lowercase = observe("core_accept_lowercase")(
        _update_core_values("accept_lowercase"))
    _observe_max_cache_size = observe("core_max_cache_size")(
        _update_core_values("max_cache_size"))

    @classmethod
    def is_supported(cls):
        return not Features.NOT_SUPPORTED

    @property
    def resize_filter(
            self) -> TCallable[['vs.VideoNode', 'int'], 'vs.VideoNode']:
        """
        Loads the resize-filter for any image operations.

        :return: The returned filter
        """
        from yuuno.vs.utils import filter_or_import
        if callable(self.resizer):
            return self.resizer
        return filter_or_import(self.resizer)

    @property
    def processor(self):
        if self.post_processor is None:
            return
        func = self.post_processor
        if not callable(func):
            func = import_item(func)
        return func

    def _on_vs_log(self, level: MessageLevel, message: str):
        try:
            for cb in self.log_handlers:
                cb(level, message)
        except Exception as e:
            import traceback
            print(
                "During logging of a vapoursynth-message, this exception occured:",
                file=sys.stderr)
            traceback.print_exc(file=sys.stderr)

    @property
    def can_hook_log(self):
        return self.hook_messages and is_single()

    def initialize_hook(self, vapoursynth):
        if self.can_hook_log:
            vapoursynth.set_message_handler(self._on_vs_log)
        elif self.hook_messages:
            self.parent.log.debug(
                "vsscript-Environment detected. Skipping hook on message-handler."
            )

    def initialize_namespace(self, vapoursynth):
        core = get_proxy_or_core()
        self.parent.namespace['vs'] = vapoursynth
        self.parent.namespace['core'] = core

    def initialize_registry(self):
        self.parent.log.debug("Registering wrappers.")
        from vapoursynth import VideoNode, VideoFrame
        from yuuno.vs.clip import VapourSynthClip, VapourSynthFrame
        from yuuno.vs.clip import VapourSynthAlphaClip

        # Detected VSScript.
        wrapperfunc = lambda cls: cls
        if self.script_manager is not None and self.vsscript_environment_wrap:
            wrapperfunc = self.script_manager.env_wrapper_for

        self.registry = Registry()
        self.registry.register(wrapperfunc(VapourSynthClip), VideoNode)
        self.registry.register(wrapperfunc(VapourSynthFrame), VideoFrame)
        self.registry.register(wrapperfunc(VapourSynthAlphaClip),
                               AlphaOutputClip)
        if Features.SUPPORT_ALPHA_OUTPUT_TUPLE:
            # Required so that IPython automatically supports alpha outputs
            from vapoursynth import AlphaOutputTuple
            self.registry.register(wrapperfunc(VapourSynthAlphaClip),
                                   AlphaOutputTuple)

        self.parent.registry.add_subregistry(self.registry)

    def initialize_multi_script(self):
        self.script_manager = None
        managers: Optional['MultiScriptExtension'] = self.parent.get_extension(
            'MultiScript')
        if managers is None:
            self.parent.log.debug("MultiScript not found. Skipping VSScript.")
            return

        managers.register_provider(
            'vapoursynth',
            ScriptProviderRegistration(
                providercls="yuuno.vs.provider.VSScriptProvider",
                extensions=[]))

        # Check support for vapoursynth/#389 at R44
        if not Features.EXPORT_VSSCRIPT_ENV:
            self.parent.log.info("Yuuno doesn't support VSScript for VS<R44")
            return

        self.parent.log.debug("Enabling VSScript.")
        from yuuno.vs.vsscript.script import VSScriptManager
        from yuuno.vs.vsscript.vs_capi import enable_vsscript
        enable_vsscript()

        self.script_manager = VSScriptManager()
        managers.register_manager('VSScript', self.script_manager)
        self.parent.log.debug("VSScript enabled.")

    def initialize(self):
        import vapoursynth

        self.initialize_hook(vapoursynth)
        self.initialize_namespace(vapoursynth)
        self.initialize_multi_script()
        self.initialize_registry()

        if self.script_manager is None:
            self.update_core_values()

    def deinitialize(self):
        self.parent.registry.remove_subregistry(self.registry)
        del self.parent.namespace['vs']
        del self.parent.namespace['core']

        if self.can_hook_log:
            import vapoursynth
            vapoursynth.set_message_handler(None)

        if Features.EXPORT_VSSCRIPT_ENV and self.script_manager is not None:
            self.script_manager.disable()
class TestRegistry(unittest.TestCase):
    def setUp(self):
        self.registry = Registry()

    def tearDown(self):
        del self.registry

    def test_001_register_raw(self):
        class TestClip(Clip):
            pass

        self.registry.register(TestClip, int)
        self.assertDictEqual(self.registry.clip_types, {int: TestClip})

    def test_002_register_decorator(self):
        @self.registry.register(int)
        class TestClip(Clip):
            pass

        self.assertDictEqual(self.registry.clip_types, {int: TestClip})

    def test_003_clip_type_correct_subclass(self):
        class A:
            pass

        class B(A):
            pass

        class C:
            pass

        @self.registry.register(A)
        class TestClip(Clip):
            pass

        self.assertEqual(self.registry.get_clip_type_for(A()), TestClip)
        self.assertEqual(self.registry.get_clip_type_for(B()), TestClip)
        self.assertEqual(self.registry.get_clip_type_for(C()), None)

    def test_004_clip_correct_wrapping(self):
        class TestClip(Clip):
            def __init__(self, item):
                self.base = item

        self.registry.register(TestClip, list)

        ref_value = []
        item = self.registry.wrap(ref_value)

        self.assertIs(item.base, ref_value)

        ref_invalid = {}
        with self.assertRaises(ValueError):
            self.registry.wrap(ref_invalid)

    def test_005_subregistry(self):
        class TestClip(Clip):
            pass

        subreg = Registry()
        subreg.register(TestClip, list)

        self.registry.add_subregistry(subreg)
        self.assertIs(self.registry.get_clip_type_for([]), TestClip)
        self.registry.remove_subregistry(subreg)
        self.assertIsNone(self.registry.get_clip_type_for([]))

    def test_006_all_types(self):
        class TestClip(Clip):
            pass

        class T1(object):
            pass

        class T2(object):
            pass

        class TS1(object):
            pass

        class TS2(object):
            pass

        sr = Registry()
        sr.register(TestClip, TS1)
        sr.register(TestClip, TS2)

        self.registry.register(TestClip, T1)
        self.registry.register(TestClip, T2)
        self.assertEqual(list(self.registry.all_types()), [T1, T2])

        self.registry.add_subregistry(sr)
        self.assertEqual(list(self.registry.all_types()), [T1, T2, TS1, TS2])

        self.registry.remove_subregistry(sr)
        self.assertEqual(list(self.registry.all_types()), [T1, T2])