Skip to content

fix: Don't attach missing or substituted fonts on submit #118

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 43 commits into
base: mainline
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
5614772
feat:Dockable submitter (#63)
eherozhao Dec 9, 2024
56a6ab1
docs: update README, CONTRIBUTING and DEVELOPMENT doc (#79)
eherozhao Dec 12, 2024
5fa02a2
fix: provide a way for CMF customers with aerender exectuable env opt…
eherozhao Dec 13, 2024
06c63a1
chore: delete unuseful log (#82)
eherozhao Dec 17, 2024
647b145
fix: use shell executable to call deadline cli for macOS. (#83)
eherozhao Dec 18, 2024
da7c422
chore: resolve merge conflict
eherozhao Dec 18, 2024
56a9b68
chore: remove unused code (#84)
eherozhao Dec 18, 2024
363ffd6
feat: add hostRequirement for AE job template (#85)
eherozhao Dec 23, 2024
23c46d4
fix: Remove CondaPackages parameter override to restore default param…
viknith Dec 25, 2024
a3bef05
fix: replace hard-coded template.yaml with code generated json file (…
eherozhao Dec 27, 2024
1e325d2
fix: Use JSON data structure to change job template parameters (#88)
eherozhao Dec 31, 2024
0214826
feat: Add custom font support by detecting fonts used in a compositio…
Jan 4, 2025
46d834e
feat: Show popup if missing fonts are detected when collecting job at…
Jan 13, 2025
cf15c9f
feat: image sequences chunking (#93)
eherozhao Jan 16, 2025
39260e0
feat: Use app.project.usedFonts when available for font collection
Jan 17, 2025
e8f2d3c
fix: add a release bump yaml (#97)
eherozhao Jan 21, 2025
a532b6c
fix: replace %20 with space for Windows os (#98)
eherozhao Jan 24, 2025
6fc164d
fix: Update warning dialog message for fonts without a fontLocation
Jan 24, 2025
314e101
feat: add xml file for submitter installer (#96)
eherozhao Jan 24, 2025
7acf4b5
Merge remote-tracking branch 'remotes/origin/feature_dockable_submitt…
Jan 27, 2025
2940887
chore: Update dist
Jan 27, 2025
244c534
chore: update GitHub issue templates (#99)
joel-wong-aws Jan 27, 2025
eaa27ab
chore: Adjust spaces and line spacing
Jan 28, 2025
589add2
feat: Check Python version with getPythonExecutable()
Jan 28, 2025
9a18cfd
feat: Show alert on error running get_user_fonts.py
Jan 28, 2025
2851df5
chore: Remove blank line
Jan 28, 2025
6cf23de
Merge branch 'feature_dockable_submitter' into feature_font_support
cilevitz Jan 28, 2025
13baf13
chore: Update requirements.txt with fonttools library
Jan 30, 2025
34aa9b1
fix: Don't attach missing or substituted fonts on submit
Feb 1, 2025
cef8d28
Merge branch 'mainline' into feature_font_support
Feb 1, 2025
2777e5d
Merge branch 'aws-deadline:mainline' into feature_font_support
cilevitz Feb 1, 2025
09a84f3
Merge branch 'aws-deadline:mainline' into feature_font_support
cilevitz Feb 1, 2025
42cb782
Merge branch 'feature_font_support' of https://github.com/redtoastpro…
Feb 1, 2025
583bb92
docs: Update README.md about missing and substituted fonts
Feb 1, 2025
b475145
Merge branch 'mainline' into feature_font_support
Feb 7, 2025
84e3c38
feat: Show errors from get_user_fonts.py in the UI
Feb 7, 2025
57c40af
fix: Adjust variable declarations
Feb 7, 2025
70de4a0
chore: Refactor getFontNameOverride, get_fonts
Feb 7, 2025
c9c25ca
Merge branch 'aws-deadline:mainline' into feature_font_support
cilevitz Feb 7, 2025
266b0a9
feat: Add confirmation dialog for submitting a composition containing…
Feb 8, 2025
d06a959
Merge branch 'feature_font_support' of https://github.com/redtoastpro…
Feb 8, 2025
db9e171
Merge branch 'aws-deadline:mainline' into feature_font_support
cilevitz Feb 11, 2025
ea25a1f
feat: Cache fontPaths during font scan
Feb 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ The submitter includes a folder `DeadlineCloudSubmitter_Assets` and a file `Dead

#### Font attachment system:

The submitter detects fonts used in the submitted composition and automatically adds them as job attachments on submission. These get installed on the worker before the render starts and get removed again when the job ends.
Fonts used in the submitted composition are detected by the submitter and are automatically added as job attachments on submission. These get installed on the worker before the render starts and get removed when the job ends.
Supported font types include: OpenType (`.otf`), TrueType (`.ttf`), and [Adobe Fonts](https://fonts.adobe.com/).
Windows bitmap fonts (`.fon`) are only supported on Windows machines.

If fonts are missing at render time, first check that they're installed (on the system or your user), and then check they're being included in the job attachments tab in the submitter.
The submitter detects if fonts are missing or substituted in a project and warns you before proceeding with submission. If you choose to submit a composition with missing or substituted fonts, its render tasks will use the substituted fonts as submitted. To ensure correct render output, you must add any required fonts to your system or your user profile before opening a project that needs them. First check that the missing fonts are installed (on the system or your user), and then check they're being included in the job attachments tab in the submitter.

Fonts distributed through Adobe Creative Cloud can be made available for all non-Adobe apps on your workstation, or only made available in Adobe apps. Cloud fonts need to be installed for all non-Adobe apps for use with Deadline Cloud.
To install fonts for non-Adobe apps in Creative Cloud:
Expand Down
936 changes: 515 additions & 421 deletions dist/DeadlineCloudSubmitter.jsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
import os


FATAL_WARNINGS = [
# "Project has missing fonts"
]

def main():
parser = argparse.ArgumentParser(description="After Effects Render Script")
parser.add_argument("project", type=str, help="Project file path")
Expand Down Expand Up @@ -91,6 +95,11 @@ def main():
if "WARNING:After Effects warning" in line:
print(f"After Effects Warning: {line.strip()}", file=sys.stderr)

# Some warnings should be treated as errors, i.e. a fatal warning
if any([w in line for w in FATAL_WARNINGS]):
print(f"After Effects Fatal Warning: {line.strip()}", file=sys.stderr)
sys.exit(1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I submitted a job with missing fonts, but this didn't stop my job rendered. Here is the log.

2025/02/11 13:23:17-08:00 After Effects Warning: WARNING:After Effects warning: Grouped_Message_{This project contains references to missing effects. Please install the following effects to restore these references.|||This project contains a reference to a missing effect. Please install the following effect to restore this reference.###@0 missing effects.}"Saber"
2025/02/11 13:23:18-08:00 WARNING:After Effects warning: Project has missing fonts.
2025/02/11 13:23:18-08:00 After Effects Warning: WARNING:After Effects warning: Project has missing fonts.
2025/02/11 13:23:18-08:00  
2025/02/11 13:23:18-08:00 rqindex 1num 1
2025/02/11 13:23:18-08:00 PROGRESS:  2/11/2025 9:23:18 PM: Starting composition "test_comp".
2025/02/11 13:23:18-08:00  
2025/02/11 13:23:18-08:00 PROGRESS:  Render Settings: Best Settings


# Also handle stderr (errors)
for line in process.stderr:
print(line, end="", flush=True) # Print stderr line by line
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,19 @@ def install_font(src_path, scope=INSTALL_SCOPE_USER):
registry_scope = winreg.HKEY_CURRENT_USER
dst_path = os.path.join(dst_dir, os.path.basename(src_path))

# Copy the font to the Windows Fonts folder
shutil.copy(src_path, dst_path)
# Check if the font is already present
if os.path.exists(dst_path) and (os.stat(src_path).st_size == os.stat(dst_path).st_size):
logger.info(f"Font already present: {dst_path}")
else:
# Copy the font to the destination Fonts folder
shutil.copy(src_path, dst_path)

# Load the font in the current session, remove font when loading fails
if not gdi32.AddFontResourceW(dst_path):
os.remove(dst_path)
try:
os.remove(dst_path)
except Exception as e:
logger.error(f"Couldn't remove: {dst_path} {e.message}")
raise WindowsError(f'AddFontResource failed to load "{src_path}"')

# Notify running programs
Expand All @@ -151,7 +158,7 @@ def install_font(src_path, scope=INSTALL_SCOPE_USER):
fontname = get_font_name(dst_path)

# Creates registry if it doesn't exist, opens when it does exist
with winreg.CreateKeyEx(registry_scope, FONTS_REG_PATH, 0, access= winreg.KEY_SET_VALUE) as key:
with winreg.CreateKeyEx(registry_scope, FONTS_REG_PATH, 0, access=winreg.KEY_SET_VALUE) as key:
winreg.SetValueEx(key, fontname, 0, winreg.REG_SZ, filename)
except Exception:
return False, traceback.format_exc()
Expand All @@ -177,13 +184,24 @@ def uninstall_font(src_path, scope=INSTALL_SCOPE_USER):

# Remove the fontname/filename from the registry
fontname = get_font_name(dst_path)

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

try:
with winreg.OpenKey(registry_scope, FONTS_REG_PATH, 0, access=winreg.KEY_SET_VALUE) as key:
winreg.DeleteValue(key, fontname)
except FileNotFoundError as e:
# The entry was already deleted
logger.warning(f"Couldn't remove {dst_path}: {getattr(e, 'message', getattr(e, 'args', 'unknown FileNotFoundError'))}")
except:
raise

# Unload the font in the current session
if not gdi32.RemoveFontResourceW(dst_path):
os.remove(dst_path)
try:
os.remove(dst_path)
except PermissionError as e:
logger.warning(f"Couldn't remove {dst_path}: {getattr(e, 'message', getattr(e, 'args', 'unknown FileNotFoundError'))}")
except Exception:
raise
raise WindowsError(f'RemoveFontResourceW failed to load "{src_path}"')

if os.path.exists(dst_path):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,9 @@
except ModuleNotFoundError:
error_msg = "Error: The fonttools module was not found.\n"
error_msg += "Please install fonttools by running:\n\npip install fonttools"
print(json.dumps({
"error": error_msg
}))
sys.exit(1)
error_exit(error_msg)
except Exception as e:
print(json.dumps({
"error": traceback.format_exc()
}))
sys.exit(1)

error_exit(traceback.format_exc())

# Font locations to search on Windows
SEARCH_PATHS = [
Expand Down Expand Up @@ -52,6 +45,13 @@
TTF_POSTSCRIPT_NAME = 6


def error_exit(message, errorlevel=1):
print(json.dumps({
"error": message
}))
sys.exit(errorlevel)


def get_font(font_path):
"""
Collect font metadata from the given font file.
Expand All @@ -62,19 +62,29 @@ def get_font(font_path):

try:
t = ttLib.TTFont(font_path)
except ttLib.TTLibError as e:
# Not a TrueType or OpenType font (bad sfntVersion)
result["error_verbose"] = traceback.format_exc()
if "bad sfntVersion" in str(e):
# These errors are very common.
# Don't show a UI warning but report the error in "error_verbose"
result["error_verbose"] = traceback.format_exc()
else:
result["error"] = f"{e}: {font_path}"
return result
except Exception as e:
if verbose:
print(traceback.format_exc())
result["error_verbose"] = traceback.format_exc()
result["error"] = f"{getattr(e, 'message', str(e))}: {font_path}"
return result

# Collect name metadata from the name table
names_table = t["name"].names
raw_table = {}
try:
raw_table = {i:str(names_table[i]) for i in range(0, len(names_table))}
except Exception:
if verbose:
print(traceback.format_exc())
except Exception as e:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: if we don't print out error in the block, do we still need as e? I would suggest us either print the error out or delete as e.

Also, are we able to print out the python results from subprocess to the main submitter output to surface to customers?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Font scan errors from Python are now surfaced in the AE UI.

result["error_verbose"] = traceback.format_exc()
result["error"] = f"{getattr(e, 'message', str(e))}: {font_path}"
return result

try:
Expand All @@ -85,95 +95,93 @@ def get_font(font_path):
"postscript_name": str(names_table[TTF_POSTSCRIPT_NAME]),
"raw": raw_table
}
except Exception:
if verbose:
print(traceback.format_exc())
except Exception as e:
result["error"] = f"{getattr(e, 'message', str(e))}: {font_path}"
return result

return result


def get_fonts(root_path, verbose=None):
def get_fonts(root_path):
"""
Collect font metadata from all font files under the specified root_path.
Returns a dictionary with file paths as the keys.
Returns a dictionary with file paths as the keys and a list of errors encountered.
"""

result = {}
errors = []
errors_verbose = []

try:
if not os.path.exists(root_path):
return result
except Exception:
if verbose:
print(traceback.format_exc())
return result
for path, dirs, files in os.walk(root_path):
for file in files:
_, ext = os.path.splitext(file)
if ext.lower() not in FONT_EXTENSIONS:
continue

try:
for path, dirs, files in os.walk(root_path):
for file in files:
_, ext = os.path.splitext(file)
if ext.lower() not in FONT_EXTENSIONS:
continue

font_path = path + "/" + file
if sys.platform == "win32":
font_path = font_path.replace("\\", "/")
else:
pass

if verbose:
print(f"font_path: {font_path}")

font_data = {}
try:
font_data = get_font(font_path)
result.update(font_data)
except Exception:
if verbose:
print(traceback.format_exc())
continue
except Exception:
if verbose:
print(traceback.format_exc())
return result
font_path = path + "/" + file
if sys.platform == "win32":
font_path = font_path.replace("\\", "/")

font_data = {}
try:
font_data = get_font(font_path)
result.update(font_data)
except Exception as e:
errors.append(f"{getattr(e, 'message', str(e))}: {font_path}")
continue
if "error" in font_data:
errors.append(font_data["error"])
if "error_verbose" in font_data:
errors_verbose.append(font_data["error_verbose"])
return result, errors, errors_verbose


def search_for_fonts(search_paths, verbose=None):
def search_for_fonts(search_paths):
"""
Searches the given paths recursively for font files and collects
their metadata.
Returns a dictionary with file paths as the keys.
"""

fonts = {}

errors = []
errors_verbose = []

for search_path in search_paths:
search_root = os.path.normpath(os.path.expandvars(os.path.expanduser(search_path)))
try:
if not os.path.exists(search_root):
continue
except Exception:
if verbose:
print(traceback.format_exc())
except Exception as e:
errors.extend(f"{getattr(e, 'message', str(e))}: {search_root}")
continue

if verbose:
print(f"search_root: {search_root}")

font_results = get_fonts(search_root, verbose=verbose)
font_results, font_errors, font_errors_verbose = get_fonts(search_root)
fonts.update(font_results)
errors.extend(font_errors)
errors_verbose.extend(font_errors_verbose)

if errors:
s = "s" if len(errors) > 1 else ""
fonts["error"] = f"Error{s} encountered during font scan:\n"
for e in errors:
fonts["error"] += e + "\n"

if errors_verbose:
fonts["error_verbose"] = errors_verbose

return fonts


if __name__ == "__main__":
verbose = False
result = []
try:
result = search_for_fonts(SEARCH_PATHS, verbose=verbose)
result = search_for_fonts(SEARCH_PATHS)
print(json.dumps(result))
except Exception as e:
result = {
"error": f"{getattr(e, 'message', str(e))}",
"error_verbose": traceback.format_exc()
}
print(json.dumps(result))
except Exception:
if verbose:
print(traceback.format_exc())

1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fonttools ~= 4.55.6
Loading