Merge pull request #760 from pypeclub/feature/AE-pull-ext

After Effects fixes
This commit is contained in:
Milan Kolar 2020-11-27 21:12:05 +01:00 committed by GitHub
commit e331518ddb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 134 additions and 31 deletions

View file

@ -520,6 +520,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin):
f.replace(orig_scene, new_scene) f.replace(orig_scene, new_scene)
) )
new_exp[aov] = replaced_files new_exp[aov] = replaced_files
# [] might be too much here, TODO
self._instance.data["expectedFiles"] = [new_exp] self._instance.data["expectedFiles"] = [new_exp]
else: else:
new_exp = [] new_exp = []
@ -527,7 +528,8 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin):
new_exp.append( new_exp.append(
f.replace(orig_scene, new_scene) 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( self.log.info("Scene name was switched {} -> {}".format(
orig_scene, new_scene orig_scene, new_scene
)) ))

View file

@ -252,6 +252,9 @@ class AfterEffectsServerStub():
Args: Args:
item_id (int): item_id (int):
Returns:
(namedtuple)
""" """
res = self.websocketserver.call(self.client.call res = self.websocketserver.call(self.client.call
('AfterEffects.get_work_area', ('AfterEffects.get_work_area',
@ -305,6 +308,35 @@ class AfterEffectsServerStub():
image_path=project_path, image_path=project_path,
as_copy=as_copy)) 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("Render queue needs to have file extension in 'Output to'")
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): def close(self):
self.client.close() self.client.close()

View file

@ -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("\\", "/")

View file

@ -11,6 +11,7 @@ from avalon import aftereffects
class AERenderInstance(RenderInstance): class AERenderInstance(RenderInstance):
# extend generic, composition name is needed # extend generic, composition name is needed
comp_name = attr.ib(default=None) comp_name = attr.ib(default=None)
comp_id = attr.ib(default=None)
class CollectAERender(abstract_collect_render.AbstractCollectRender): class CollectAERender(abstract_collect_render.AbstractCollectRender):
@ -39,13 +40,11 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender):
continue continue
work_area_info = aftereffects.stub().get_work_area(int(item_id)) work_area_info = aftereffects.stub().get_work_area(int(item_id))
frameStart = round(float(work_area_info.workAreaStart) * frameStart = work_area_info.workAreaStart
float(work_area_info.frameRate))
frameEnd = round(float(work_area_info.workAreaStart) * frameEnd = round(work_area_info.workAreaStart +
float(work_area_info.frameRate) +
float(work_area_info.workAreaDuration) * float(work_area_info.workAreaDuration) *
float(work_area_info.frameRate)) float(work_area_info.frameRate)) - 1
if inst["family"] == "render" and inst["active"]: if inst["family"] == "render" and inst["active"]:
instance = AERenderInstance( instance = AERenderInstance(
@ -83,6 +82,7 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender):
raise ValueError("There is no composition for item {}". raise ValueError("There is no composition for item {}".
format(item_id)) format(item_id))
instance.comp_name = comp.name instance.comp_name = comp.name
instance.comp_id = item_id
instance._anatomy = context.data["anatomy"] instance._anatomy = context.data["anatomy"]
instance.anatomyData = context.data["anatomyData"] instance.anatomyData = context.data["anatomyData"]
@ -108,18 +108,29 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender):
start = render_instance.frameStart start = render_instance.frameStart
end = render_instance.frameEnd 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) base_dir = self._get_output_dir(render_instance)
expected_files = [] expected_files = []
for frame in range(start, end + 1): if "#" not in render_q.file_name: # single frame (mov)W
path = os.path.join(base_dir, "{}_{}_{}.{}.{}".format( path = os.path.join(base_dir, "{}_{}_{}.{}".format(
render_instance.asset, render_instance.asset,
render_instance.subset, render_instance.subset,
"v{:03d}".format(render_instance.version), "v{:03d}".format(render_instance.version),
str(frame).zfill(self.padding_width), ext.replace('.', '')
self.rendered_extension ))
))
expected_files.append(path) 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 return expected_files
def _get_output_dir(self, render_instance): def _get_output_dir(self, render_instance):

View file

@ -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()

View file

@ -18,15 +18,18 @@ class DeadlinePluginInfo():
ProjectPath = attr.ib(default=None) ProjectPath = attr.ib(default=None)
AWSAssetFile0 = attr.ib(default=None) AWSAssetFile0 = attr.ib(default=None)
Version = attr.ib(default=None) Version = attr.ib(default=None)
MultiProcess = attr.ib(default=None)
class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
label = "Submit AE to Deadline" label = "Submit AE to Deadline"
order = pyblish.api.IntegratorOrder order = pyblish.api.IntegratorOrder + 0.1
hosts = ["aftereffects"] hosts = ["aftereffects"]
families = ["render.farm"] # cannot be "render' as that is integrated families = ["render.farm"] # cannot be "render' as that is integrated
use_published = False use_published = True
chunk_size = 1000000
def get_job_info(self): def get_job_info(self):
dln_job_info = DeadlineJobInfo(Plugin="AfterEffects") dln_job_info = DeadlineJobInfo(Plugin="AfterEffects")
@ -39,9 +42,12 @@ class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline
dln_job_info.Plugin = "AfterEffects" dln_job_info.Plugin = "AfterEffects"
dln_job_info.UserName = context.data.get( dln_job_info.UserName = context.data.get(
"deadlineUser", getpass.getuser()) "deadlineUser", getpass.getuser())
frame_range = "{}-{}".format(self._instance.data["frameStart"], if self._instance.data["frameEnd"] > self._instance.data["frameStart"]:
self._instance.data["frameEnd"]) frame_range = "{}-{}".format(self._instance.data["frameStart"],
dln_job_info.Frames = frame_range self._instance.data["frameEnd"])
dln_job_info.Frames = frame_range
dln_job_info.ChunkSize = self.chunk_size
dln_job_info.OutputFilename = \ dln_job_info.OutputFilename = \
os.path.basename(self._instance.data["expectedFiles"][0]) os.path.basename(self._instance.data["expectedFiles"][0])
dln_job_info.OutputDirectory = \ dln_job_info.OutputDirectory = \
@ -77,21 +83,25 @@ class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline
script_path = context.data["currentFile"] script_path = context.data["currentFile"]
render_path = self._instance.data["expectedFiles"][0] 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, if len(self._instance.data["expectedFiles"]) > 1:
'{}.{}.{}'.format(arr[0], hashed, arr[2])) # 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
deadline_plugin_info.Comp = self._instance.data["comp_name"] deadline_plugin_info.Comp = self._instance.data["comp_name"]
deadline_plugin_info.Version = "17.5" 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("\\", "/") deadline_plugin_info.Output = render_path.replace("\\", "/")
return attr.asdict(deadline_plugin_info) return attr.asdict(deadline_plugin_info)

View file

@ -600,6 +600,13 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
"files": os.path.basename(remainder), "files": os.path.basename(remainder),
"stagingDir": os.path.dirname(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: if remainder in bake_render_path:
rep.update({ rep.update({
"fps": instance.get("fps"), "fps": instance.get("fps"),