From 7b025ffc830ac027c62227579204d1593415faf5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Nov 2020 13:07:26 +0100 Subject: [PATCH 1/3] Committing again after rebase Add pulling extension from AE Render Queue Add pulling information about audio layer for composition --- .../stubs/aftereffects_server_stub.py | 32 ++++++++++++++++ .../aftereffects/publish/collect_audio.py | 27 +++++++++++++ .../aftereffects/publish/collect_render.py | 31 ++++++++++----- .../publish/submit_aftereffects_deadline.py | 38 ++++++++++++------- 4 files changed, 106 insertions(+), 22 deletions(-) create mode 100644 pype/plugins/aftereffects/publish/collect_audio.py diff --git a/pype/modules/websocket_server/stubs/aftereffects_server_stub.py b/pype/modules/websocket_server/stubs/aftereffects_server_stub.py index 84dce39a41..e47aaee471 100644 --- a/pype/modules/websocket_server/stubs/aftereffects_server_stub.py +++ b/pype/modules/websocket_server/stubs/aftereffects_server_stub.py @@ -252,6 +252,9 @@ class AfterEffectsServerStub(): Args: item_id (int): + Returns: + (namedtuple) + """ res = self.websocketserver.call(self.client.call ('AfterEffects.get_work_area', @@ -305,6 +308,35 @@ class AfterEffectsServerStub(): image_path=project_path, as_copy=as_copy)) + def get_render_info(self): + """ Get render queue info for render purposes + + Returns: + (namedtuple): with 'file_name' field + """ + res = self.websocketserver.call(self.client.call + ('AfterEffects.get_render_info')) + + records = self._to_records(res) + if records: + return records.pop() + + log.debug("Couldn't get render queue info") + + def get_audio_url(self, item_id): + """ Get audio layer absolute url for comp + + Args: + item_id (int): composition id + Returns: + (str): absolute path url + """ + res = self.websocketserver.call(self.client.call + ('AfterEffects.get_audio_url', + item_id=item_id)) + + return res + def close(self): self.client.close() diff --git a/pype/plugins/aftereffects/publish/collect_audio.py b/pype/plugins/aftereffects/publish/collect_audio.py new file mode 100644 index 0000000000..37938eb6a6 --- /dev/null +++ b/pype/plugins/aftereffects/publish/collect_audio.py @@ -0,0 +1,27 @@ +import os + +import pyblish.api + +from avalon import aftereffects + + +class CollectAudio(pyblish.api.ContextPlugin): + """Inject audio file url for rendered composition into context. + Needs to run AFTER 'collect_render'. Use collected comp_id to check + if there is an AVLayer in this composition + """ + + order = pyblish.api.CollectorOrder + 0.499 + label = "Collect Audio" + hosts = ["aftereffects"] + + def process(self, context): + for instance in context: + if instance.data["family"] == 'render.farm': + comp_id = instance.data["comp_id"] + if not comp_id: + self.log.debug("No comp_id filled in instance") + return + context.data["audioFile"] = os.path.normpath( + aftereffects.stub().get_audio_url(comp_id) + ).replace("\\", "/") diff --git a/pype/plugins/aftereffects/publish/collect_render.py b/pype/plugins/aftereffects/publish/collect_render.py index 13ffc3f208..f053eb7ce3 100644 --- a/pype/plugins/aftereffects/publish/collect_render.py +++ b/pype/plugins/aftereffects/publish/collect_render.py @@ -11,6 +11,7 @@ from avalon import aftereffects class AERenderInstance(RenderInstance): # extend generic, composition name is needed comp_name = attr.ib(default=None) + comp_id = attr.ib(default=None) class CollectAERender(abstract_collect_render.AbstractCollectRender): @@ -83,6 +84,7 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): raise ValueError("There is no composition for item {}". format(item_id)) instance.comp_name = comp.name + instance.comp_id = item_id instance._anatomy = context.data["anatomy"] instance.anatomyData = context.data["anatomyData"] @@ -108,18 +110,29 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): start = render_instance.frameStart end = render_instance.frameEnd + # pull file name from Render Queue Output module + render_q = aftereffects.stub().get_render_info() + _, ext = os.path.splitext(os.path.basename(render_q.file_name)) base_dir = self._get_output_dir(render_instance) expected_files = [] - for frame in range(start, end + 1): - path = os.path.join(base_dir, "{}_{}_{}.{}.{}".format( - render_instance.asset, - render_instance.subset, - "v{:03d}".format(render_instance.version), - str(frame).zfill(self.padding_width), - self.rendered_extension - )) + if "#" not in render_q.file_name: # single frame (mov)W + path = os.path.join(base_dir, "{}_{}_{}.{}".format( + render_instance.asset, + render_instance.subset, + "v{:03d}".format(render_instance.version), + ext.replace('.', '') + )) expected_files.append(path) - + else: + for frame in range(start, end + 1): + path = os.path.join(base_dir, "{}_{}_{}.{}.{}".format( + render_instance.asset, + render_instance.subset, + "v{:03d}".format(render_instance.version), + str(frame).zfill(self.padding_width), + ext.replace('.', '') + )) + expected_files.append(path) return expected_files def _get_output_dir(self, render_instance): diff --git a/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py b/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py index 15d9e216fb..8bb42a0239 100644 --- a/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py +++ b/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py @@ -18,6 +18,7 @@ class DeadlinePluginInfo(): ProjectPath = attr.ib(default=None) AWSAssetFile0 = attr.ib(default=None) Version = attr.ib(default=None) + MultiProcess = attr.ib(default=None) class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): @@ -39,9 +40,14 @@ class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline dln_job_info.Plugin = "AfterEffects" dln_job_info.UserName = context.data.get( "deadlineUser", getpass.getuser()) - frame_range = "{}-{}".format(self._instance.data["frameStart"], - self._instance.data["frameEnd"]) - dln_job_info.Frames = frame_range + if self._instance.data["frameEnd"] > self._instance.data["frameStart"]: + frame_range = "{}-{}".format(self._instance.data["frameStart"], + self._instance.data["frameEnd"]) + dln_job_info.Frames = frame_range + + if len(self._instance.data["expectedFiles"]) == 1: + dln_job_info.ChunkSize = 1000000 + dln_job_info.OutputFilename = \ os.path.basename(self._instance.data["expectedFiles"][0]) dln_job_info.OutputDirectory = \ @@ -77,17 +83,23 @@ class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline script_path = context.data["currentFile"] render_path = self._instance.data["expectedFiles"][0] - # replace frame info ('000001') with Deadline's required '[#######]' - # expects filename in format project_asset_subset_version.FRAME.ext - render_dir = os.path.dirname(render_path) - file_name = os.path.basename(render_path) - arr = file_name.split('.') - assert len(arr) == 3, \ - "Unable to parse frames from {}".format(file_name) - hashed = '[{}]'.format(len(arr[1]) * "#") - render_path = os.path.join(render_dir, - '{}.{}.{}'.format(arr[0], hashed, arr[2])) + if len(self._instance.data["expectedFiles"]) > 1: + # replace frame ('000001') with Deadline's required '[#######]' + # expects filename in format project_asset_subset_version.FRAME.ext + render_dir = os.path.dirname(render_path) + file_name = os.path.basename(render_path) + arr = file_name.split('.') + assert len(arr) == 3, \ + "Unable to parse frames from {}".format(file_name) + hashed = '[{}]'.format(len(arr[1]) * "#") + + render_path = os.path.join(render_dir, + '{}.{}.{}'.format(arr[0], hashed, + arr[2])) + deadline_plugin_info.MultiProcess = True + else: + deadline_plugin_info.MultiProcess = False deadline_plugin_info.Comp = self._instance.data["comp_name"] deadline_plugin_info.Version = "17.5" From 9de325987a1f7073ef51dc5d13c449711c8a351f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Nov 2020 17:57:58 +0100 Subject: [PATCH 2/3] Fix frame start, fix frame end --- .../stubs/aftereffects_server_stub.py | 2 +- .../plugins/aftereffects/publish/collect_render.py | 8 +++----- .../aftereffects/publish/extract_save_scene.py | 14 ++++++++++++++ .../publish/submit_aftereffects_deadline.py | 9 ++++----- pype/plugins/global/publish/submit_publish_job.py | 7 +++++++ 5 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 pype/plugins/aftereffects/publish/extract_save_scene.py diff --git a/pype/modules/websocket_server/stubs/aftereffects_server_stub.py b/pype/modules/websocket_server/stubs/aftereffects_server_stub.py index e47aaee471..0b8c54e884 100644 --- a/pype/modules/websocket_server/stubs/aftereffects_server_stub.py +++ b/pype/modules/websocket_server/stubs/aftereffects_server_stub.py @@ -321,7 +321,7 @@ class AfterEffectsServerStub(): if records: return records.pop() - log.debug("Couldn't get render queue info") + log.debug("Render queue needs to have file extension in 'Output to'") def get_audio_url(self, item_id): """ Get audio layer absolute url for comp diff --git a/pype/plugins/aftereffects/publish/collect_render.py b/pype/plugins/aftereffects/publish/collect_render.py index f053eb7ce3..fbe392d52b 100644 --- a/pype/plugins/aftereffects/publish/collect_render.py +++ b/pype/plugins/aftereffects/publish/collect_render.py @@ -40,13 +40,11 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): continue work_area_info = aftereffects.stub().get_work_area(int(item_id)) - frameStart = round(float(work_area_info.workAreaStart) * - float(work_area_info.frameRate)) + frameStart = work_area_info.workAreaStart - frameEnd = round(float(work_area_info.workAreaStart) * - float(work_area_info.frameRate) + + frameEnd = round(work_area_info.workAreaStart + float(work_area_info.workAreaDuration) * - float(work_area_info.frameRate)) + float(work_area_info.frameRate)) - 1 if inst["family"] == "render" and inst["active"]: instance = AERenderInstance( diff --git a/pype/plugins/aftereffects/publish/extract_save_scene.py b/pype/plugins/aftereffects/publish/extract_save_scene.py new file mode 100644 index 0000000000..e19065d086 --- /dev/null +++ b/pype/plugins/aftereffects/publish/extract_save_scene.py @@ -0,0 +1,14 @@ +import pype.api +from avalon import aftereffects + + +class ExtractSaveScene(pype.api.Extractor): + """Save scene before extraction.""" + + order = pype.api.Extractor.order - 0.49 + label = "Extract Save Scene" + hosts = ["aftereffects"] + families = ["workfile"] + + def process(self, instance): + aftereffects.stub().save() diff --git a/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py b/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py index 8bb42a0239..d5f2747be7 100644 --- a/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py +++ b/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py @@ -29,6 +29,8 @@ class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline families = ["render.farm"] # cannot be "render' as that is integrated use_published = False + chunk_size = 1000000 + def get_job_info(self): dln_job_info = DeadlineJobInfo(Plugin="AfterEffects") @@ -45,8 +47,7 @@ class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline self._instance.data["frameEnd"]) dln_job_info.Frames = frame_range - if len(self._instance.data["expectedFiles"]) == 1: - dln_job_info.ChunkSize = 1000000 + dln_job_info.ChunkSize = self.chunk_size dln_job_info.OutputFilename = \ os.path.basename(self._instance.data["expectedFiles"][0]) @@ -97,10 +98,8 @@ class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline render_path = os.path.join(render_dir, '{}.{}.{}'.format(arr[0], hashed, arr[2])) - deadline_plugin_info.MultiProcess = True - else: - deadline_plugin_info.MultiProcess = False + deadline_plugin_info.MultiProcess = True deadline_plugin_info.Comp = self._instance.data["comp_name"] deadline_plugin_info.Version = "17.5" deadline_plugin_info.SceneFile = script_path diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 256bf01665..b8bf240c06 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -600,6 +600,13 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "files": os.path.basename(remainder), "stagingDir": os.path.dirname(remainder), } + if "render" in instance.get("families"): + rep.update({ + "fps": instance.get("fps"), + "tags": ["review"] + }) + self._solve_families(instance, True) + if remainder in bake_render_path: rep.update({ "fps": instance.get("fps"), From 6e9fc1c13e6760a39bcc445b1be5fa64b52a241f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Nov 2020 20:37:29 +0100 Subject: [PATCH 3/3] Fix activate 'use_published' --- pype/lib/abstract_submit_deadline.py | 4 +++- .../aftereffects/publish/submit_aftereffects_deadline.py | 7 +++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pype/lib/abstract_submit_deadline.py b/pype/lib/abstract_submit_deadline.py index 09916523a4..170e4908b7 100644 --- a/pype/lib/abstract_submit_deadline.py +++ b/pype/lib/abstract_submit_deadline.py @@ -520,6 +520,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): f.replace(orig_scene, new_scene) ) new_exp[aov] = replaced_files + # [] might be too much here, TODO self._instance.data["expectedFiles"] = [new_exp] else: new_exp = [] @@ -527,7 +528,8 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): new_exp.append( f.replace(orig_scene, new_scene) ) - self._instance.data["expectedFiles"] = [new_exp] + self._instance.data["expectedFiles"] = new_exp + self.log.info("Scene name was switched {} -> {}".format( orig_scene, new_scene )) diff --git a/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py b/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py index d5f2747be7..9414bdd39d 100644 --- a/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py +++ b/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py @@ -24,10 +24,10 @@ class DeadlinePluginInfo(): class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): label = "Submit AE to Deadline" - order = pyblish.api.IntegratorOrder + order = pyblish.api.IntegratorOrder + 0.1 hosts = ["aftereffects"] families = ["render.farm"] # cannot be "render' as that is integrated - use_published = False + use_published = True chunk_size = 1000000 @@ -48,7 +48,6 @@ class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline dln_job_info.Frames = frame_range dln_job_info.ChunkSize = self.chunk_size - dln_job_info.OutputFilename = \ os.path.basename(self._instance.data["expectedFiles"][0]) dln_job_info.OutputDirectory = \ @@ -102,7 +101,7 @@ class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline deadline_plugin_info.MultiProcess = True deadline_plugin_info.Comp = self._instance.data["comp_name"] deadline_plugin_info.Version = "17.5" - deadline_plugin_info.SceneFile = script_path + deadline_plugin_info.SceneFile = self.scene_path deadline_plugin_info.Output = render_path.replace("\\", "/") return attr.asdict(deadline_plugin_info)