Skip to content

Commit

Permalink
Install any custom fonts provided with the Job
Browse files Browse the repository at this point in the history
Our adaptor will now install any font files uploaded with the job as a
part of its "Launch After Effects" step.
It is installing these fonts to the User and not the System so Admin
permissions should not be required.

Signed-off-by: Alex Hughes <[email protected]>
  • Loading branch information
Ahuge committed Jun 28, 2024
1 parent d3439d4 commit 00762dc
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 0 deletions.
46 changes: 46 additions & 0 deletions src/deadline/ae_adaptor/AEAdaptor/adaptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ class AEAdaptor(Adaptor[AdaptorConfiguration]):
_exc_info: Exception | None = None
_performing_cleanup = False

FONT_EXTENSIONS = {".OTF", "TTF"}

@property
def integration_data_interface_version(self) -> SemanticVersion:
return SemanticVersion(major=0, minor=1)
Expand Down Expand Up @@ -285,6 +287,47 @@ def _populate_action_queue(self) -> None:
if action_name in self.init_data:
self._action_queue.enqueue_action(self._action_from_action_item(action_name))

def _find_fonts(self):
fonts = set()
for path in os.listdir(os.getcwd()):
if path.startswith("assetroot-"):
asset_dir = os.path.join(os.getcwd(), path)
for asset_path in os.listdir(asset_dir):
_, ext = os.path.splitext(os.path.join(asset_dir, asset_path))
if ext.upper() in self.FONT_EXTENSIONS:
fonts.add(os.path.join(asset_dir, asset_path))
return fonts

def _install_fonts(self):
fonts = self._find_fonts()
installed_fonts = set()
if not fonts:
return

from . import font_installer

for font in fonts:
_logger.info("Installing font: %s" % font)
installed, msg = font_installer.install_font(font)
if not installed:
_logger.error(" Error installing font: %s" % msg)
else:
installed_fonts.add(font)
return installed_fonts

def _remove_fonts(self):
fonts = self._find_fonts()
if not fonts:
return

from . import font_installer

for font in fonts:
_logger.info("Uninstalling font: %s" % font)
uninstalled, msg = font_installer.uninstall_font(font)
if not uninstalled:
_logger.error(" Error uninstalling font: %s" % msg)

def on_start(self) -> None:
"""
For job stickiness. Will start everything required for the Task.
Expand All @@ -296,6 +339,7 @@ def on_start(self) -> None:
- TimeoutError: If After Effects did not complete initialization actions due to timing out.
- FileNotFoundError: If the ae_client.py file could not be found.
"""
self._install_fonts()
# Validate init data against schema
cur_dir = os.path.dirname(__file__)
schema_dir = os.path.join(cur_dir, "schemas")
Expand Down Expand Up @@ -385,6 +429,8 @@ def on_cleanup(self):
if self._server_thread.is_alive():
_logger.error("Failed to shutdown the After Effects Adaptor server.")

self._remove_fonts()

self._performing_cleanup = False

def on_cancel(self):
Expand Down
119 changes: 119 additions & 0 deletions src/deadline/ae_adaptor/AEAdaptor/font_installer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import os
import shutil
import ctypes
from ctypes import wintypes

try:
import winreg
except ImportError:
import _winreg as winreg

user32 = ctypes.WinDLL("user32", use_last_error=True)
gdi32 = ctypes.WinDLL("gdi32", use_last_error=True)

FONTS_REG_PATH = r"Software\Microsoft\Windows NT\CurrentVersion\Fonts"

HWND_BROADCAST = 0xFFFF
SMTO_ABORTIFHUNG = 0x0002
WM_FONTCHANGE = 0x001D
GFRI_DESCRIPTION = 1
GFRI_ISTRUETYPE = 3

INSTALL_SCOPE_USER = "USER"
INSTALL_SCOPE_SYSTEM = "SYSTEM"

FONT_LOCATION_SYSTEM = os.path.join(os.environ.get("SystemRoot"), "Fonts")
FONT_LOCATION_USER = os.path.join(os.environ.get("LocalAppData"), "Microsoft", "Windows", "Fonts")


def install_font(src_path, scope=INSTALL_SCOPE_USER):
try:
# copy the font to the Windows Fonts folder
if scope == INSTALL_SCOPE_SYSTEM:
dst_path = os.path.join(FONT_LOCATION_SYSTEM, os.path.basename(src_path))
registry_scope = winreg.HKEY_LOCAL_MACHINE
else:
dst_path = os.path.join(FONT_LOCATION_USER, os.path.basename(src_path))
registry_scope = winreg.HKEY_CURRENT_USER

shutil.copy(src_path, dst_path)
# load the font in the current session
if not gdi32.AddFontResourceW(dst_path):
os.remove(dst_path)
raise WindowsError('AddFontResource failed to load "%s"' % src_path)
# notify running programs
user32.SendMessageTimeoutW(
HWND_BROADCAST, WM_FONTCHANGE, 0, 0, SMTO_ABORTIFHUNG, 1000, None
)
# store the fontname/filename in the registry
filename = os.path.basename(dst_path)
fontname = os.path.splitext(filename)[0]
# try to get the font's real name
cb = wintypes.DWORD()
if gdi32.GetFontResourceInfoW(filename, ctypes.byref(cb), None, GFRI_DESCRIPTION):
buf = (ctypes.c_wchar * cb.value)()
if gdi32.GetFontResourceInfoW(filename, ctypes.byref(cb), buf, GFRI_DESCRIPTION):
fontname = buf.value
is_truetype = wintypes.BOOL()
cb.value = ctypes.sizeof(is_truetype)
gdi32.GetFontResourceInfoW(
filename, ctypes.byref(cb), ctypes.byref(is_truetype), GFRI_ISTRUETYPE
)
if is_truetype:
fontname += " (TrueType)"
with winreg.OpenKey(registry_scope, FONTS_REG_PATH, 0, winreg.KEY_SET_VALUE) as key:
winreg.SetValueEx(key, fontname, 0, winreg.REG_SZ, filename)
except Exception:
import traceback

return False, traceback.format_exc()
return True, ""


def uninstall_font(src_path, scope=INSTALL_SCOPE_USER):
try:
# copy the font to the Windows Fonts folder
if scope == INSTALL_SCOPE_SYSTEM:
dst_path = os.path.join(FONT_LOCATION_SYSTEM, os.path.basename(src_path))
registry_scope = winreg.HKEY_LOCAL_MACHINE
else:
dst_path = os.path.join(FONT_LOCATION_USER, os.path.basename(src_path))
registry_scope = winreg.HKEY_CURRENT_USER

# remove the fontname/filename from the registry
filename = os.path.basename(dst_path)
fontname = os.path.splitext(filename)[0]
# try to get the font's real name
cb = wintypes.DWORD()
if gdi32.GetFontResourceInfoW(filename, ctypes.byref(cb), None, GFRI_DESCRIPTION):
buf = (ctypes.c_wchar * cb.value)()
if gdi32.GetFontResourceInfoW(filename, ctypes.byref(cb), buf, GFRI_DESCRIPTION):
fontname = buf.value
is_truetype = wintypes.BOOL()
cb.value = ctypes.sizeof(is_truetype)
gdi32.GetFontResourceInfoW(
filename, ctypes.byref(cb), ctypes.byref(is_truetype), GFRI_ISTRUETYPE
)
if is_truetype:
fontname += " (TrueType)"

with winreg.OpenKey(registry_scope, FONTS_REG_PATH, 0, winreg.KEY_SET_VALUE) as key:
winreg.DeleteValue(key, fontname)

# unload the font in the current session
if not gdi32.RemoveFontResourceW(dst_path):
os.remove(dst_path)
raise WindowsError('RemoveFontResourceW failed to load "%s"' % src_path)

if os.path.exists(dst_path):
os.remove(dst_path)

# notify running programs
user32.SendMessageTimeoutW(
HWND_BROADCAST, WM_FONTCHANGE, 0, 0, SMTO_ABORTIFHUNG, 1000, None
)
except Exception:
import traceback

return False, traceback.format_exc()
return True, ""

0 comments on commit 00762dc

Please sign in to comment.