Pour la fonctionalité d'Autolock sur Nuxeo Drive, nous avons besoin de savoir si un fichier est ouvert dans tel ou tel logiciel.
Certains sont assez facile à détecter, comme LibreOffice ou Microsoft Office, qui créent des fichiers temporaires reconnaissables.
Mais ce n'est ni le cas pour Photoshop, ni Illustrator, de la suite Adobe Creative. Ils utilisent un seul gros fichier temporaire protégé en lecture et on ne peut rien en tirer (Photoshop Temp\d{10}
).
J'ai écrit un rapport assez complet sur l'analyse de Photoshop, si cela vous tente : Adobe Creative Suite: Photoshop (en anglais).
En fin de compte, j'ai trouvé une solution élégante : Photoshop Scripting (idem pour Illustrator).
La documentation technique et les exemples sur internet sont orientés manipulation de documents, mais rien n'indique clairement comment avoir une liste des fichiers ouverts dans le logiciel ; de manière passive.
Notez que les morceaux de code suivants sont tout aussi valables pour Photoshop qu'Illustrator.
Windows
L'idée est d'utiliser COM pour communiquer avec une instance en cours de Photoshop.
Pour ce faire, nous avons besoin du module pywin32 et ces ces quelques lignes :
from contextlib import suppress
from typing import Iterator
from win32com.client import GetActiveObject
def get_opened_files_adobe_cc_(obj: str) -> Iterator[str]:
""" Retrieve documents path of opened files of the given *obj* (application). """
with suppress(Exception):
app = GetActiveObject(obj)
for doc in app.Application.Documents:
yield doc.fullName
def get_opened_files() -> Iterator[str]:
""" Find opened files in Photoshop or Illustrator. """
yield from get_opened_files_adobe_cc_("Photoshop.Application")
yield from get_opened_files_adobe_cc_("Illustrator.Application")
macOS
Nous utiliserons AppleScript, aurons besoin des modules PyObjC Cocoa et ScriptingBridge, ainsi que de ce code :
from typing import Iterator
from AppKit import NSWorkspace
from ScriptingBridge import SBApplication
def is_running_(identifier: str) -> bool:
"""
Check if a given application bundle identifier is found in
the list of opened applications, meaning it is running.
"""
shared_workspace = NSWorkspace.sharedWorkspace()
if not shared_workspace:
return False
running_apps = shared_workspace.runningApplications()
if not running_apps:
return False
return any(str(app.bundleIdentifier()) == identifier for app in running_apps)
def get_opened_files_adobe_cc_(identifier: str) -> Iterator[str]:
""" Retrieve documents path of opened files of the given bundle *identifier* (application). """
if not is_running_(identifier):
return
app = SBApplication.applicationWithBundleIdentifier_(identifier)
if not (app and app.isRunning()):
return
try:
documents = list(app.documents())
except AttributeError:
return
if not documents:
return
for doc in documents:
file_path = doc.filePath()
if not file_path:
# The document is not yet saved and so has no path
continue
yield file_path.path()
def get_opened_files() -> Iterator[str]:
""" Find opened files in Photoshop or Illustrator. """
yield from get_opened_files_adobe_cc_("com.adobe.Photoshop")
yield from get_opened_files_adobe_cc_("com.adobe.Illustrator")
Utilisation
Maintenant que nous avons nos jolies fonctions, nous pouvons faire ce genre de chose :
for file in get_opened_files():
print(file)
De rien ! ♣
Sources
Historique
- 2020-03-20 : Amélioration du code grâce à Sourcery.
- 2019-11-25 : Fix pour macOS.
- 2019-10-11 : Fix pour
AttributeError: 'SBApplication' object has no attribute 'documents'
sur macOS. - 2019-05-10 : Fix pour
IndexError: NSRangeException - *** -[NSArray getObjects:range:]: range {0, 2} extends beyond bounds for empty array
sur macOS. - 2019-04-11 : Ignorance des fichiers ouverts mais non enregistrés sur macOS.
- 2019-04-02 : Correction de
is_running_()
quand il n'y a pas deNSWorkspace
et utilisation de runningApplication au lieu de launchedApplications (dépréciée). - 2019-03-16 : Refactorisation du code, ajout des sources.
- 2019-03-07 : Correction de l'application démarrée automatiquement sur macOS.