Skip to content

pre_resolve_check_version

PreLaunchResolveCheckVersion

Bases: PreLaunchHook

Special hook to ensure installed version is compatible with AYON.

Source code in client/ayon_resolve/hooks/pre_resolve_check_version.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
class PreLaunchResolveCheckVersion(PreLaunchHook):
    """Special hook to ensure installed version is compatible with AYON.
    """
    order = 10
    app_groups = {"resolve"}
    launch_types = {LaunchTypes.local}

    def _find_readme(self, executable: str) -> Path | None:
        """Locate the ReadMe.html shipped with the Resolve install.
        - Windows: <install>/Resolve.exe -> <install>/Documents
        - Linux: /opt/resolve/bin/resolve -> /opt/resolve/Documents
        - macOS: .../Contents/MacOS/Resolve -> .../Contents/Documents
        """
        exe = Path(executable).resolve()
        for base in list(exe.parents)[:4]:
            candidate = base / "Documents" / "ReadMe.html"
            if candidate.is_file():
                return candidate
        return None

    @staticmethod
    def _parse_readme(readme: Path) -> tuple[str | None, bool]:
        """ Return version + is studio from readme file.
        """
        text = readme.read_text(encoding="utf-8", errors="replace")
        version = None
        match = re.search(
            r"<title>\s*DaVinci Resolve\s+(\d+\.\d[\d.]*)",
            text,
            re.IGNORECASE,
        )
        if not match:
            match = re.search(
                r"\bDaVinci Resolve(?:\s+Studio)?\s+(\d+\.\d[\d.]*)", text
            )
        if match:
            version = match.group(1).rstrip(".")

        is_studio = bool(
            re.search(r"About DaVinci Resolve\s+Studio\s+\d", text, re.IGNORECASE)
        )
        return version, is_studio

    @staticmethod
    def _is_supported(version: str | None, is_studio: bool) -> bool:
        """Free Resolve 21+ dropped external python scripting."""
        if is_studio:
            return True
        if not version:
            # Unknown version, do not block the launch.
            return True
        major = int(version.split(".")[0])
        return major < 21

    def _should_run_resolve_check_version(self) -> bool:
        resolve_settings = self.data["project_settings"]["resolve"]
        return resolve_settings["check_compatible_version_on_start"]

    def execute(self):
        if not self._should_run_resolve_check_version():
            return

        executable = self.launch_context.launch_args[0]

        # Detect Resolve version from the shipped ReadMe
        # Cannot rely on '-v' executable output, as it cannot be reliably captured.
        readme = self._find_readme(executable)
        if readme is None:
            self.log.warning(
                "Could not locate Resolve ReadMe.html "
                "from executable: %s.",
                executable,
            )
            return

        try:
            version, is_studio = self._parse_readme(readme)
        except OSError:
            self.log.warning(
                "Could not gather version from ReadMe.html at %s. "
                "Skipping compatibility check.",
                readme,
                exc_info=True,
            )
            return

        edition = "Studio" if is_studio else "Free"
        self.log.debug("Detected DaVinci Resolve %s %s", edition, version)

        if not self._is_supported(version, is_studio):
            raise ApplicationLaunchFailed(
                f"AYON is not supported in DaVinci Resolve {edition} {version}. "
                "Only the studio version supports python external scripting:\n"
                "https://forum.blackmagicdesign.com/viewtopic.php?f=21&t=239823"
            )