From aa7b65f266b1c1726ec012ebbed5cbcab549acef Mon Sep 17 00:00:00 2001 From: Alex Hughes Date: Fri, 6 Dec 2024 17:09:31 -0800 Subject: [PATCH] Implement Frame Chunking logic This happens via a FrameStart, FrameEnd, ChunkSize and 2 internal parameters that are just calculations based on the previous ones. We've also reorganized some of the code to hopefully isolate it against future changes. Signed-off-by: Alex Hughes --- .../ae_submitter/UI/AESubmitterUI.jsx | 17 +++ src/deadline/ae_submitter/UI/SubmitButton.jsx | 68 +++++++--- .../ae_submitter/data/RenderScript.jsx | 19 +++ .../ae_submitter/submission/DataTemplate.jsx | 63 ++++++++- .../ae_submitter/submission/JobTemplate.jsx | 127 +++++++++--------- src/deadline/ae_submitter/utils/Util.jsx | 35 +++++ 6 files changed, 241 insertions(+), 88 deletions(-) create mode 100644 src/deadline/ae_submitter/data/RenderScript.jsx diff --git a/src/deadline/ae_submitter/UI/AESubmitterUI.jsx b/src/deadline/ae_submitter/UI/AESubmitterUI.jsx index 8afc2bf..c3b3835 100644 --- a/src/deadline/ae_submitter/UI/AESubmitterUI.jsx +++ b/src/deadline/ae_submitter/UI/AESubmitterUI.jsx @@ -672,6 +672,12 @@ function __generateSubmitterUI() { frameListGroup.useCompFrameList.value = initUseCompFrameRange; frameListGroup.useCompFrameList.helpTip = 'If enabled, the Comp\'s frame list will be used instead of the frame list in this submitter.'; + frameListGroup.framesPerTask = frameListGroup.add('edittext', undefined, 10); + frameListGroup.framesPerTask.size = SHORT_TEXT_SIZE; + frameListGroup.framesPerTaskLabel = frameListGroup.add('statictext', undefined, 'Frames Per Task'); + frameListGroup.framesPerTaskLabel.size = LABEL_SIZE; + frameListGroup.framesPerTaskLabel.helpTip = 'This is the number of frames to be rendered per Job Task.'; + // Create Comp submission group and widgets (Select One, Use Selected in RQ, All as separate) compSubmissionGroup = deadlineCloud.aeOptionsPanel.add('group', undefined); compSubmissionGroup.alignment = [ScriptUI.Alignment.FILL, ScriptUI.Alignment.TOP]; @@ -1222,6 +1228,17 @@ function __generateSubmitterUI() { } } + frameListGroup.framesPerTask.onChange = function() { + currentValue = frameListGroup.framesPerTask.text.replace(/[^\d]/g, ''); + + if (parseInt(currentValue) > 9999) { + frameListGroup.framesPerTask.text = "9999" + } + if (parseInt(currentValue) < 1) { + frameListGroup.framesPerTask.text = "1" + } + } + frameListGroup.frameListLabel.enabled = !initSubmitEntireQueue && !initMultiMachine; frameListGroup.frameList.enabled = !initUseCompFrameRange && !initSubmitEntireQueue && !initMultiMachine; frameListGroup.useCompFrameList.enabled = !initSubmitEntireQueue && !initMultiMachine; diff --git a/src/deadline/ae_submitter/UI/SubmitButton.jsx b/src/deadline/ae_submitter/UI/SubmitButton.jsx index 520ebb9..50eea02 100644 --- a/src/deadline/ae_submitter/UI/SubmitButton.jsx +++ b/src/deadline/ae_submitter/UI/SubmitButton.jsx @@ -79,26 +79,46 @@ function __generateSubmitButton() { function generateStep(basicTemplate, itemName, stepsTemplate, stepID, renderQueueItem) { var compNameToCheck = dcUtil.removeIllegalCharacters(renderQueueItem.comp.name); - // logger.debug("[generateStep] itemName: " + itemName + " stepID: " + stepID + " compNameToCheck: " + compNameToCheck, _submitButtonFileName); stepsTemplate[0].name = itemName; - stepsTemplate[0].parameterSpace.taskParameterDefinitions[0].range = "{{Param." + itemName + "_Frames}}"; - stepsTemplate[0].script.embeddedFiles[0].data = "frame: {{Task.Param.Frame}}"; + stepsTemplate[0].parameterSpace.taskParameterDefinitions[0].range = "{{Param." + itemName + "_FrameStart}} - {{Param." + itemName + "_FrameEnd}} : {{Param." + itemName + "_ChunkSize}}"; + stepsTemplate[0].parameterSpace.taskParameterDefinitions[1].range = "{{Param." + itemName + "_FrameStartPlusChunkSizeMinusOne}}-{{Param." + itemName + "_FrameEndMinusOne}}:{{Param." + itemName + "_ChunkSize}},{{Param." + itemName + "_FrameEnd}}"; if(itemName != compNameToCheck) { - stepsTemplate[0].stepEnvironments[0].script.embeddedFiles[0].data += "comp_name: {{Param." + compNameToCheck + "_CompName}} \n"; + stepsTemplate[0].script.embeddedFiles[0].data = "\"%AFTEREFFECTS_ADAPTOR_AERENDER_EXECUTABLE%\" -project \"{{Param.AfterEffectsProjectFile}}\" -comp \"{{Param." + itemName + "_CompName}}\" -s {{Task.Param.FrameChunkStart}} -e {{Task.Param.FrameChunkEnd}} || exit /b 1 \n" + // stepsTemplate[0].taskParameterDefinitions[2].range = ["{{Param." + compNameToCheck + "_CompName}}"]; } - else - { - stepsTemplate[0].stepEnvironments[0].script.embeddedFiles[0].data += "comp_name: {{Param." + itemName + "_CompName}} \n"; + else { + stepsTemplate[0].script.embeddedFiles[0].data = "\"%AFTEREFFECTS_ADAPTOR_AERENDER_EXECUTABLE%\" -project \"{{Param.AfterEffectsProjectFile}}\" -comp \"{{Param." + itemName + "_CompName}}\" -s {{Task.Param.FrameChunkStart}} -e {{Task.Param.FrameChunkEnd}} || exit /b 1 \n" + // stepsTemplate[0].taskParameterDefinitions[2].range = ["{{Param." + itemName + "_CompName}}"]; } - stepsTemplate[0].stepEnvironments[0].script.embeddedFiles[0].data += "output_file_path: {{Param." + itemName + "_OutputFilePath}} \n"; - stepsTemplate[0].stepEnvironments[0].script.embeddedFiles[0].data += "output_pattern: {{Param." + itemName + "_OutputPattern}} \n"; - stepsTemplate[0].stepEnvironments[0].script.embeddedFiles[0].data += "output_format: {{Param." + itemName + "_OutputFormat}} \n"; basicTemplate.steps[stepID-1] = stepsTemplate[0]; // logger.debug("[generateStep] basicTemplate: " + JSON.stringify(basicTemplate), _submitButtonFileName); return basicTemplate; } + function applyFrameChunkingDataToTemplate(itemName, frameList) + { + var output = { + "parameterDefinitions": [], + "parameterValues": [], + } + + var chunkParametersList = dcUtil.getFrameChunkParameters(frameList, frameListGroup.framesPerTask.text); + // Currently only support a single frame range + output.parameterDefinitions.push(applyDataToTemplate(itemName + "_FrameStart", dcUtil.deepCopy(dcDataTemplate.FrameStart))); + output.parameterDefinitions.push(applyDataToTemplate(itemName + "_FrameEnd", dcUtil.deepCopy(dcDataTemplate.FrameEnd))); + output.parameterDefinitions.push(applyDataToTemplate(itemName + "_ChunkSize", dcUtil.deepCopy(dcDataTemplate.ChunkSize))); + output.parameterDefinitions.push(applyDataToTemplate(itemName + "_FrameStartPlusChunkSizeMinusOne", dcUtil.deepCopy(dcDataTemplate.FrameStartPlusChunkSizeMinusOne))); + output.parameterDefinitions.push(applyDataToTemplate(itemName + "_FrameEndMinusOne", dcUtil.deepCopy(dcDataTemplate.FrameEndMinusOne))); + + output.parameterValues.push(applyDataToParameterTemplate(itemName + "_FrameStart", chunkParametersList[0].frameStart)); + output.parameterValues.push(applyDataToParameterTemplate(itemName + "_FrameEnd", chunkParametersList[0].frameEnd)); + output.parameterValues.push(applyDataToParameterTemplate(itemName + "_ChunkSize", chunkParametersList[0].chunkSize)); + output.parameterValues.push(applyDataToParameterTemplate(itemName + "_FrameStartPlusChunkSizeMinusOne", chunkParametersList[0].frameStartPlusChunkSizeMinusOne)); + output.parameterValues.push(applyDataToParameterTemplate(itemName + "_FrameEndMinusOne", chunkParametersList[0].frameEndMinusOne)); + return output + } + function applyDataToTemplate(dataName, dataTemplate) { if(dataName.indexOf("Frames") >= 0 || dataName.indexOf("OutputFilePath") >= 0) @@ -315,6 +335,7 @@ function __generateSubmitButton() { if (submissionText == useQueue) totalJobs = 1; + queuedCount = 1; progressBarPanel.progressBar.value = 0; @@ -467,12 +488,13 @@ function __generateSubmitButton() { var __compName = dcUtil.removeIllegalCharacters(app.project.renderQueue.item(j).comp.name); var frameList = getFrameList(app.project.renderQueue.item(j)); - - // logger.debug("[createDataAndParameterTemplateOneJob] __compName: " + __compName, _submitButtonFileName); - // logger.debug("[createDataAndParameterTemplateOneJob] frameList: " + frameList, _submitButtonFileName); - + + var chunking = applyFrameChunkingDataToTemplate(__compName, frameList) + // Add data to the main template - jobTemplate.parameterDefinitions.push(applyDataToTemplate(__compName + "_Frames", dcUtil.deepCopy(dcDataTemplate.Frames))); + for (var pIdx = 0; pIdx < chunking.parameterDefinitions.length; pIdx++) { + jobTemplate.parameterDefinitions.push(chunking.parameterDefinitions[pIdx]); + } jobTemplate.parameterDefinitions.push(applyDataToTemplate(__compName + "_OutputPattern", dcUtil.deepCopy(dcDataTemplate.OutputPattern))); jobTemplate.parameterDefinitions.push(applyDataToTemplate(__compName + "_OutputFormat", dcUtil.deepCopy(dcDataTemplate.OutputFormat))); jobTemplate.parameterDefinitions.push(applyDataToTemplate(__compName + "_CompName", dcUtil.deepCopy(dcDataTemplate.CompName))); @@ -480,13 +502,13 @@ function __generateSubmitButton() { // Add steps data per task that needs to be run jobTemplate = generateStep(jobTemplate, __compName, dcUtil.deepCopy(stepsTemplate), stepIndex, app.project.renderQueue.item(j)); - // logger.debug("[createDataAndParameterTemplateOneJob] jobTemplate: " + JSON.stringify(jobTemplate), _submitButtonFileName); jobTemplate = applyHostReqToTemplate(jobTemplate); - // logger.debug("[createDataAndParameterTemplateOneJob] jobTemplate applyHostReqToTemplate: " + JSON.stringify(jobTemplate), _submitButtonFileName); // Add data to the parameter template - jobParams.parameterValues.push(applyDataToParameterTemplate(__compName + "_Frames", frameList)); + for (var pIdx = 0; pIdx < chunking.parameterValues.length; pIdx++) { + jobParams.parameterValues.push(chunking.parameterValues[pIdx]); + } jobParams.parameterValues.push(applyDataToParameterTemplate(__compName + "_CompName", app.project.renderQueue.item(j).comp.name)); jobParams.parameterValues.push(applyDataToParameterTemplate(__compName + "_OutputPattern", dcUtil.removePercentageFromFileName(getRenderQueueItemData(app.project.renderQueue.item(j))["fileName"]))); jobParams.parameterValues.push(applyDataToParameterTemplate(__compName + "_OutputFormat", getRenderQueueItemData(app.project.renderQueue.item(j))["extension"])); @@ -505,9 +527,13 @@ function __generateSubmitButton() { { var compNameToCheck = dcUtil.removeIllegalCharacters(renderQueueItem.comp.name); var frameList = getFrameList(renderQueueItem); + + var chunking = applyFrameChunkingDataToTemplate(itemName, frameList) jobTemplate = generatePartialTemplate(); // Add data to the main template - jobTemplate.parameterDefinitions.push(applyDataToTemplate(itemName + "_Frames", dcDataTemplate.Frames)); + for (var pIdx = 0; pIdx < chunking.parameterDefinitions.length; pIdx++) { + jobTemplate.parameterDefinitions.push(chunking.parameterDefinitions[pIdx]); + } jobTemplate.parameterDefinitions.push(applyDataToTemplate(itemName + "_OutputPattern", dcDataTemplate.OutputPattern)); jobTemplate.parameterDefinitions.push(applyDataToTemplate(itemName + "_OutputFormat", dcDataTemplate.OutputFormat)); // jobTemplate.parameterDefinitions.push(applyDataToTemplate(itemName + "_CompName", dcDataTemplate.CompName)); @@ -523,7 +549,9 @@ function __generateSubmitButton() { jobParams.parameterValues.push(applyDataToParameterTemplate("deadline:maxFailedTasksCount", dcProperties.deadlineJobParameters.maxFailedTasksCount.get())); jobParams.parameterValues.push(applyDataToParameterTemplate("deadline:maxRetriesPerTask", dcProperties.deadlineJobParameters.maxRetriesPerTask.get())); jobParams.parameterValues.push(applyDataToParameterTemplate("deadline:priority", dcProperties.deadlineJobParameters.priority.get())); - jobParams.parameterValues.push(applyDataToParameterTemplate(itemName + "_Frames", frameList)); + for (var pIdx = 0; pIdx < chunking.parameterValues.length; pIdx++) { + jobParams.parameterValues.push(chunking.parameterValues[pIdx]); + } // jobParams.parameterValues.push(applyDataToParameterTemplate(itemName + "_CompName", comp)); jobParams.parameterValues.push(applyDataToParameterTemplate(itemName + "_OutputPattern", dcUtil.removePercentageFromFileName((getRenderQueueItemData(renderQueueItem)["fileName"])))); jobParams.parameterValues.push(applyDataToParameterTemplate(itemName + "_OutputFormat", getRenderQueueItemData(renderQueueItem)["extension"])); diff --git a/src/deadline/ae_submitter/data/RenderScript.jsx b/src/deadline/ae_submitter/data/RenderScript.jsx new file mode 100644 index 0000000..01f406d --- /dev/null +++ b/src/deadline/ae_submitter/data/RenderScript.jsx @@ -0,0 +1,19 @@ +function __generateRenderScriptTemplate(compParameterName) { + return ( +"@ECHO OFF\n" + +"IF \"%AFTEREFFECTS_ADAPTOR_AERENDER_EXECUTABLE%\" == \"\" (\n" + +" set AFTEREFFECTS_ADAPTOR_AERENDER_EXECUTABLE=aerender.exe\n" + +")\n" + +"echo \"Running: \\\"%AFTEREFFECTS_ADAPTOR_AERENDER_EXECUTABLE%\\\" -project \\\"{{Param.AfterEffectsProjectFile}}\\\" -comp \\\"" + compParameterName + "\\\" -s {{Task.Param.FrameChunkStart}} -e {{Task.Param.FrameChunkEnd}}\"\n" + +"\"%AFTEREFFECTS_ADAPTOR_AERENDER_EXECUTABLE%\" -project \"{{Param.AfterEffectsProjectFile}}\" -comp \"" + compParameterName + "\" -s {{Task.Param.FrameChunkStart}} -e {{Task.Param.FrameChunkEnd}}\n" + +"IF %ERRORLEVEL% NEQ 0 (\n" + +" echo \"Return code: %ERRORLEVEL%\"\n" + +" exit %ERRORLEVEL%\n" + +")\n" + +"exit 0\n" + ) +} + +dcRenderScript = { + "generateRenderCommand": __generateRenderScriptTemplate, +}; \ No newline at end of file diff --git a/src/deadline/ae_submitter/submission/DataTemplate.jsx b/src/deadline/ae_submitter/submission/DataTemplate.jsx index 21460c0..3f29ebd 100644 --- a/src/deadline/ae_submitter/submission/DataTemplate.jsx +++ b/src/deadline/ae_submitter/submission/DataTemplate.jsx @@ -1,16 +1,65 @@ function __generateDataTemplate() { - var Frames = + var FrameStart = { - "name": "Frames", + "name": "FrameStart", "type": "STRING", "userInterface": { "control": "LINE_EDIT", - "label": "Frames", + "label": "Starting Frames", "groupLabel": "After Effects Settings" }, - "description": "The frames to render. E.g. 1,8,11", + "description": "The starting frame to render.", "minLength": 1 } + var FrameEnd = + { + "name": "FrameEnd", + "type": "STRING", + "userInterface": { + "control": "LINE_EDIT", + "label": "Ending Frames", + "groupLabel": "After Effects Settings" + }, + "description": "The ending frame to render.", + "minLength": 1 + } + var ChunkSize = + { + "name": "ChunkSize", + "type": "STRING", + "userInterface": { + "control": "LINE_EDIT", + "label": "Frames Per Task", + "groupLabel": "After Effects Settings" + }, + "description": "The chunk size of frames per task to render", + "minLength": 1 + } + var FrameStartPlusChunkSizeMinusOne = + { + "name": "FrameStartPlusChunkSizeMinusOne", + "type": "STRING", + "userInterface": { + "control": "LINE_EDIT", + "label": "FrameStart + ChunkSize - 1", + "groupLabel": "After Effects Settings" + }, + "description": "[Internal] This value needs to equal FrameStart + ChunkSize - 1", + "minLength": 1 + } + var FrameEndMinusOne = + { + "name": "FrameEndMinusOne", + "type": "STRING", + "userInterface": { + "control": "LINE_EDIT", + "label": "FrameEnd - 1", + "groupLabel": "After Effects Settings" + }, + "description": "[Internal] This value needs to equal FrameEnd - 1", + "minLength": 1 + } + var OutputPattern = { "name": "OutputPattern", @@ -45,7 +94,11 @@ function __generateDataTemplate() { "description": "The render output path." } return { - "Frames": Frames, + "FrameStart": FrameStart, + "FrameEnd": FrameEnd, + "ChunkSize": ChunkSize, + "FrameStartPlusChunkSizeMinusOne": FrameStartPlusChunkSizeMinusOne, + "FrameEndMinusOne": FrameEndMinusOne, "OutputPattern" : OutputPattern, "OutputFormat": OutputFormat, "CompName": CompName, diff --git a/src/deadline/ae_submitter/submission/JobTemplate.jsx b/src/deadline/ae_submitter/submission/JobTemplate.jsx index 9c2eb7f..f32cead 100644 --- a/src/deadline/ae_submitter/submission/JobTemplate.jsx +++ b/src/deadline/ae_submitter/submission/JobTemplate.jsx @@ -29,14 +29,58 @@ var OPENJD_TEMPLATE = { "description": "The After Effects Project file to render." }, { - "name": "Frames", + "name": "FrameStart", "type": "STRING", "userInterface": { "control": "LINE_EDIT", - "label": "Frames", + "label": "Starting Frame", "groupLabel": "After Effects Settings" }, - "description": "The frame range to render. E.g. 1,8,11", + "description": "The starting frame to render.", + "minLength": 1 + }, + { + "name": "FrameEnd", + "type": "STRING", + "userInterface": { + "control": "LINE_EDIT", + "label": "Ending Frame", + "groupLabel": "After Effects Settings" + }, + "description": "The ending frame to render.", + "minLength": 1 + }, + { + "name": "ChunkSize", + "type": "STRING", + "userInterface": { + "control": "LINE_EDIT", + "label": "Frames Per Task", + "groupLabel": "After Effects Settings" + }, + "description": "The chunk size of frames per task to render", + "minLength": 1 + }, + { + "name": "FrameStartPlusChunkSizeMinusOne", + "type": "STRING", + "userInterface": { + "control": "LINE_EDIT", + "label": "FrameStart + ChunkSize - 1", + "groupLabel": "After Effects Settings" + }, + "description": "[Internal] This value needs to equal FrameStart + ChunkSize - 1", + "minLength": 1 + }, + { + "name": "FrameEndMinusOne", + "type": "STRING", + "userInterface": { + "control": "LINE_EDIT", + "label": "FrameEnd - 1", + "groupLabel": "After Effects Settings" + }, + "description": "[Internal] This value needs to equal FrameEnd - 1", "minLength": 1 }, { @@ -74,11 +118,17 @@ var OPENJD_TEMPLATE = { "parameterSpace": { "taskParameterDefinitions": [ { - "name": "Frame", - "type": "INT", - "range": "{{Param.Frames}}" + "name": "FrameChunkStart", + "type": "INT", + "range": "{{Param.FrameStart}}-{{Param.FrameEnd}}:{{Param.ChunkSize}}" + }, + { + "name": "FrameChunkEnd", + "type": "INT", + "range": "{{Param.FrameStartPlusChunkSizeMinusOne}}-{{Param.FrameEndMinusOne}}:{{Param.ChunkSize}},{{Param.FrameEnd}}" } - ] + ], + "combination": "(FrameChunkStart, FrameChunkEnd)" }, "stepEnvironments": [{ "name": "Install Fonts", @@ -89,8 +139,8 @@ var OPENJD_TEMPLATE = { "name": "font_installer", "filename": "font_installer.py", "type": "TEXT", - "data": "import argparse\nimport os\nimport shutil\nimport ctypes\nfrom ctypes import wintypes\n\ntry:\n import winreg\nexcept ImportError:\n import _winreg as winreg\n\nuser32 = ctypes.WinDLL('user32', use_last_error=True)\ngdi32 = ctypes.WinDLL('gdi32', use_last_error=True)\n\nFONTS_REG_PATH = r'Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts'\n\nHWND_BROADCAST = 0xFFFF\nSMTO_ABORTIFHUNG = 0x0002\nWM_FONTCHANGE = 0x001D\nGFRI_DESCRIPTION = 1\nGFRI_ISTRUETYPE = 3\n\nINSTALL_SCOPE_USER = 'USER'\nINSTALL_SCOPE_SYSTEM = 'SYSTEM'\n\nFONT_LOCATION_SYSTEM = os.path.join(os.environ.get('SystemRoot'), 'Fonts')\nFONT_LOCATION_USER = os.path.join(os.environ.get('LocalAppData'), 'Microsoft', 'Windows', 'Fonts')\n\nFONT_EXTENSIONS = {'.OTF', 'TTF'}\n\n# Check if the Fonts folder exists, create it if it doesn't\nif not os.path.exists(FONT_LOCATION_USER):\n print('Creating User Fonts folder: %s' % FONT_LOCATION_USER)\n os.makedirs(FONT_LOCATION_USER)\n\ndef install_font(src_path, scope=INSTALL_SCOPE_USER):\n try:\n # copy the font to the Windows Fonts folder\n if scope == INSTALL_SCOPE_SYSTEM:\n dst_path = os.path.join(FONT_LOCATION_SYSTEM, os.path.basename(src_path))\n registry_scope = winreg.HKEY_LOCAL_MACHINE\n else:\n dst_path = os.path.join(FONT_LOCATION_USER, os.path.basename(src_path))\n registry_scope = winreg.HKEY_CURRENT_USER\n\n shutil.copy(src_path, dst_path)\n # load the font in the current session\n if not gdi32.AddFontResourceW(dst_path):\n os.remove(dst_path)\n raise Exception('AddFontResource failed to load \\'%s\\'' % src_path)\n # notify running programs\n user32.SendMessageTimeoutW(\n HWND_BROADCAST, WM_FONTCHANGE, 0, 0, SMTO_ABORTIFHUNG, 1000, None\n )\n # store the fontname/filename in the registry\n filename = os.path.basename(dst_path)\n fontname = os.path.splitext(filename)[0]\n # try to get the font's real name\n cb = wintypes.DWORD()\n if gdi32.GetFontResourceInfoW(filename, ctypes.byref(cb), None, GFRI_DESCRIPTION):\n buf = (ctypes.c_wchar * cb.value)()\n if gdi32.GetFontResourceInfoW(filename, ctypes.byref(cb), buf, GFRI_DESCRIPTION):\n fontname = buf.value\n is_truetype = wintypes.BOOL()\n cb.value = ctypes.sizeof(is_truetype)\n gdi32.GetFontResourceInfoW(\n filename, ctypes.byref(cb), ctypes.byref(is_truetype), GFRI_ISTRUETYPE\n )\n if is_truetype:\n fontname += ' (TrueType)'\n with winreg.OpenKey(registry_scope, FONTS_REG_PATH, 0, winreg.KEY_SET_VALUE) as key:\n winreg.SetValueEx(key, fontname, 0, winreg.REG_SZ, filename)\n except Exception:\n import traceback\n\n return False, traceback.format_exc()\n return True, ''\n\n\ndef uninstall_font(src_path, scope=INSTALL_SCOPE_USER):\n try:\n # copy the font to the Windows Fonts folder\n if scope == INSTALL_SCOPE_SYSTEM:\n dst_path = os.path.join(FONT_LOCATION_SYSTEM, os.path.basename(src_path))\n registry_scope = winreg.HKEY_LOCAL_MACHINE\n else:\n dst_path = os.path.join(FONT_LOCATION_USER, os.path.basename(src_path))\n registry_scope = winreg.HKEY_CURRENT_USER\n\n # remove the fontname/filename from the registry\n filename = os.path.basename(dst_path)\n fontname = os.path.splitext(filename)[0]\n # try to get the font's real name\n cb = wintypes.DWORD()\n if gdi32.GetFontResourceInfoW(filename, ctypes.byref(cb), None, GFRI_DESCRIPTION):\n buf = (ctypes.c_wchar * cb.value)()\n if gdi32.GetFontResourceInfoW(filename, ctypes.byref(cb), buf, GFRI_DESCRIPTION):\n fontname = buf.value\n is_truetype = wintypes.BOOL()\n cb.value = ctypes.sizeof(is_truetype)\n gdi32.GetFontResourceInfoW(\n filename, ctypes.byref(cb), ctypes.byref(is_truetype), GFRI_ISTRUETYPE\n )\n if is_truetype:\n fontname += ' (TrueType)'\n\n with winreg.OpenKey(registry_scope, FONTS_REG_PATH, 0, winreg.KEY_SET_VALUE) as key:\n winreg.DeleteValue(key, fontname)\n\n # unload the font in the current session\n if not gdi32.RemoveFontResourceW(dst_path):\n os.remove(dst_path)\n raise Exception('RemoveFontResourceW failed to load \\'%s\\'' % src_path)\n\n if os.path.exists(dst_path):\n os.remove(dst_path)\n\n # notify running programs\n user32.SendMessageTimeoutW(\n HWND_BROADCAST, WM_FONTCHANGE, 0, 0, SMTO_ABORTIFHUNG, 1000, None\n )\n except Exception:\n import traceback\n\n return False, traceback.format_exc()\n return True, ''\n\ndef _find_fonts(folder):\n fonts = set()\n for path in os.listdir(folder):\n print('path: ' + path)\n if path.startswith('assetroot-'):\n asset_dir = os.path.join(folder, path)\n print(' asset_dir: ' + asset_dir)\n for asset_path in os.listdir(asset_dir):\n print(' asset_path: ' + asset_path)\n _, ext = os.path.splitext(os.path.join(asset_dir, asset_path))\n if ext.upper() in FONT_EXTENSIONS:\n fonts.add(os.path.join(asset_dir, asset_path))\n return fonts\n\ndef _install_fonts(folder):\n fonts = _find_fonts(folder)\n installed_fonts = set()\n if not fonts:\n print('No fonts to install')\n return\n\n for font in fonts:\n print('Installing font: %s' % font)\n installed, msg = install_font(font)\n if not installed:\n print(' Error installing font: %s' % msg)\n else:\n installed_fonts.add(font)\n return installed_fonts\n\ndef _remove_fonts(folder):\n fonts = _find_fonts(folder)\n if not fonts:\n print('No fonts to uninstall')\n return\n\n for font in fonts:\n print('Uninstalling font: %s' % font)\n uninstalled, msg = uninstall_font(font)\n if not uninstalled:\n print(' Error uninstalling font: %s' % msg)\n\nif __name__ == '__main__':\n parser = argparse.ArgumentParser(\n prog='InstallFonts',\n description='Installs and uninstalls fonts for After Effects')\n\n group = parser.add_mutually_exclusive_group(required=True)\n group.add_argument('-i', '--install', metavar='FOLDER_PATH', help='Install font located in FOLDER_PATH')\n group.add_argument('-un', '--uninstall', metavar='FOLDER_PATH', help='Remove fonts located in FOLDER_PATH')\n\n args = parser.parse_args()\n if args.install:\n _install_fonts(args.install)\n elif args.uninstall:\n _remove_fonts(args.uninstall)\n else:\n raise RuntimeError('Argparse forces you to specify install or uninstall')\n \n print('Done running font installer')", - } + "data": "import argparse\nimport os\nimport shutil\nimport ctypes\nfrom ctypes import wintypes\n\ntry:\n import winreg\nexcept ImportError:\n import _winreg as winreg\n\nuser32 = ctypes.WinDLL('user32', use_last_error=True)\ngdi32 = ctypes.WinDLL('gdi32', use_last_error=True)\n\nFONTS_REG_PATH = r'Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts'\n\nHWND_BROADCAST = 0xFFFF\nSMTO_ABORTIFHUNG = 0x0002\nWM_FONTCHANGE = 0x001D\nGFRI_DESCRIPTION = 1\nGFRI_ISTRUETYPE = 3\n\nINSTALL_SCOPE_USER = 'USER'\nINSTALL_SCOPE_SYSTEM = 'SYSTEM'\n\nFONT_LOCATION_SYSTEM = os.path.join(os.environ.get('SystemRoot'), 'Fonts')\nFONT_LOCATION_USER = os.path.join(os.environ.get('LocalAppData'), 'Microsoft', 'Windows', 'Fonts')\n\nFONT_EXTENSIONS = {'.OTF', 'TTF'}\n\n# Check if the Fonts folder exists, create it if it doesn't\nif not os.path.exists(FONT_LOCATION_USER):\n print('Creating User Fonts folder: %s' % FONT_LOCATION_USER)\n os.makedirs(FONT_LOCATION_USER)\n\ndef install_font(src_path, scope=INSTALL_SCOPE_USER):\n try:\n # copy the font to the Windows Fonts folder\n if scope == INSTALL_SCOPE_SYSTEM:\n dst_path = os.path.join(FONT_LOCATION_SYSTEM, os.path.basename(src_path))\n registry_scope = winreg.HKEY_LOCAL_MACHINE\n else:\n dst_path = os.path.join(FONT_LOCATION_USER, os.path.basename(src_path))\n registry_scope = winreg.HKEY_CURRENT_USER\n\n shutil.copy(src_path, dst_path)\n # load the font in the current session\n if not gdi32.AddFontResourceW(dst_path):\n os.remove(dst_path)\n raise Exception('AddFontResource failed to load \\'%s\\'' % src_path)\n # notify running programs\n user32.SendMessageTimeoutW(\n HWND_BROADCAST, WM_FONTCHANGE, 0, 0, SMTO_ABORTIFHUNG, 1000, None\n )\n # store the fontname/filename in the registry\n filename = os.path.basename(dst_path)\n fontname = os.path.splitext(filename)[0]\n # try to get the font's real name\n cb = wintypes.DWORD()\n if gdi32.GetFontResourceInfoW(filename, ctypes.byref(cb), None, GFRI_DESCRIPTION):\n buf = (ctypes.c_wchar * cb.value)()\n if gdi32.GetFontResourceInfoW(filename, ctypes.byref(cb), buf, GFRI_DESCRIPTION):\n fontname = buf.value\n is_truetype = wintypes.BOOL()\n cb.value = ctypes.sizeof(is_truetype)\n gdi32.GetFontResourceInfoW(\n filename, ctypes.byref(cb), ctypes.byref(is_truetype), GFRI_ISTRUETYPE\n )\n if is_truetype:\n fontname += ' (TrueType)'\n with winreg.OpenKey(registry_scope, FONTS_REG_PATH, 0, winreg.KEY_SET_VALUE) as key:\n winreg.SetValueEx(key, fontname, 0, winreg.REG_SZ, filename)\n except Exception:\n import traceback\n\n return False, traceback.format_exc()\n return True, ''\n\n\ndef uninstall_font(src_path, scope=INSTALL_SCOPE_USER):\n try:\n # copy the font to the Windows Fonts folder\n if scope == INSTALL_SCOPE_SYSTEM:\n dst_path = os.path.join(FONT_LOCATION_SYSTEM, os.path.basename(src_path))\n registry_scope = winreg.HKEY_LOCAL_MACHINE\n else:\n dst_path = os.path.join(FONT_LOCATION_USER, os.path.basename(src_path))\n registry_scope = winreg.HKEY_CURRENT_USER\n\n # remove the fontname/filename from the registry\n filename = os.path.basename(dst_path)\n fontname = os.path.splitext(filename)[0]\n # try to get the font's real name\n cb = wintypes.DWORD()\n if gdi32.GetFontResourceInfoW(filename, ctypes.byref(cb), None, GFRI_DESCRIPTION):\n buf = (ctypes.c_wchar * cb.value)()\n if gdi32.GetFontResourceInfoW(filename, ctypes.byref(cb), buf, GFRI_DESCRIPTION):\n fontname = buf.value\n is_truetype = wintypes.BOOL()\n cb.value = ctypes.sizeof(is_truetype)\n gdi32.GetFontResourceInfoW(\n filename, ctypes.byref(cb), ctypes.byref(is_truetype), GFRI_ISTRUETYPE\n )\n if is_truetype:\n fontname += ' (TrueType)'\n\n with winreg.OpenKey(registry_scope, FONTS_REG_PATH, 0, winreg.KEY_SET_VALUE) as key:\n winreg.DeleteValue(key, fontname)\n\n # unload the font in the current session\n if not gdi32.RemoveFontResourceW(dst_path):\n os.remove(dst_path)\n raise Exception('RemoveFontResourceW failed to load \\'%s\\'' % src_path)\n\n if os.path.exists(dst_path):\n os.remove(dst_path)\n\n # notify running programs\n user32.SendMessageTimeoutW(\n HWND_BROADCAST, WM_FONTCHANGE, 0, 0, SMTO_ABORTIFHUNG, 1000, None\n )\n except Exception:\n import traceback\n\n return False, traceback.format_exc()\n return True, ''\n\ndef _find_fonts(folder):\n fonts = set()\n for path in os.listdir(folder):\n print('path: ' + path)\n if path.startswith('assetroot-'):\n asset_dir = os.path.join(folder, path)\n print(' asset_dir: ' + asset_dir)\n for asset_path in os.listdir(asset_dir):\n print(' asset_path: ' + asset_path)\n _, ext = os.path.splitext(os.path.join(asset_dir, asset_path))\n if ext.upper() in FONT_EXTENSIONS:\n fonts.add(os.path.join(asset_dir, asset_path))\n return fonts\n\ndef _install_fonts(folder):\n fonts = _find_fonts(folder)\n installed_fonts = set()\n if not fonts:\n print('No fonts to install')\n return\n\n for font in fonts:\n print('Installing font: %s' % font)\n installed, msg = install_font(font)\n if not installed:\n print(' Error installing font: %s' % msg)\n else:\n installed_fonts.add(font)\n return installed_fonts\n\ndef _remove_fonts(folder):\n fonts = _find_fonts(folder)\n if not fonts:\n print('No fonts to uninstall')\n return\n\n for font in fonts:\n print('Uninstalling font: %s' % font)\n uninstalled, msg = uninstall_font(font)\n if not uninstalled:\n print(' Error uninstalling font: %s' % msg)\n\nif __name__ == '__main__':\n parser = argparse.ArgumentParser(\n prog='InstallFonts',\n description='Installs and uninstalls fonts for After Effects')\n\n group = parser.add_mutually_exclusive_group(required=True)\n group.add_argument('-i', '--install', metavar='FOLDER_PATH', help='Install font located in FOLDER_PATH')\n group.add_argument('-un', '--uninstall', metavar='FOLDER_PATH', help='Remove fonts located in FOLDER_PATH')\n\n args = parser.parse_args()\n if args.install:\n _install_fonts(args.install)\n elif args.uninstall:\n _remove_fonts(args.uninstall)\n else:\n raise RuntimeError('Argparse forces you to specify install or uninstall')\n \n print('Done running font installer')" + } ], "actions": { "onEnter": { @@ -111,72 +161,23 @@ var OPENJD_TEMPLATE = { } } } - }, - { - "name": "AfterEffects", - "description": "Runs After Effects in the background.", - "script": { - "embeddedFiles": [ - { - "name": "initData", - "filename": "init-data.yaml", - "type": "TEXT", - "data": "project_file: {{Param.AfterEffectsProjectFile}} \n" - - }, - { - "name": "runStart", - "filename": "start.bat", - "type": "TEXT", - "data": "afterfx-openjd daemon start --connection-file {{Session.WorkingDirectory}}/connection.json --init-data file://{{Env.File.initData}} \n" - }, - { - "name": "runStop", - "filename": "stop.bat", - "type": "TEXT", - "data": "afterfx-openjd daemon stop --connection-file {{Session.WorkingDirectory}}/connection.json \n" - } - ], - "actions": { - "onEnter": { - "command": "powershell", - "args": [ - "{{Env.File.runStart}}" - ] - }, - "onExit": { - "command": "powershell", - "args": [ - "{{Env.File.runStop}}" - ] - } - } - } }], "script": { "actions": { "onRun": { "command": "powershell", "args": [ - "{{Task.File.runScript}}" + "{{Task.File.runAerender}}" ] } }, "embeddedFiles": [ { - "name": "runData", - "filename": "run-data.yaml", - "type": "TEXT", - "data": [ - "frame: {{Task.Param.Frame}}" - ] - }, - { - "name": "runScript", - "filename": "bootstrap.bat", + "name": "runAerender", + "filename": "aerender.bat", "type": "TEXT", "runnable": true, - "data": "afterfx-openjd daemon run --connection-file {{ Session.WorkingDirectory }}/connection.json --run-data file://{{Task.File.runData}} \n" + "data": "\"%AFTEREFFECTS_ADAPTOR_AERENDER_EXECUTABLE%\" -project \"{{Param.AfterEffectsProjectFile}}\" -comp \"{{Task.Param.Comp}}\" -s {{Task.Param.FrameChunkStart}} -e {{Task.Param.FrameChunkEnd}} || exit /b 1 \n" } ] } @@ -337,4 +338,4 @@ var OPENJD_TEMPLATE_LAYER = { ] } }] -} +} \ No newline at end of file diff --git a/src/deadline/ae_submitter/utils/Util.jsx b/src/deadline/ae_submitter/utils/Util.jsx index 1574389..19618f3 100644 --- a/src/deadline/ae_submitter/utils/Util.jsx +++ b/src/deadline/ae_submitter/utils/Util.jsx @@ -837,6 +837,40 @@ function __generateUtil() { return fileName; } + function getFrameChunkParameters(frameList, framesPerTask) { + var frameChunkParametersList = []; + var splitList = frameList.split(","); + + for(var i = 0; i < splitList.length; i++) { + if(splitList[i].indexOf("-") == -1) + { + // Single Frame + frameChunkParametersList.push({ + "originalRange": splitList[i] + "-" + splitList[i], + "frameStart": parseInt(splitList[i]), + "frameEnd": parseInt(splitList[i]), + "chunkSize": 1, + "frameStartPlusChunkSizeMinusOne": parseInt(frameStart) + 1 - 1, + "frameEndMinusOne": parseInt(splitList[i]) - 1, + }) + } else { + // Range + var numbers = splitList[i].split("-"); + // Either the framesPerTask we requested, or the smaller range if X-Y is less than framesPerTask + var chunkSize = Math.min(parseInt(numbers[1]) - parseInt(numbers[0]) + 1, framesPerTask) + frameChunkParametersList.push({ + "originalRange": splitList[i], + "frameStart": parseInt(numbers[0]), + "frameEnd": parseInt(numbers[1]), + "chunkSize": chunkSize, + "frameStartPlusChunkSizeMinusOne": parseInt(numbers[0]) + chunkSize - 1, + "frameEndMinusOne": parseInt(numbers[1]) - 1, + }) + } + } + return frameChunkParametersList + } + function getDuplicateFrames(frameList) { /** @@ -954,6 +988,7 @@ function __generateUtil() { "removeIllegalCharacters": removeIllegalCharacters, "removePercentageFromFileName": removePercentageFromFileName, "getDuplicateFrames": getDuplicateFrames, + "getFrameChunkParameters": getFrameChunkParameters, "getTempFile": getTempFile, "getUserDirectory": getUserDirectory }