Skip to content

core

This class provides all photoshop API core functions.

Photoshop

Core API for all photoshop objects.

Source code in photoshop/api/_core.py
 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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
class Photoshop:
    """Core API for all photoshop objects."""

    _root = "Photoshop"
    _reg_path = "SOFTWARE\\Adobe\\Photoshop"
    object_name: str = "Application"

    def __init__(self, ps_version: Optional[str] = None, parent: Any = None):
        """
        Initialize the Photoshop core object.

        Args:
            ps_version: Optional, Photoshop version to look for explicitly in registry.
            parent: Optional, parent instance to use as app object.
        """
        # Establish the initial app and program ID
        ps_version = os.getenv("PS_VERSION", ps_version)
        self._app_id = PHOTOSHOP_VERSION_MAPPINGS.get(ps_version, "")
        self._has_parent, self.adobe, self.app = False, None, None

        # Store current photoshop version
        if ps_version:
            os.environ["PS_VERSION"] = ps_version

        # Establish the application object using provided version ID
        if self.app_id:
            self.app = self._get_application_object([self.app_id])
            if not self.app:
                # Attempt unsuccessful
                self._logger.debug(
                    f"Unable to retrieve Photoshop object '{self.typename}' using version '{ps_version}'."
                )

        # Look for version ID in registry data
        if not self.app:
            versions = self._get_photoshop_versions()
            self.app = self._get_application_object(versions)
            if not self.app:
                # All attempts exhausted
                raise PhotoshopPythonAPIError("Please check if you have Photoshop installed correctly.")

        # Add the parent app object
        if parent:
            self.adobe = self.app
            self.app = parent
            self._has_parent = True

    def __repr__(self):
        return self

    def __call__(self, *args, **kwargs):
        return self.app

    def __str__(self):
        return f"{self.__class__.__name__} <{self.program_name}>"

    def __getattribute__(self, item):
        try:
            return super().__getattribute__(item)
        except AttributeError:
            return getattr(self.app, item)

    """
    * Debug Logger
    """

    @cached_property
    def _debug(self) -> bool:
        """bool: Enable DEBUG level in logger if PS_DEBUG environment variable is truthy."""
        return bool(os.getenv("PS_DEBUG", "False").lower() in ["y", "t", "on", "yes", "true"])

    @cached_property
    def _logger(self) -> Logger:
        """Logger: Logging object for warning output."""
        logr = getLogger("photoshop")
        logr.setLevel(DEBUG if self._debug else CRITICAL)
        return logr

    """
    * Properties
    """

    @property
    def typename(self) -> str:
        """str: Current typename."""
        return self.__class__.__name__

    @property
    def program_name(self) -> str:
        """str: Formatted program name found in the Windows Classes registry, e.g. Photoshop.Application.140.

        Examples:
            - Photoshop.ActionDescriptor
            - Photoshop.ActionDescriptor.140
            - Photoshop.ActionList
            - Photoshop.ActionList.140
            - Photoshop.ActionReference
            - Photoshop.ActionReference.140
            - Photoshop.Application
            - Photoshop.Application.140
            - Photoshop.BatchOptions
            - Photoshop.BatchOptions.140
            - Photoshop.BitmapConversionOptions
            - Photoshop.BMPSaveOptions
            - Photoshop.BMPSaveOptions.140
            - Photoshop.CameraRAWOpenOptions
            - Photoshop.CameraRAWOpenOptions.140
        """
        if self.app_id:
            return f"{self._root}.{self.object_name}.{self.app_id}"
        return f"{self._root}.{self.object_name}"

    @property
    def app_id(self) -> str:
        """str: Photoshop version ID from Windows registry, e.g. 180."""
        return self._app_id

    @app_id.setter
    def app_id(self, value: str):
        self._app_id = value

    """
    * Private Methods
    """

    def _flag_as_method(self, *names: str):
        """
        * This is a hack for Photoshop's broken COM implementation.
        * Photoshop does not implement 'IDispatch::GetTypeInfo', so when
        getting a field from the COM object, comtypes will first try
        to fetch it as a property, then treat it as a method if it fails.
        * In this case, Photoshop does not return the proper error code, since it
        blindly treats the property getter as a method call.
        * Fortunately, comtypes provides a way to explicitly flag methods.
        """
        if isinstance(self.app, FullyDynamicDispatch):
            self.app._FlagAsMethod(*names)

    def _get_photoshop_versions(self) -> List[str]:
        """Retrieve a list of Photoshop version ID's from registry."""
        with suppress(OSError, IndexError):
            key = self._open_key(self._reg_path)
            key_count = winreg.QueryInfoKey(key)[0]
            versions = [winreg.EnumKey(key, i).split(".")[0] for i in range(key_count)]
            # Sort from latest version to oldest, use blank version as a fallback
            return [*sorted(versions, reverse=True), ""]
        self._logger.debug("Unable to find Photoshop version number in HKEY_LOCAL_MACHINE registry!")
        return []

    def _get_application_object(self, versions: List[str] = None) -> Optional[Dispatch]:
        """
        Try each version string until a valid Photoshop application Dispatch object is returned.

        Args:
            versions: List of Photoshop version ID's found in registry.

        Returns:
            Photoshop application Dispatch object.

        Raises:
            OSError: If a Dispatch object wasn't resolved.
        """
        for v in versions:
            self.app_id = v
            with suppress(OSError):
                return CreateObject(self.program_name, dynamic=True)
        return

    """
    * Public Methods
    """

    def get_application_path(self) -> str:
        """str: The absolute path of Photoshop installed location."""
        key = self.open_key(f"{self._reg_path}\\{self.program_id}")
        return winreg.QueryValueEx(key, "ApplicationPath")[0]

    def get_plugin_path(self) -> str:
        """str: The absolute plugin path of Photoshop."""
        return os.path.join(self.application_path, "Plug-ins")

    def get_presets_path(self) -> str:
        """str: The absolute presets path of Photoshop."""
        return os.path.join(self.application_path, "Presets")

    def get_script_path(self) -> str:
        """str: The absolute scripts path of Photoshop."""
        return os.path.join(self.presets_path, "Scripts")

    def eval_javascript(self, javascript: str, Arguments: Any = None, ExecutionMode: Any = None) -> str:
        """Instruct the application to execute javascript code."""
        executor = self.adobe if self._has_parent else self.app
        return executor.doJavaScript(javascript, Arguments, ExecutionMode)

    """
    * Private Static Methods
    """

    @staticmethod
    def _open_key(key: str) -> winreg.HKEYType:
        """Open the register key.

        Args:
            key: Photoshop application key path.

        Returns:
            The handle to the specified key.

        Raises:
            OSError: if registry key cannot be read.
        """
        machine_type = platform.machine()
        mappings = {"AMD64": winreg.KEY_WOW64_64KEY}
        access = winreg.KEY_READ | mappings.get(machine_type, winreg.KEY_WOW64_32KEY)
        try:
            return winreg.OpenKey(key=winreg.HKEY_LOCAL_MACHINE, sub_key=key, access=access)
        except FileNotFoundError as err:
            raise OSError(
                "Failed to read the registration: <{path}>\n"
                "Please check if you have Photoshop installed correctly.".format(path=f"HKEY_LOCAL_MACHINE\\{key}")
            ) from err

app_id: str property writable

program_name: str property

Examples:

  • Photoshop.ActionDescriptor
  • Photoshop.ActionDescriptor.140
  • Photoshop.ActionList
  • Photoshop.ActionList.140
  • Photoshop.ActionReference
  • Photoshop.ActionReference.140
  • Photoshop.Application
  • Photoshop.Application.140
  • Photoshop.BatchOptions
  • Photoshop.BatchOptions.140
  • Photoshop.BitmapConversionOptions
  • Photoshop.BMPSaveOptions
  • Photoshop.BMPSaveOptions.140
  • Photoshop.CameraRAWOpenOptions
  • Photoshop.CameraRAWOpenOptions.140

typename: str property

__init__(ps_version=None, parent=None)

Initialize the Photoshop core object.

Parameters:

Name Type Description Default
ps_version Optional[str]

Optional, Photoshop version to look for explicitly in registry.

None
parent Any

Optional, parent instance to use as app object.

None
Source code in photoshop/api/_core.py
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
def __init__(self, ps_version: Optional[str] = None, parent: Any = None):
    """
    Initialize the Photoshop core object.

    Args:
        ps_version: Optional, Photoshop version to look for explicitly in registry.
        parent: Optional, parent instance to use as app object.
    """
    # Establish the initial app and program ID
    ps_version = os.getenv("PS_VERSION", ps_version)
    self._app_id = PHOTOSHOP_VERSION_MAPPINGS.get(ps_version, "")
    self._has_parent, self.adobe, self.app = False, None, None

    # Store current photoshop version
    if ps_version:
        os.environ["PS_VERSION"] = ps_version

    # Establish the application object using provided version ID
    if self.app_id:
        self.app = self._get_application_object([self.app_id])
        if not self.app:
            # Attempt unsuccessful
            self._logger.debug(
                f"Unable to retrieve Photoshop object '{self.typename}' using version '{ps_version}'."
            )

    # Look for version ID in registry data
    if not self.app:
        versions = self._get_photoshop_versions()
        self.app = self._get_application_object(versions)
        if not self.app:
            # All attempts exhausted
            raise PhotoshopPythonAPIError("Please check if you have Photoshop installed correctly.")

    # Add the parent app object
    if parent:
        self.adobe = self.app
        self.app = parent
        self._has_parent = True

eval_javascript(javascript, Arguments=None, ExecutionMode=None)

Instruct the application to execute javascript code.

Source code in photoshop/api/_core.py
215
216
217
218
def eval_javascript(self, javascript: str, Arguments: Any = None, ExecutionMode: Any = None) -> str:
    """Instruct the application to execute javascript code."""
    executor = self.adobe if self._has_parent else self.app
    return executor.doJavaScript(javascript, Arguments, ExecutionMode)

get_application_path()

Source code in photoshop/api/_core.py
198
199
200
201
def get_application_path(self) -> str:
    """str: The absolute path of Photoshop installed location."""
    key = self.open_key(f"{self._reg_path}\\{self.program_id}")
    return winreg.QueryValueEx(key, "ApplicationPath")[0]

get_plugin_path()

Source code in photoshop/api/_core.py
203
204
205
def get_plugin_path(self) -> str:
    """str: The absolute plugin path of Photoshop."""
    return os.path.join(self.application_path, "Plug-ins")

get_presets_path()

Source code in photoshop/api/_core.py
207
208
209
def get_presets_path(self) -> str:
    """str: The absolute presets path of Photoshop."""
    return os.path.join(self.application_path, "Presets")

get_script_path()

Source code in photoshop/api/_core.py
211
212
213
def get_script_path(self) -> str:
    """str: The absolute scripts path of Photoshop."""
    return os.path.join(self.presets_path, "Scripts")

Last update: 2024-03-05
Created: 2024-03-05