OP-3446 - implemented render_mov_batch in TrayPublisher

This commit is contained in:
Petr Kalis 2022-07-11 18:15:46 +02:00
parent 3b7a0a5bee
commit 74e57f9f49
5 changed files with 371 additions and 1 deletions

View file

@ -0,0 +1,61 @@
# Helper functions to find matching asset for (multiple) processed source files
import os
import collections
from openpype.client import get_assets
def get_children_assets_by_name(project_name, top_asset_doc):
""" Get all children for 'top_asset_doc' by theirs name
Args:
project_name (str)
top_asset_doc (asset doc) (eg dict)
Returns:
(dict) {"shot1": shot1_asset_doc}
"""
assets_by_parent_id = get_asset_docs_by_parent_id(project_name)
_children_docs = get_children_docs(
assets_by_parent_id, top_asset_doc
)
children_docs = {
children_doc["name"].lower(): children_doc
for children_doc in _children_docs
}
return children_docs
def get_asset_docs_by_parent_id(project_name):
""" Query all assets for project and store them by parent's id to list
Args:
project_name (str)
Returns:
(dict) { _id of parent :[asset_doc1, asset_doc2]}
"""
asset_docs_by_parent_id = collections.defaultdict(list)
for asset_doc in get_assets(project_name):
parent_id = asset_doc["data"]["visualParent"]
asset_docs_by_parent_id[parent_id].append(asset_doc)
return asset_docs_by_parent_id
def get_children_docs(documents_by_parent_id, parent_doc):
""" Recursively find all children in reverse order
Last children first.
Args:
documents_by_parent_id (dict)
parent_doc (asset doc, eg dict)
Returns
(list) of asset docs
"""
output = []
children = documents_by_parent_id.get(parent_doc["_id"]) or tuple()
for child in children:
output.extend(
get_children_docs(documents_by_parent_id, child)
)
output.append(parent_doc)
return output

View file

@ -0,0 +1,191 @@
import copy
import os
import re
from openpype.client import get_assets
from openpype.hosts.traypublisher.api import pipeline
from openpype.lib import FileDef, TextDef, get_subset_name_with_asset_doc
from openpype.pipeline import (
CreatedInstance
)
from openpype.hosts.traypublisher.api.plugin import TrayPublishCreator
class BatchMovCreator(TrayPublishCreator):
"""Creates instances from .mov file(s)."""
identifier = "render_mov_batch"
label = "Batch Mov"
family = "render"
description = "Publish batch of movs"
host_name = "traypublisher"
create_allow_context_change = False
version_regex = re.compile(r"^(.+)_v([0-9]+)$")
default_tasks = ["Compositing"]
extensions = [".mov"]
def __init__(self, project_settings, *args, **kwargs):
super(BatchMovCreator, self).__init__(project_settings,
*args, **kwargs)
self._default_variants = (project_settings["traypublisher"]
["BatchMovCreator"]
["default_variants"])
def get_icon(self):
return "fa.file"
def create(self, subset_name, data, pre_create_data):
file_paths = pre_create_data.get("filepath")
if not file_paths:
return
for file_info in file_paths:
instance_data = copy.deepcopy(data)
file_name = file_info["filenames"][0]
filepath = os.path.join(file_info["directory"], file_name)
instance_data["creator_attributes"] = {"filepath": filepath}
asset_doc, version = self.get_asset_doc_from_file_name(
file_name, self.project_name)
subset_name, task_name = self._get_subset_and_task(
asset_doc, data["variant"], self.project_name)
instance_data["task"] = task_name
instance_data["asset"] = asset_doc["name"]
# Create new instance
new_instance = CreatedInstance(self.family, subset_name,
instance_data, self)
# Host implementation of storing metadata about instance
pipeline.HostContext.add_instance(new_instance.data_to_store())
# Add instance to current context
self._add_instance_to_context(new_instance)
def get_asset_doc_from_file_name(self, source_filename, project_name):
"""Try to parse out asset name from file name provided.
Artists might provide various file name formats.
Currently handled:
- chair.mov
- chair_v001.mov
- my_chair_to_upload.mov
"""
version = None
asset_name = os.path.splitext(source_filename)[0]
# Always first check if source filename is in assets
matching_asset_doc = self._get_asset_by_name_case_not_sensitive(
project_name, asset_name)
if matching_asset_doc is None:
matching_asset_doc, version = (
self._parse_with_version(project_name, asset_name))
if matching_asset_doc is None:
matching_asset_doc = self._parse_containing(project_name,
asset_name)
if matching_asset_doc is None:
raise ValueError(
"Cannot guess asset name from {}".format(source_filename))
return matching_asset_doc, version
def _parse_with_version(self, project_name, asset_name):
"""Try to parse asset name from a file name containing version too
Eg. 'chair_v001.mov' >> 'chair', 1
"""
self.log.debug((
"Asset doc by \"{}\" was not found, trying version regex."
).format(asset_name))
matching_asset_doc = version_number = None
regex_result = self.version_regex.findall(asset_name)
if regex_result:
_asset_name, _version_number = regex_result[0]
matching_asset_doc = self._get_asset_by_name_case_not_sensitive(
project_name, _asset_name)
if matching_asset_doc:
version_number = int(_version_number)
return matching_asset_doc, version_number
def _parse_containing(self, project_name, asset_name):
"""Look if file name contains any existing asset name"""
for asset_doc in get_assets(project_name, fields=["name"]):
if asset_doc["name"].lower() in asset_name.lower():
return get_assets(project_name,
asset_names=[asset_doc["name"]])
def _get_subset_and_task(self, asset_doc, variant, project_name):
"""Create subset name according to standard template process"""
task_name = self._get_task_name(asset_doc)
subset_name = get_subset_name_with_asset_doc(
self.family,
variant,
task_name,
asset_doc,
project_name
)
return subset_name, task_name
def _get_task_name(self, asset_doc):
"""Get applicable task from 'asset_doc' """
available_task_names = {}
asset_tasks = asset_doc.get("data", {}).get("tasks") or {}
for task_name in asset_tasks.keys():
available_task_names[task_name.lower()] = task_name
task_name = None
for _task_name in self.default_tasks:
_task_name_low = _task_name.lower()
if _task_name_low in available_task_names:
task_name = available_task_names[_task_name_low]
break
return task_name
def get_default_variants(self):
return self._default_variants
def get_instance_attr_defs(self):
return []
def get_pre_create_attr_defs(self):
# Use same attributes as for instance attributes
return [
FileDef(
"filepath",
folders=False,
single_item=False,
extensions=self.extensions,
label="Filepath"
)
]
def get_detail_description(self):
return """# Publish batch of .mov to multiple assets.
File names must then contain only asset name, or asset name + version.
(eg. 'chair.mov', 'chair_v001.mov', not really safe `my_chair_v001.mov`
"""
def _get_asset_by_name_case_not_sensitive(self, project_name, asset_name):
"""Handle more cases in file names"""
asset_name = re.compile(asset_name, re.IGNORECASE)
assets = list(get_assets(project_name, asset_names=[asset_name]))
if assets:
if len(assets) > 1:
self.log.warning("Too many records found for {}".format(
asset_name))
return
return assets.pop()

View file

@ -0,0 +1,34 @@
import os
import pyblish.api
from openpype.pipeline import OpenPypePyblishPluginMixin
class CollectMovBatch(
pyblish.api.InstancePlugin, OpenPypePyblishPluginMixin
):
"""Collect file url for batch mov and create representation."""
label = "Collect Mov Batch Files"
order = pyblish.api.CollectorOrder
hosts = ["traypublisher"]
def process(self, instance):
if not instance.data.get("creator_identifier") == "render_mov_batch":
return
file_url = instance.data["creator_attributes"]["filepath"]
file_name = os.path.basename(file_url)
_, ext = os.path.splitext(file_name)
repre = {
"name": ext[1:],
"ext": ext[1:],
"files": file_name,
"stagingDir": os.path.dirname(file_url)
}
instance.data["representations"].append(repre)
self.log.debug("instance.data {}".format(instance.data))

View file

@ -31,5 +31,18 @@
".aep"
]
}
]
],
"BatchMovCreator": {
"family": "render_mov_batch",
"identifier": "",
"label": "Batch Mov",
"icon": "fa.file",
"default_variants": [],
"description": "",
"detailed_description": "",
"default_tasks": "Compositing",
"extensions": [
".mov"
]
}
}

View file

@ -78,6 +78,77 @@
}
]
}
},
{
"type": "dict",
"collapsible": true,
"key": "BatchMovCreator",
"label": "Batch Mov Creator",
"use_label_wrap": true,
"collapsible_key": true,
"children": [
{
"type": "text",
"key": "family",
"label": "Family"
},
{
"type": "text",
"key": "identifier",
"label": "Identifier",
"placeholder": "< Use 'Family' >",
"tooltip": "All creators must have unique identifier.\nBy default is used 'family' but if you need to have more creators with same families\nyou have to set identifier too."
},
{
"type": "text",
"key": "label",
"label": "Label"
},
{
"type": "text",
"key": "icon",
"label": "Icon"
},
{
"type": "list",
"key": "default_variants",
"label": "Default variants",
"object_type": {
"type": "text"
}
},
{
"type": "separator"
},
{
"type": "text",
"key": "description",
"label": "Description"
},
{
"type": "text",
"key": "detailed_description",
"label": "Detailed Description",
"multiline": true
},
{
"type": "separator"
},
{
"type": "text",
"key": "default_tasks",
"label": "Default task"
},
{
"type": "list",
"key": "extensions",
"label": "Extensions",
"use_label_wrap": true,
"collapsible_key": true,
"collapsed": false,
"object_type": "text"
}
]
}
]
}