mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #5941 from ynput/enhancement/OP-6659_nuke-explicit-thumbnail-workflow
Nuke: Explicit Thumbnail workflow
This commit is contained in:
commit
e64f030a08
17 changed files with 231 additions and 529 deletions
|
|
@ -21,6 +21,11 @@ from openpype.pipeline import (
|
|||
CreatedInstance,
|
||||
get_current_task_name
|
||||
)
|
||||
from openpype.pipeline.colorspace import (
|
||||
get_display_view_colorspace_name,
|
||||
get_colorspace_settings_from_publish_context,
|
||||
set_colorspace_data_to_representation
|
||||
)
|
||||
from openpype.lib.transcoding import (
|
||||
VIDEO_EXTENSIONS
|
||||
)
|
||||
|
|
@ -612,7 +617,7 @@ class ExporterReview(object):
|
|||
|
||||
def get_representation_data(
|
||||
self, tags=None, range=False,
|
||||
custom_tags=None
|
||||
custom_tags=None, colorspace=None
|
||||
):
|
||||
""" Add representation data to self.data
|
||||
|
||||
|
|
@ -652,6 +657,14 @@ class ExporterReview(object):
|
|||
if self.publish_on_farm:
|
||||
repre["tags"].append("publish_on_farm")
|
||||
|
||||
# add colorspace data to representation
|
||||
if colorspace:
|
||||
set_colorspace_data_to_representation(
|
||||
repre,
|
||||
self.instance.context.data,
|
||||
colorspace=colorspace,
|
||||
log=self.log
|
||||
)
|
||||
self.data["representations"].append(repre)
|
||||
|
||||
def get_imageio_baking_profile(self):
|
||||
|
|
@ -866,6 +879,13 @@ class ExporterReviewMov(ExporterReview):
|
|||
return path
|
||||
|
||||
def generate_mov(self, farm=False, **kwargs):
|
||||
# colorspace data
|
||||
colorspace = None
|
||||
# get colorspace settings
|
||||
# get colorspace data from context
|
||||
config_data, _ = get_colorspace_settings_from_publish_context(
|
||||
self.instance.context.data)
|
||||
|
||||
add_tags = []
|
||||
self.publish_on_farm = farm
|
||||
read_raw = kwargs["read_raw"]
|
||||
|
|
@ -951,6 +971,14 @@ class ExporterReviewMov(ExporterReview):
|
|||
# assign viewer
|
||||
dag_node["view"].setValue(viewer)
|
||||
|
||||
if config_data:
|
||||
# convert display and view to colorspace
|
||||
colorspace = get_display_view_colorspace_name(
|
||||
config_path=config_data["path"],
|
||||
display=display,
|
||||
view=viewer
|
||||
)
|
||||
|
||||
self._connect_to_above_nodes(dag_node, subset, "OCIODisplay... `{}`")
|
||||
# Write node
|
||||
write_node = nuke.createNode("Write")
|
||||
|
|
@ -996,9 +1024,10 @@ class ExporterReviewMov(ExporterReview):
|
|||
|
||||
# ---------- generate representation data
|
||||
self.get_representation_data(
|
||||
tags=["review", "delete"] + add_tags,
|
||||
tags=["review", "need_thumbnail", "delete"] + add_tags,
|
||||
custom_tags=add_custom_tags,
|
||||
range=True
|
||||
range=True,
|
||||
colorspace=colorspace
|
||||
)
|
||||
|
||||
self.log.debug("Representation... `{}`".format(self.data))
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ class ExtractSlateFrame(publish.Extractor):
|
|||
|
||||
if not matching_repre:
|
||||
self.log.info(
|
||||
"Matching reresentation was not found."
|
||||
"Matching representation was not found."
|
||||
" Representation files were not filled with slate."
|
||||
)
|
||||
return
|
||||
|
|
@ -294,7 +294,7 @@ class ExtractSlateFrame(publish.Extractor):
|
|||
self.log.debug(
|
||||
"__ matching_repre: {}".format(pformat(matching_repre)))
|
||||
|
||||
self.log.warning("Added slate frame to representation files")
|
||||
self.log.info("Added slate frame to representation files")
|
||||
|
||||
def add_comment_slate_node(self, instance, node):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,216 +0,0 @@
|
|||
import sys
|
||||
import os
|
||||
import nuke
|
||||
import pyblish.api
|
||||
|
||||
from openpype.pipeline import publish
|
||||
from openpype.hosts.nuke import api as napi
|
||||
from openpype.hosts.nuke.api.lib import set_node_knobs_from_settings
|
||||
|
||||
|
||||
# Python 2/3 compatibility
|
||||
if sys.version_info[0] >= 3:
|
||||
unicode = str
|
||||
|
||||
|
||||
class ExtractThumbnail(publish.Extractor):
|
||||
"""Extracts movie and thumbnail with baked in luts
|
||||
|
||||
must be run after extract_render_local.py
|
||||
|
||||
"""
|
||||
|
||||
order = pyblish.api.ExtractorOrder + 0.011
|
||||
label = "Extract Thumbnail"
|
||||
|
||||
families = ["review"]
|
||||
hosts = ["nuke"]
|
||||
|
||||
# settings
|
||||
use_rendered = False
|
||||
bake_viewer_process = True
|
||||
bake_viewer_input_process = True
|
||||
nodes = {}
|
||||
reposition_nodes = None
|
||||
|
||||
def process(self, instance):
|
||||
if instance.data.get("farm"):
|
||||
return
|
||||
|
||||
with napi.maintained_selection():
|
||||
self.log.debug("instance: {}".format(instance))
|
||||
self.log.debug("instance.data[families]: {}".format(
|
||||
instance.data["families"]))
|
||||
|
||||
if instance.data.get("bakePresets"):
|
||||
for o_name, o_data in instance.data["bakePresets"].items():
|
||||
self.render_thumbnail(instance, o_name, **o_data)
|
||||
else:
|
||||
viewer_process_switches = {
|
||||
"bake_viewer_process": True,
|
||||
"bake_viewer_input_process": True
|
||||
}
|
||||
self.render_thumbnail(
|
||||
instance, None, **viewer_process_switches)
|
||||
|
||||
def render_thumbnail(self, instance, output_name=None, **kwargs):
|
||||
first_frame = instance.data["frameStartHandle"]
|
||||
last_frame = instance.data["frameEndHandle"]
|
||||
colorspace = instance.data["colorspace"]
|
||||
|
||||
# find frame range and define middle thumb frame
|
||||
mid_frame = int((last_frame - first_frame) / 2)
|
||||
|
||||
# solve output name if any is set
|
||||
output_name = output_name or ""
|
||||
|
||||
bake_viewer_process = kwargs["bake_viewer_process"]
|
||||
bake_viewer_input_process_node = kwargs[
|
||||
"bake_viewer_input_process"]
|
||||
|
||||
node = instance.data["transientData"]["node"] # group node
|
||||
self.log.debug("Creating staging dir...")
|
||||
|
||||
if "representations" not in instance.data:
|
||||
instance.data["representations"] = []
|
||||
|
||||
staging_dir = os.path.normpath(
|
||||
os.path.dirname(instance.data['path']))
|
||||
|
||||
instance.data["stagingDir"] = staging_dir
|
||||
|
||||
self.log.debug(
|
||||
"StagingDir `{0}`...".format(instance.data["stagingDir"]))
|
||||
|
||||
temporary_nodes = []
|
||||
|
||||
# try to connect already rendered images
|
||||
previous_node = node
|
||||
collection = instance.data.get("collection", None)
|
||||
self.log.debug("__ collection: `{}`".format(collection))
|
||||
|
||||
if collection:
|
||||
# get path
|
||||
fhead = collection.format("{head}")
|
||||
|
||||
thumb_fname = list(collection)[mid_frame]
|
||||
else:
|
||||
fname = thumb_fname = os.path.basename(
|
||||
instance.data.get("path", None))
|
||||
fhead = os.path.splitext(fname)[0] + "."
|
||||
|
||||
self.log.debug("__ fhead: `{}`".format(fhead))
|
||||
|
||||
if "#" in fhead:
|
||||
fhead = fhead.replace("#", "")[:-1]
|
||||
|
||||
path_render = os.path.join(
|
||||
staging_dir, thumb_fname).replace("\\", "/")
|
||||
self.log.debug("__ path_render: `{}`".format(path_render))
|
||||
|
||||
if self.use_rendered and os.path.isfile(path_render):
|
||||
# check if file exist otherwise connect to write node
|
||||
rnode = nuke.createNode("Read")
|
||||
rnode["file"].setValue(path_render)
|
||||
rnode["colorspace"].setValue(colorspace)
|
||||
|
||||
# turn it raw if none of baking is ON
|
||||
if all([
|
||||
not self.bake_viewer_input_process,
|
||||
not self.bake_viewer_process
|
||||
]):
|
||||
rnode["raw"].setValue(True)
|
||||
|
||||
temporary_nodes.append(rnode)
|
||||
previous_node = rnode
|
||||
|
||||
if self.reposition_nodes is None:
|
||||
# [deprecated] create reformat node old way
|
||||
reformat_node = nuke.createNode("Reformat")
|
||||
ref_node = self.nodes.get("Reformat", None)
|
||||
if ref_node:
|
||||
for k, v in ref_node:
|
||||
self.log.debug("k, v: {0}:{1}".format(k, v))
|
||||
if isinstance(v, unicode):
|
||||
v = str(v)
|
||||
reformat_node[k].setValue(v)
|
||||
|
||||
reformat_node.setInput(0, previous_node)
|
||||
previous_node = reformat_node
|
||||
temporary_nodes.append(reformat_node)
|
||||
else:
|
||||
# create reformat node new way
|
||||
for repo_node in self.reposition_nodes:
|
||||
node_class = repo_node["node_class"]
|
||||
knobs = repo_node["knobs"]
|
||||
node = nuke.createNode(node_class)
|
||||
set_node_knobs_from_settings(node, knobs)
|
||||
|
||||
# connect in order
|
||||
node.setInput(0, previous_node)
|
||||
previous_node = node
|
||||
temporary_nodes.append(node)
|
||||
|
||||
# only create colorspace baking if toggled on
|
||||
if bake_viewer_process:
|
||||
if bake_viewer_input_process_node:
|
||||
# get input process and connect it to baking
|
||||
ipn = napi.get_view_process_node()
|
||||
if ipn is not None:
|
||||
ipn.setInput(0, previous_node)
|
||||
previous_node = ipn
|
||||
temporary_nodes.append(ipn)
|
||||
|
||||
dag_node = nuke.createNode("OCIODisplay")
|
||||
dag_node.setInput(0, previous_node)
|
||||
previous_node = dag_node
|
||||
temporary_nodes.append(dag_node)
|
||||
|
||||
thumb_name = "thumbnail"
|
||||
# only add output name and
|
||||
# if there are more than one bake preset
|
||||
if (
|
||||
output_name
|
||||
and len(instance.data.get("bakePresets", {}).keys()) > 1
|
||||
):
|
||||
thumb_name = "{}_{}".format(output_name, thumb_name)
|
||||
|
||||
# create write node
|
||||
write_node = nuke.createNode("Write")
|
||||
file = fhead[:-1] + thumb_name + ".jpg"
|
||||
thumb_path = os.path.join(staging_dir, file).replace("\\", "/")
|
||||
|
||||
# add thumbnail to cleanup
|
||||
instance.context.data["cleanupFullPaths"].append(thumb_path)
|
||||
|
||||
# make sure only one thumbnail path is set
|
||||
# and it is existing file
|
||||
instance_thumb_path = instance.data.get("thumbnailPath")
|
||||
if not instance_thumb_path or not os.path.isfile(instance_thumb_path):
|
||||
instance.data["thumbnailPath"] = thumb_path
|
||||
|
||||
write_node["file"].setValue(thumb_path)
|
||||
write_node["file_type"].setValue("jpg")
|
||||
write_node["raw"].setValue(1)
|
||||
write_node.setInput(0, previous_node)
|
||||
temporary_nodes.append(write_node)
|
||||
|
||||
repre = {
|
||||
'name': thumb_name,
|
||||
'ext': "jpg",
|
||||
"outputName": thumb_name,
|
||||
'files': file,
|
||||
"stagingDir": staging_dir,
|
||||
"tags": ["thumbnail", "publish_on_farm", "delete"]
|
||||
}
|
||||
instance.data["representations"].append(repre)
|
||||
|
||||
# Render frames
|
||||
nuke.execute(write_node.name(), mid_frame, mid_frame)
|
||||
|
||||
self.log.debug(
|
||||
"representations: {}".format(instance.data["representations"]))
|
||||
|
||||
# Clean up
|
||||
for node in temporary_nodes:
|
||||
nuke.delete(node)
|
||||
|
|
@ -1103,7 +1103,6 @@ def convert_colorspace(
|
|||
target_colorspace=None,
|
||||
view=None,
|
||||
display=None,
|
||||
additional_input_args=None,
|
||||
additional_command_args=None,
|
||||
logger=None,
|
||||
):
|
||||
|
|
@ -1125,7 +1124,6 @@ def convert_colorspace(
|
|||
both 'view' and 'display' must be filled (if 'target_colorspace')
|
||||
display (str): name for display-referred reference space (ocio valid)
|
||||
both 'view' and 'display' must be filled (if 'target_colorspace')
|
||||
additional_input_args (list): arguments for input file
|
||||
additional_command_args (list): arguments for oiiotool (like binary
|
||||
depth for .dpx)
|
||||
logger (logging.Logger): Logger used for logging.
|
||||
|
|
@ -1140,9 +1138,6 @@ def convert_colorspace(
|
|||
# Collect channels to export
|
||||
input_arg, channels_arg = get_oiio_input_and_channel_args(input_info)
|
||||
|
||||
if additional_input_args:
|
||||
input_arg = "{} {}".format(input_arg, " ".join(additional_input_args))
|
||||
|
||||
# Prepare subprocess arguments
|
||||
oiio_cmd = get_oiio_tool_args(
|
||||
"oiiotool",
|
||||
|
|
|
|||
|
|
@ -370,10 +370,6 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin,
|
|||
environment = dict({key: os.environ[key] for key in keys
|
||||
if key in os.environ}, **legacy_io.Session)
|
||||
|
||||
for _path in os.environ:
|
||||
if _path.lower().startswith('openpype_'):
|
||||
environment[_path] = os.environ[_path]
|
||||
|
||||
# to recognize render jobs
|
||||
if AYON_SERVER_ENABLED:
|
||||
environment["AYON_BUNDLE_NAME"] = os.environ["AYON_BUNDLE_NAME"]
|
||||
|
|
@ -402,7 +398,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin,
|
|||
self.log.debug("Submitting..")
|
||||
self.log.debug(json.dumps(payload, indent=4, sort_keys=True))
|
||||
|
||||
# adding expectied files to instance.data
|
||||
# adding expected files to instance.data
|
||||
self.expected_files(
|
||||
instance,
|
||||
render_path,
|
||||
|
|
@ -458,7 +454,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin,
|
|||
def expected_files(
|
||||
self,
|
||||
instance,
|
||||
path,
|
||||
filepath,
|
||||
start_frame,
|
||||
end_frame
|
||||
):
|
||||
|
|
@ -467,21 +463,44 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin,
|
|||
if not instance.data.get("expectedFiles"):
|
||||
instance.data["expectedFiles"] = []
|
||||
|
||||
dirname = os.path.dirname(path)
|
||||
file = os.path.basename(path)
|
||||
dirname = os.path.dirname(filepath)
|
||||
file = os.path.basename(filepath)
|
||||
|
||||
# since some files might be already tagged as publish_on_farm
|
||||
# we need to avoid adding them to expected files since those would be
|
||||
# duplicated into metadata.json file
|
||||
representations = instance.data.get("representations", [])
|
||||
# check if file is not in representations with publish_on_farm tag
|
||||
for repre in representations:
|
||||
# Skip if 'publish_on_farm' not available
|
||||
if "publish_on_farm" not in repre.get("tags", []):
|
||||
continue
|
||||
|
||||
# in case where single file (video, image) is already in
|
||||
# representation file. Will be added to expected files via
|
||||
# submit_publish_job.py
|
||||
if file in repre.get("files", []):
|
||||
self.log.debug(
|
||||
"Skipping expected file: {}".format(filepath))
|
||||
return
|
||||
|
||||
# in case path is hashed sequence expression
|
||||
# (e.g. /path/to/file.####.png)
|
||||
if "#" in file:
|
||||
pparts = file.split("#")
|
||||
padding = "%0{}d".format(len(pparts) - 1)
|
||||
file = pparts[0] + padding + pparts[-1]
|
||||
|
||||
# in case input path was single file (video or image)
|
||||
if "%" not in file:
|
||||
instance.data["expectedFiles"].append(path)
|
||||
instance.data["expectedFiles"].append(filepath)
|
||||
return
|
||||
|
||||
# shift start frame by 1 if slate is present
|
||||
if instance.data.get("slate"):
|
||||
start_frame -= 1
|
||||
|
||||
# add sequence files to expected files
|
||||
for i in range(start_frame, (end_frame + 1)):
|
||||
instance.data["expectedFiles"].append(
|
||||
os.path.join(dirname, (file % i)).replace("\\", "/"))
|
||||
|
|
|
|||
|
|
@ -127,17 +127,25 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin):
|
|||
other_representations = []
|
||||
has_movie_review = False
|
||||
for repre in instance_repres:
|
||||
self.log.debug("Representation {}".format(repre))
|
||||
repre_tags = repre.get("tags") or []
|
||||
# exclude representations with are going to be published on farm
|
||||
if "publish_on_farm" in repre_tags:
|
||||
continue
|
||||
|
||||
self.log.debug("Representation {}".format(repre))
|
||||
|
||||
# include only thumbnail representations
|
||||
if repre.get("thumbnail") or "thumbnail" in repre_tags:
|
||||
thumbnail_representations.append(repre)
|
||||
|
||||
# include only review representations
|
||||
elif "ftrackreview" in repre_tags:
|
||||
review_representations.append(repre)
|
||||
if self._is_repre_video(repre):
|
||||
has_movie_review = True
|
||||
|
||||
else:
|
||||
# include all other representations
|
||||
other_representations.append(repre)
|
||||
|
||||
# Prepare ftrack locations
|
||||
|
|
|
|||
|
|
@ -145,6 +145,9 @@ def get_transferable_representations(instance):
|
|||
|
||||
trans_rep = representation.copy()
|
||||
|
||||
# remove publish_on_farm from representations tags
|
||||
trans_rep["tags"].remove("publish_on_farm")
|
||||
|
||||
staging_dir = trans_rep.get("stagingDir")
|
||||
|
||||
if staging_dir:
|
||||
|
|
|
|||
|
|
@ -89,8 +89,8 @@ class ExtractBurnin(publish.Extractor):
|
|||
|
||||
self.main_process(instance)
|
||||
|
||||
# Remove any representations tagged for deletion.
|
||||
# QUESTION Is possible to have representation with "delete" tag?
|
||||
# Remove only representation tagged with both
|
||||
# tags `delete` and `burnin`
|
||||
for repre in tuple(instance.data["representations"]):
|
||||
if all(x in repre.get("tags", []) for x in ['delete', 'burnin']):
|
||||
self.log.debug("Removing representation: {}".format(repre))
|
||||
|
|
|
|||
|
|
@ -89,8 +89,18 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
|||
# Make sure cleanup happens and pop representations with "delete" tag.
|
||||
for repre in tuple(instance.data["representations"]):
|
||||
tags = repre.get("tags") or []
|
||||
if "delete" in tags and "thumbnail" not in tags:
|
||||
instance.data["representations"].remove(repre)
|
||||
# Representation is not marked to be deleted
|
||||
if "delete" not in tags:
|
||||
continue
|
||||
|
||||
# The representation can be used as thumbnail source
|
||||
if "thumbnail" in tags or "need_thumbnail" in tags:
|
||||
continue
|
||||
|
||||
self.log.debug(
|
||||
"Removing representation: {}".format(repre)
|
||||
)
|
||||
instance.data["representations"].remove(repre)
|
||||
|
||||
def _get_outputs_for_instance(self, instance):
|
||||
host_name = instance.context.data["hostName"]
|
||||
|
|
@ -321,19 +331,26 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
|||
|
||||
# Create copy of representation
|
||||
new_repre = copy.deepcopy(repre)
|
||||
new_tags = new_repre.get("tags") or []
|
||||
# Make sure new representation has origin staging dir
|
||||
# - this is because source representation may change
|
||||
# it's staging dir because of ffmpeg conversion
|
||||
new_repre["stagingDir"] = src_repre_staging_dir
|
||||
|
||||
# Remove "delete" tag from new repre if there is
|
||||
if "delete" in new_repre["tags"]:
|
||||
new_repre["tags"].remove("delete")
|
||||
if "delete" in new_tags:
|
||||
new_tags.remove("delete")
|
||||
|
||||
if "need_thumbnail" in new_tags:
|
||||
new_tags.remove("need_thumbnail")
|
||||
|
||||
# Add additional tags from output definition to representation
|
||||
for tag in output_def["tags"]:
|
||||
if tag not in new_repre["tags"]:
|
||||
new_repre["tags"].append(tag)
|
||||
if tag not in new_tags:
|
||||
new_tags.append(tag)
|
||||
|
||||
# Return tags to new representation
|
||||
new_repre["tags"] = new_tags
|
||||
|
||||
# Add burnin link from output definition to representation
|
||||
for burnin in output_def["burnins"]:
|
||||
|
|
|
|||
|
|
@ -376,9 +376,13 @@ class ExtractReviewSlate(publish.Extractor):
|
|||
|
||||
# Remove any representations tagged for deletion.
|
||||
for repre in inst_data.get("representations", []):
|
||||
if "delete" in repre.get("tags", []):
|
||||
self.log.debug("Removing representation: {}".format(repre))
|
||||
inst_data["representations"].remove(repre)
|
||||
tags = repre.get("tags", [])
|
||||
if "delete" not in tags:
|
||||
continue
|
||||
if "need_thumbnail" in tags:
|
||||
continue
|
||||
self.log.debug("Removing representation: {}".format(repre))
|
||||
inst_data["representations"].remove(repre)
|
||||
|
||||
self.log.debug(inst_data["representations"])
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,14 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
"imagesequence", "render", "render2d", "prerender",
|
||||
"source", "clip", "take", "online", "image"
|
||||
]
|
||||
hosts = ["shell", "fusion", "resolve", "traypublisher", "substancepainter"]
|
||||
hosts = [
|
||||
"shell",
|
||||
"fusion",
|
||||
"resolve",
|
||||
"traypublisher",
|
||||
"substancepainter",
|
||||
"nuke",
|
||||
]
|
||||
enabled = False
|
||||
|
||||
integrate_thumbnail = False
|
||||
|
|
@ -44,6 +51,26 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
ffmpeg_args = None
|
||||
|
||||
def process(self, instance):
|
||||
# run main process
|
||||
self._main_process(instance)
|
||||
|
||||
# Make sure cleanup happens to representations which are having both
|
||||
# tags `delete` and `need_thumbnail`
|
||||
for repre in tuple(instance.data["representations"]):
|
||||
tags = repre.get("tags") or []
|
||||
# skip representations which are going to be published on farm
|
||||
if "publish_on_farm" in tags:
|
||||
continue
|
||||
if (
|
||||
"delete" in tags
|
||||
and "need_thumbnail" in tags
|
||||
):
|
||||
self.log.debug(
|
||||
"Removing representation: {}".format(repre)
|
||||
)
|
||||
instance.data["representations"].remove(repre)
|
||||
|
||||
def _main_process(self, instance):
|
||||
subset_name = instance.data["subset"]
|
||||
instance_repres = instance.data.get("representations")
|
||||
if not instance_repres:
|
||||
|
|
@ -76,7 +103,13 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
self.log.debug("Skipping crypto passes.")
|
||||
return
|
||||
|
||||
filtered_repres = self._get_filtered_repres(instance)
|
||||
# first check for any explicitly marked representations for thumbnail
|
||||
explicit_repres = self._get_explicit_repres_for_thumbnail(instance)
|
||||
if explicit_repres:
|
||||
filtered_repres = explicit_repres
|
||||
else:
|
||||
filtered_repres = self._get_filtered_repres(instance)
|
||||
|
||||
if not filtered_repres:
|
||||
self.log.info(
|
||||
"Instance doesn't have representations that can be used "
|
||||
|
|
@ -168,6 +201,24 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
if not thumbnail_created:
|
||||
continue
|
||||
|
||||
if len(explicit_repres) > 1:
|
||||
repre_name = "thumbnail_{}".format(repre["outputName"])
|
||||
else:
|
||||
repre_name = "thumbnail"
|
||||
|
||||
# add thumbnail path to instance data for integrator
|
||||
instance_thumb_path = instance.data.get("thumbnailPath")
|
||||
if (
|
||||
not instance_thumb_path
|
||||
or not os.path.isfile(instance_thumb_path)
|
||||
):
|
||||
self.log.debug(
|
||||
"Adding thumbnail path to instance data: {}".format(
|
||||
full_output_path
|
||||
)
|
||||
)
|
||||
instance.data["thumbnailPath"] = full_output_path
|
||||
|
||||
new_repre_tags = ["thumbnail"]
|
||||
# for workflows which needs to have thumbnails published as
|
||||
# separate representations `delete` tag should not be added
|
||||
|
|
@ -175,7 +226,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
new_repre_tags.append("delete")
|
||||
|
||||
new_repre = {
|
||||
"name": "thumbnail",
|
||||
"name": repre_name,
|
||||
"ext": "jpg",
|
||||
"files": jpeg_file,
|
||||
"stagingDir": dst_staging,
|
||||
|
|
@ -184,12 +235,21 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
}
|
||||
|
||||
# adding representation
|
||||
self.log.debug(
|
||||
"Adding thumbnail representation: {}".format(new_repre)
|
||||
)
|
||||
instance.data["representations"].append(new_repre)
|
||||
# There is no need to create more then one thumbnail
|
||||
break
|
||||
|
||||
if explicit_repres:
|
||||
# this key will then align assetVersion ftrack thumbnail sync
|
||||
new_repre["outputName"] = (
|
||||
repre.get("outputName") or repre["name"])
|
||||
self.log.debug(
|
||||
"Adding explicit thumbnail representation: {}".format(
|
||||
new_repre))
|
||||
else:
|
||||
self.log.debug(
|
||||
"Adding thumbnail representation: {}".format(new_repre)
|
||||
)
|
||||
# There is no need to create more then one thumbnail
|
||||
break
|
||||
|
||||
if not thumbnail_created:
|
||||
self.log.warning("Thumbnail has not been created.")
|
||||
|
|
@ -208,12 +268,42 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
return True
|
||||
return False
|
||||
|
||||
def _get_explicit_repres_for_thumbnail(self, instance):
|
||||
src_repres = instance.data.get("representations") or []
|
||||
# This is mainly for Nuke where we have multiple representations for
|
||||
# one instance and representations are tagged for thumbnail.
|
||||
# First check if any of the representations have
|
||||
# `need_thumbnail` in tags and add them to filtered_repres
|
||||
need_thumb_repres = [
|
||||
repre for repre in src_repres
|
||||
if "need_thumbnail" in repre.get("tags", [])
|
||||
if "publish_on_farm" not in repre.get("tags", [])
|
||||
]
|
||||
if not need_thumb_repres:
|
||||
return []
|
||||
|
||||
self.log.info(
|
||||
"Instance has representation with tag `need_thumbnail`. "
|
||||
"Using only this representations for thumbnail creation. "
|
||||
)
|
||||
self.log.debug(
|
||||
"Representations: {}".format(need_thumb_repres)
|
||||
)
|
||||
return need_thumb_repres
|
||||
|
||||
def _get_filtered_repres(self, instance):
|
||||
filtered_repres = []
|
||||
src_repres = instance.data.get("representations") or []
|
||||
|
||||
for repre in src_repres:
|
||||
self.log.debug(repre)
|
||||
tags = repre.get("tags") or []
|
||||
|
||||
if "publish_on_farm" in tags:
|
||||
# only process representations with are going
|
||||
# to be published locally
|
||||
continue
|
||||
|
||||
valid = "review" in tags or "thumb-nuke" in tags
|
||||
if not valid:
|
||||
continue
|
||||
|
|
@ -286,7 +376,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
display=repre_display or oiio_default_display,
|
||||
view=repre_view or oiio_default_view,
|
||||
target_colorspace=oiio_default_colorspace,
|
||||
additional_input_args=resolution_arg,
|
||||
additional_command_args=resolution_arg,
|
||||
logger=self.log,
|
||||
)
|
||||
except Exception:
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class IntegrateThumbnailsAYON(pyblish.api.ContextPlugin):
|
|||
)
|
||||
|
||||
def _prepare_instances(self, context):
|
||||
context_thumbnail_path = context.get("thumbnailPath")
|
||||
context_thumbnail_path = context.data.get("thumbnailPath")
|
||||
valid_context_thumbnail = bool(
|
||||
context_thumbnail_path
|
||||
and os.path.exists(context_thumbnail_path)
|
||||
|
|
@ -93,9 +93,12 @@ class IntegrateThumbnailsAYON(pyblish.api.ContextPlugin):
|
|||
|
||||
# Find thumbnail path on instance
|
||||
thumbnail_source = instance.data.get("thumbnailSource")
|
||||
thumbnail_path = (thumbnail_source or
|
||||
self._get_instance_thumbnail_path(
|
||||
published_repres))
|
||||
thumbnail_path = instance.data.get("thumbnailPath")
|
||||
thumbnail_path = (
|
||||
thumbnail_source
|
||||
or thumbnail_path
|
||||
or self._get_instance_thumbnail_path(published_repres)
|
||||
)
|
||||
if thumbnail_path:
|
||||
self.log.debug((
|
||||
"Found thumbnail path for instance \"{}\"."
|
||||
|
|
@ -133,7 +136,7 @@ class IntegrateThumbnailsAYON(pyblish.api.ContextPlugin):
|
|||
thumb_repre_doc = None
|
||||
for repre_info in published_representations.values():
|
||||
repre_doc = repre_info["representation"]
|
||||
if repre_doc["name"].lower() == "thumbnail":
|
||||
if "thumbnail" in repre_doc["name"].lower():
|
||||
thumb_repre_doc = repre_doc
|
||||
break
|
||||
|
||||
|
|
|
|||
|
|
@ -842,28 +842,6 @@ def _convert_nuke_project_settings(ayon_settings, output):
|
|||
collect_instance_data.pop(
|
||||
"sync_workfile_version_on_product_types"))
|
||||
|
||||
# TODO 'ExtractThumbnail' does not have ideal schema in v3
|
||||
ayon_extract_thumbnail = ayon_publish["ExtractThumbnail"]
|
||||
new_thumbnail_nodes = {}
|
||||
for item in ayon_extract_thumbnail["nodes"]:
|
||||
name = item["nodeclass"]
|
||||
value = []
|
||||
for knob in _convert_nuke_knobs(item["knobs"]):
|
||||
knob_name = knob["name"]
|
||||
# This may crash
|
||||
if knob["type"] == "expression":
|
||||
knob_value = knob["expression"]
|
||||
else:
|
||||
knob_value = knob["value"]
|
||||
value.append([knob_name, knob_value])
|
||||
new_thumbnail_nodes[name] = value
|
||||
|
||||
ayon_extract_thumbnail["nodes"] = new_thumbnail_nodes
|
||||
|
||||
if "reposition_nodes" in ayon_extract_thumbnail:
|
||||
for item in ayon_extract_thumbnail["reposition_nodes"]:
|
||||
item["knobs"] = _convert_nuke_knobs(item["knobs"])
|
||||
|
||||
# --- ImageIO ---
|
||||
# NOTE 'monitorOutLut' is maybe not yet in v3 (ut should be)
|
||||
_convert_host_imageio(ayon_nuke)
|
||||
|
|
|
|||
|
|
@ -379,68 +379,6 @@
|
|||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ExtractThumbnail": {
|
||||
"enabled": true,
|
||||
"use_rendered": true,
|
||||
"bake_viewer_process": true,
|
||||
"bake_viewer_input_process": true,
|
||||
"nodes": {
|
||||
"Reformat": [
|
||||
[
|
||||
"type",
|
||||
"to format"
|
||||
],
|
||||
[
|
||||
"format",
|
||||
"HD_1080"
|
||||
],
|
||||
[
|
||||
"filter",
|
||||
"Lanczos6"
|
||||
],
|
||||
[
|
||||
"black_outside",
|
||||
true
|
||||
],
|
||||
[
|
||||
"pbb",
|
||||
false
|
||||
]
|
||||
]
|
||||
},
|
||||
"reposition_nodes": [
|
||||
{
|
||||
"node_class": "Reformat",
|
||||
"knobs": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "type",
|
||||
"value": "to format"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "format",
|
||||
"value": "HD_1080"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "filter",
|
||||
"value": "Lanczos6"
|
||||
},
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "black_outside",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "pbb",
|
||||
"value": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ExtractReviewData": {
|
||||
"enabled": false
|
||||
},
|
||||
|
|
|
|||
|
|
@ -125,81 +125,6 @@
|
|||
"type": "label",
|
||||
"label": "Extractors"
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"key": "ExtractThumbnail",
|
||||
"label": "ExtractThumbnail",
|
||||
"is_group": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "use_rendered",
|
||||
"label": "Use rendered images"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "bake_viewer_process",
|
||||
"label": "Bake viewer process"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "bake_viewer_input_process",
|
||||
"label": "Bake viewer input process"
|
||||
},
|
||||
{
|
||||
"type": "collapsible-wrap",
|
||||
"label": "Nodes",
|
||||
"collapsible": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "label",
|
||||
"label": "Nodes attribute will be deprecated in future releases. Use reposition_nodes instead."
|
||||
},
|
||||
{
|
||||
"type": "raw-json",
|
||||
"key": "nodes",
|
||||
"label": "Nodes [depricated]"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"label": "Reposition knobs supported only. You can add multiple reformat nodes <br/>and set their knobs. Order of reformat nodes is important. First reformat node <br/>will be applied first and last reformat node will be applied last."
|
||||
},
|
||||
{
|
||||
"key": "reposition_nodes",
|
||||
"type": "list",
|
||||
"label": "Reposition nodes",
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"key": "node_class",
|
||||
"label": "Node class",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_nuke_knob_inputs",
|
||||
"template_data": [
|
||||
{
|
||||
"label": "Node knobs",
|
||||
"key": "knobs"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
|
|
|
|||
|
|
@ -51,17 +51,6 @@ class NodeModel(BaseSettingsModel):
|
|||
return value
|
||||
|
||||
|
||||
class ThumbnailRepositionNodeModel(BaseSettingsModel):
|
||||
node_class: str = Field(title="Node class")
|
||||
knobs: list[KnobModel] = Field(title="Knobs", default_factory=list)
|
||||
|
||||
@validator("knobs")
|
||||
def ensure_unique_names(cls, value):
|
||||
"""Ensure name fields within the lists have unique names."""
|
||||
ensure_unique_names(value)
|
||||
return value
|
||||
|
||||
|
||||
class CollectInstanceDataModel(BaseSettingsModel):
|
||||
sync_workfile_version_on_product_types: list[str] = Field(
|
||||
default_factory=list,
|
||||
|
|
@ -89,22 +78,6 @@ class ValidateKnobsModel(BaseSettingsModel):
|
|||
return validate_json_dict(value)
|
||||
|
||||
|
||||
class ExtractThumbnailModel(BaseSettingsModel):
|
||||
enabled: bool = Field(title="Enabled")
|
||||
use_rendered: bool = Field(title="Use rendered images")
|
||||
bake_viewer_process: bool = Field(title="Bake view process")
|
||||
bake_viewer_input_process: bool = Field(title="Bake viewer input process")
|
||||
|
||||
nodes: list[NodeModel] = Field(
|
||||
default_factory=list,
|
||||
title="Nodes (deprecated)"
|
||||
)
|
||||
reposition_nodes: list[ThumbnailRepositionNodeModel] = Field(
|
||||
title="Reposition nodes",
|
||||
default_factory=list
|
||||
)
|
||||
|
||||
|
||||
class ExtractReviewDataModel(BaseSettingsModel):
|
||||
enabled: bool = Field(title="Enabled")
|
||||
|
||||
|
|
@ -153,16 +126,29 @@ class IntermediateOutputModel(BaseSettingsModel):
|
|||
name: str = Field(title="Output name")
|
||||
filter: BakingStreamFilterModel = Field(
|
||||
title="Filter", default_factory=BakingStreamFilterModel)
|
||||
read_raw: bool = Field(title="Read raw switch")
|
||||
viewer_process_override: str = Field(title="Viewer process override")
|
||||
bake_viewer_process: bool = Field(title="Bake viewer process")
|
||||
read_raw: bool = Field(
|
||||
False,
|
||||
title="Read raw switch"
|
||||
)
|
||||
viewer_process_override: str = Field(
|
||||
"",
|
||||
title="Viewer process override"
|
||||
)
|
||||
bake_viewer_process: bool = Field(
|
||||
True,
|
||||
title="Bake viewer process"
|
||||
)
|
||||
bake_viewer_input_process: bool = Field(
|
||||
True,
|
||||
title="Bake viewer input process node (LUT)"
|
||||
)
|
||||
reformat_nodes_config: ReformatNodesConfigModel = Field(
|
||||
default_factory=ReformatNodesConfigModel,
|
||||
title="Reformat Nodes")
|
||||
extension: str = Field(title="File extension")
|
||||
extension: str = Field(
|
||||
"mov",
|
||||
title="File extension"
|
||||
)
|
||||
add_custom_tags: list[str] = Field(
|
||||
title="Custom tags", default_factory=list)
|
||||
|
||||
|
|
@ -267,11 +253,6 @@ class PublishPuginsModel(BaseSettingsModel):
|
|||
title="Validate workfile attributes",
|
||||
default_factory=OptionalPluginModel
|
||||
)
|
||||
ExtractThumbnail: ExtractThumbnailModel = Field(
|
||||
title="Extract Thumbnail",
|
||||
default_factory=ExtractThumbnailModel,
|
||||
section="Extractors"
|
||||
)
|
||||
ExtractReviewData: ExtractReviewDataModel = Field(
|
||||
title="Extract Review Data",
|
||||
default_factory=ExtractReviewDataModel
|
||||
|
|
@ -350,78 +331,6 @@ DEFAULT_PUBLISH_PLUGIN_SETTINGS = {
|
|||
"optional": True,
|
||||
"active": True
|
||||
},
|
||||
"ExtractThumbnail": {
|
||||
"enabled": True,
|
||||
"use_rendered": True,
|
||||
"bake_viewer_process": True,
|
||||
"bake_viewer_input_process": True,
|
||||
"nodes": [
|
||||
{
|
||||
"name": "Reformat01",
|
||||
"nodeclass": "Reformat",
|
||||
"dependency": "",
|
||||
"knobs": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "type",
|
||||
"text": "to format"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "format",
|
||||
"text": "HD_1080"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "filter",
|
||||
"text": "Lanczos6"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "black_outside",
|
||||
"boolean": True
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "pbb",
|
||||
"boolean": False
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"reposition_nodes": [
|
||||
{
|
||||
"node_class": "Reformat",
|
||||
"knobs": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "type",
|
||||
"text": "to format"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "format",
|
||||
"text": "HD_1080"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "filter",
|
||||
"text": "Lanczos6"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "black_outside",
|
||||
"boolean": True
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "pbb",
|
||||
"boolean": False
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ExtractReviewData": {
|
||||
"enabled": False
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
__version__ = "0.1.6"
|
||||
__version__ = "0.1.7"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue