Skip to content

Commit

Permalink
Add and implement a new "Font Installer" system
Browse files Browse the repository at this point in the history
This font installer system will package up any non standard fonts used
in the After Effects scene and upload them as job attachments in the
"Input Files".

The adaptor now will install any fonts uploaded with the job as a part
of its Launch After Effects step.

Signed-off-by: Alex Hughes <[email protected]>
  • Loading branch information
Ahuge committed Jun 26, 2024
1 parent 758cad6 commit 5c86725
Show file tree
Hide file tree
Showing 3 changed files with 245 additions and 0 deletions.
44 changes: 44 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,45 @@ 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 +337,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 +427,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
129 changes: 129 additions & 0 deletions src/deadline/ae_adaptor/AEAdaptor/font_installer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import os
import shutil
import ctypes
from ctypes import wintypes
import sys
import ntpath
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, ""
72 changes: 72 additions & 0 deletions src/deadline/ae_submitter/data/InitData.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,81 @@ function __generateInitData()
}
}
}
var fontReferences = generateFontReferences();
logger.debug("Get Fonts: " + key, scriptFileInitDataName);
for (var i = 0; i < fontReferences.length; i++) {
detectedItemsList.push(fontReferences[i]);
}
dcProperties.jobAttachments.autoDetectedInputFiles.set(detectedItemsList);
}

function _generateFontReferences() {
var fontLocations = [];
var usedList = app.project.usedFonts;
for (var i = 0; i < usedList.length; i++) {
var font = usedList[i].font;
var fontFamilyName = font.familyName;
var familyStyle = font.styleName;
var fontName = fontFamilyName + " " + familyStyle + ".otf";
fontLocations.push([fontName, fontLocation]);
}
return fontLocations
}

function _oldGenerateFontReferences() {
var fontLocations = [];
var items = app.project.items;
for (var i = items.length; i >= 1; i--) {
var myItem = app.project.item(i);
if (myItem instanceof CompItem) {
for (var j = myItem.layers.length; j >= 1; j--) {
var myLayer = myItem.layers[j];
if (myLayer instanceof TextLayer){
var textDocument = myLayer.text.sourceText.value;
var fontLocation = textDocument.fontLocation;
var fontFamilyName = textDocument.fontFamily;
var familyStyle = textDocument.fontStyle;
var fontName = fontFamilyName + " " + familyStyle + ".otf";
fontLocations.push([fontName, fontLocation]);
}
}
}
}
return fontLocations
}

function generateFontReferences() {
var rawFontPaths = [];
if (parseInt(app.version[0] + app.version[1]) >= 24) {
rawFontPaths = _generateFontReferences();
} else {
rawFontPaths = _oldGenerateFontReferences();
}
var _tempFilePath = dcUtil.normPath(Folder.temp.fsName + "/" + "tempFonts");
var formattedFontPaths = [];
var tempFontPath = new Folder(_tempFilePath);
if (!tempFontPath.exists) {
tempFontPath.create();
}


for (var i = 0; i < rawFontPaths.length; i++) {
var fontName = rawFontPaths[i][0];
var fontLocation = rawFontPaths[i][1];
if (fontLocation.indexOf("C:\\Windows\\Fonts") !== -1) {
// We are filtering out fonts installed in C:\Windows\Fonts potentially here.
// I would make the argument that if a font is installed to the system (not an Adobe Font), that
// it should be managed on a system level not through the submitter.
continue
}
var fontFile = File(fontLocation);
var _filePath = dcUtil.normPath(_tempFilePath + "/" + fontName);
fontFile.copy(_filePath);
formattedFontPaths.push(_filePath);
}
return formattedFontPaths;
}

function initAutoDetectOutputDirectories()
{
var detectedOutputDirectories= [];
Expand Down

0 comments on commit 5c86725

Please sign in to comment.