From b7c827f35d4b8d223ed62838bb15d60abfdb8b48 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Oct 2019 13:13:51 +0200 Subject: [PATCH 01/10] family widget also stores and collect key of family in presets so can be trackable on pyblish --- pype/standalonepublish/widgets/__init__.py | 1 + pype/standalonepublish/widgets/widget_family.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pype/standalonepublish/widgets/__init__.py b/pype/standalonepublish/widgets/__init__.py index 4c6a0e85a5..c6e0dd9a47 100644 --- a/pype/standalonepublish/widgets/__init__.py +++ b/pype/standalonepublish/widgets/__init__.py @@ -6,6 +6,7 @@ HelpRole = QtCore.Qt.UserRole + 2 FamilyRole = QtCore.Qt.UserRole + 3 ExistsRole = QtCore.Qt.UserRole + 4 PluginRole = QtCore.Qt.UserRole + 5 +PluginKeyRole = QtCore.Qt.UserRole + 6 from ..resources import get_resource from .button_from_svgs import SvgResizable, SvgButton diff --git a/pype/standalonepublish/widgets/widget_family.py b/pype/standalonepublish/widgets/widget_family.py index 63776b1df3..26eb8077d9 100644 --- a/pype/standalonepublish/widgets/widget_family.py +++ b/pype/standalonepublish/widgets/widget_family.py @@ -5,7 +5,7 @@ import json from collections import namedtuple from . import QtWidgets, QtCore -from . import HelpRole, FamilyRole, ExistsRole, PluginRole +from . import HelpRole, FamilyRole, ExistsRole, PluginRole, PluginKeyRole from . import FamilyDescriptionWidget from pypeapp import config @@ -116,8 +116,10 @@ class FamilyWidget(QtWidgets.QWidget): def collect_data(self): plugin = self.list_families.currentItem().data(PluginRole) + key = self.list_families.currentItem().data(PluginKeyRole) family = plugin.family.rsplit(".", 1)[-1] data = { + 'family_preset_key': key, 'family': family, 'subset': self.input_result.text(), 'version': self.version_spinbox.value() @@ -318,7 +320,7 @@ class FamilyWidget(QtWidgets.QWidget): has_families = False presets = config.get_presets().get('standalone_publish', {}) - for creator in presets.get('families', {}).values(): + for key, creator in presets.get('families', {}).items(): creator = namedtuple("Creator", creator.keys())(*creator.values()) label = creator.label or creator.family @@ -327,6 +329,7 @@ class FamilyWidget(QtWidgets.QWidget): item.setData(HelpRole, creator.help or "") item.setData(FamilyRole, creator.family) item.setData(PluginRole, creator) + item.setData(PluginKeyRole, key) item.setData(ExistsRole, False) self.list_families.addItem(item) From 71ad16e650e8df39fe50bfb63a6792084e34e7bb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Oct 2019 13:14:50 +0200 Subject: [PATCH 02/10] drop frame uses full path to ffprobe set in FFMPEG_PATH env --- pype/standalonepublish/widgets/widget_drop_frame.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pype/standalonepublish/widgets/widget_drop_frame.py b/pype/standalonepublish/widgets/widget_drop_frame.py index e60db892db..a5a686bae1 100644 --- a/pype/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/standalonepublish/widgets/widget_drop_frame.py @@ -220,15 +220,21 @@ class DropDataFrame(QtWidgets.QFrame): self._process_data(data) def load_data_with_probe(self, filepath): + ffprobe_path = os.getenv("FFMPEG_PATH", "") + if ffprobe_path: + ffprobe_path += '/ffprobe' + else: + ffprobe_path = 'ffprobe' + args = [ - 'ffprobe', + ffprobe_path, '-v', 'quiet', '-print_format', 'json', '-show_format', '-show_streams', filepath ] ffprobe_p = subprocess.Popen( - args, + ' '.join(args), stdout=subprocess.PIPE, shell=True ) From c9b8bcc60247dbd4471c6e0280543158703f14de Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Oct 2019 13:15:14 +0200 Subject: [PATCH 03/10] host of application is set to standalonepublisher instead of shell --- pype/standalonepublish/publish.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/standalonepublish/publish.py b/pype/standalonepublish/publish.py index 13b505666c..f199aaf84e 100644 --- a/pype/standalonepublish/publish.py +++ b/pype/standalonepublish/publish.py @@ -103,7 +103,7 @@ def avalon_api_publish(data, gui=True): "-pp", os.pathsep.join(pyblish.api.registered_paths()) ] - os.environ["PYBLISH_HOSTS"] = "shell" + os.environ["PYBLISH_HOSTS"] = "standalonepublisher" os.environ["SAPUBLISH_INPATH"] = json_data_path if gui: @@ -139,7 +139,7 @@ def cli_publish(data, gui=True): if gui: args += ["gui"] - os.environ["PYBLISH_HOSTS"] = "shell" + os.environ["PYBLISH_HOSTS"] = "standalonepublisher" os.environ["SAPUBLISH_INPATH"] = json_data_path os.environ["SAPUBLISH_OUTPATH"] = return_data_path From 6f889eec9e332ba42ed7ea01df5e385e1e4164af Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Oct 2019 13:15:54 +0200 Subject: [PATCH 04/10] added conversion to int in case frameStart contain string --- pype/plugins/global/publish/integrate_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 6c89e22a83..a3efd10b2e 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -307,7 +307,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if repre.get("frameStart"): frame_start_padding = len(str( repre.get("frameEnd"))) - index_frame_start = repre.get("frameStart") + index_frame_start = int(repre.get("frameStart")) dst_padding_exp = src_padding_exp for i in src_collection.indexes: From e3ac16427256cb59a87eb86eb939246d7f245e70 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Oct 2019 13:17:30 +0200 Subject: [PATCH 05/10] collect scene version is skipped in standalone publisher --- pype/plugins/global/publish/collect_scene_version.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/plugins/global/publish/collect_scene_version.py b/pype/plugins/global/publish/collect_scene_version.py index 12075e2417..3fac823b5c 100644 --- a/pype/plugins/global/publish/collect_scene_version.py +++ b/pype/plugins/global/publish/collect_scene_version.py @@ -13,6 +13,8 @@ class CollectSceneVersion(pyblish.api.ContextPlugin): label = 'Collect Version' def process(self, context): + if "standalonepublisher" in context.data.get("host"): + return filename = os.path.basename(context.data.get('currentFile')) From 4bd116b096bd529dd3c09945ce9870fd42e38863 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Oct 2019 13:18:16 +0200 Subject: [PATCH 06/10] collect presets order is lowered so they are collected much earlier (before collect_context plugin) --- pype/plugins/global/publish/collect_presets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/collect_presets.py b/pype/plugins/global/publish/collect_presets.py index 7e0d3e2f4b..4d483ec79b 100644 --- a/pype/plugins/global/publish/collect_presets.py +++ b/pype/plugins/global/publish/collect_presets.py @@ -5,7 +5,7 @@ from pypeapp import config class CollectPresets(api.ContextPlugin): """Collect Presets.""" - order = api.CollectorOrder + order = api.CollectorOrder - 0.491 label = "Collect Presets" def process(self, context): From 3e00bb57e848f0e3074c4d4ff07444f9dd843fc9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Oct 2019 13:18:51 +0200 Subject: [PATCH 07/10] added standalonepublisher to hosts of collect output repre config --- pype/plugins/global/publish/collect_output_repre_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/collect_output_repre_config.py b/pype/plugins/global/publish/collect_output_repre_config.py index 5595e29cab..248599f749 100644 --- a/pype/plugins/global/publish/collect_output_repre_config.py +++ b/pype/plugins/global/publish/collect_output_repre_config.py @@ -9,7 +9,7 @@ class CollectOutputRepreConfig(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder label = "Collect Config for representation" - hosts = ["shell"] + hosts = ["shell", "standalonepublisher"] def process(self, context): config_data = config.get_presets()["ftrack"]["output_representation"] From ff0009f8147cbbe4c90bb0b346d8586fcb38be0c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Oct 2019 13:21:17 +0200 Subject: [PATCH 08/10] anatomy template is not set if family don't have set anatom_template key --- .../plugins/global/publish/collect_context.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/collect_context.py b/pype/plugins/global/publish/collect_context.py index 31ab95259c..61c3bcf4d8 100644 --- a/pype/plugins/global/publish/collect_context.py +++ b/pype/plugins/global/publish/collect_context.py @@ -31,9 +31,25 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): in_data = json.load(f) asset_name = in_data['asset'] + family_preset_key = in_data.get('family_preset_key', '') family = in_data['family'] subset = in_data['subset'] + # Load presets + presets = context.data.get("presets") + if not presets: + from pypeapp import config + presets = config.get_presets() + + # Get from presets anatomy key that will be used for getting template + # - default integrate new is used if not set + anatomy_key = presets.get( + "standalone_publish", {}).get( + "families", {}).get( + family_preset_key, {}).get( + "anatomy_template" + ) + project = io.find_one({'type': 'project'}) asset = io.find_one({ 'type': 'asset', @@ -63,7 +79,9 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): component['destination'] = component['files'] component['stagingDir'] = component['stagingDir'] - component['anatomy_template'] = 'render' + # Do not set anatomy_template if not specified + if anatomy_key: + component['anatomy_template'] = anatomy_key if isinstance(component['files'], list): collections, remainder = clique.assemble(component['files']) self.log.debug("collecting sequence: {}".format(collections)) From 195f9640bd87c44929db23c542497527333d2f23 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Oct 2019 13:22:37 +0200 Subject: [PATCH 09/10] added extractor for thumbnails in standalone publisher should create thumbnail from any image or video input --- .../global/publish/extract_thumbnail_sa.py | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 pype/plugins/global/publish/extract_thumbnail_sa.py diff --git a/pype/plugins/global/publish/extract_thumbnail_sa.py b/pype/plugins/global/publish/extract_thumbnail_sa.py new file mode 100644 index 0000000000..7e31e3c701 --- /dev/null +++ b/pype/plugins/global/publish/extract_thumbnail_sa.py @@ -0,0 +1,126 @@ +import os +import tempfile +import subprocess +import pyblish.api +import pype.api + + +class ExtractThumbnail(pyblish.api.InstancePlugin): + """Extract jpeg thumbnail from component input from standalone publisher + + Uses jpeg file from component if possible (when single or multiple jpegs + are loaded to component selected as thumbnail) otherwise extracts from + input file/s single jpeg to temp. + """ + + label = "Extract Thumbnail" + hosts = ["standalonepublisher"] + order = pyblish.api.ExtractorOrder + + def process(self, instance): + repres = instance.data.get('representations') + if not repres: + return + + thumbnail_repre = None + for repre in repres: + if repre.get("thumbnail"): + thumbnail_repre = repre + break + + if not thumbnail_repre: + return + + files = thumbnail_repre.get("files") + if not files: + return + + if isinstance(files, list): + files_len = len(files) + file = str(files[0]) + else: + files_len = 1 + file = files + + is_jpeg = False + if file.endswith(".jpeg") or file.endswith(".jpg"): + is_jpeg = True + + if is_jpeg and files_len == 1: + # skip if already is single jpeg file + return + + elif is_jpeg: + # use first frame as thumbnail if is sequence of jpegs + full_thumbnail_path = file + self.log.info( + "For thumbnail is used file: {}".format(full_thumbnail_path) + ) + + else: + # Convert to jpeg if not yet + full_input_path = os.path.join(thumbnail_repre["stagingDir"], file) + self.log.info("input {}".format(full_input_path)) + + full_thumbnail_path = tempfile.mkstemp(suffix=".jpg")[1] + self.log.info("output {}".format(full_thumbnail_path)) + + config_data = instance.context.data.get("output_repre_config", {}) + + proj_name = os.environ.get("AVALON_PROJECT", "__default__") + profile = config_data.get( + proj_name, + config_data.get("__default__", {}) + ) + + ffmpeg_path = os.getenv("FFMPEG_PATH", "") + if ffmpeg_path: + ffmpeg_path += "/ffmpeg" + else: + ffmpeg_path = "ffmpeg" + + jpeg_items = [] + jpeg_items.append(ffmpeg_path) + # override file if already exists + jpeg_items.append("-y") + # add input filters from peresets + if profile: + jpeg_items.extend(profile.get('input', [])) + # input file + jpeg_items.append("-i {}".format(full_input_path)) + # extract only single file + jpeg_items.append("-vframes 1") + # output file + jpeg_items.append(full_thumbnail_path) + + subprocess_jpeg = " ".join(jpeg_items) + + # run subprocess + self.log.debug("Executing: {}".format(subprocess_jpeg)) + subprocess.Popen( + subprocess_jpeg, + stdout=subprocess.PIPE, + shell=True + ) + + # remove thumbnail key from origin repre + thumbnail_repre.pop("thumbnail") + + filename = os.path.basename(full_thumbnail_path) + staging_dir = os.path.dirname(full_thumbnail_path) + + # create new thumbnail representation + representation = { + 'name': 'jpg', + 'ext': 'jpg', + 'files': filename, + "stagingDir": staging_dir, + "thumbnail": True, + "tags": [] + } + + # add Delete tag when temp file was rendered + if not is_jpeg: + representation["tags"].append("delete") + + instance.data["representations"].append(representation) From 97673d503ccdd70e5b991ebbbf144a5c836aa80e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 7 Oct 2019 12:32:35 +0200 Subject: [PATCH 10/10] fix(standalonepublish): getting `frameStart` and `frameEnd` from representaions --- pype/plugins/global/publish/collect_context.py | 2 ++ pype/plugins/global/publish/extract_thumbnail_sa.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pype/plugins/global/publish/collect_context.py b/pype/plugins/global/publish/collect_context.py index 61c3bcf4d8..f538509a9b 100644 --- a/pype/plugins/global/publish/collect_context.py +++ b/pype/plugins/global/publish/collect_context.py @@ -66,6 +66,8 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): "label": subset, "name": subset, "family": family, + "frameStart": in_data.get("representations", [None])[0].get("frameStart", None), + "frameEnd": in_data.get("representations", [None])[0].get("frameEnd", None), "families": [family, 'ftrack'], }) self.log.info("collected instance: {}".format(instance.data)) diff --git a/pype/plugins/global/publish/extract_thumbnail_sa.py b/pype/plugins/global/publish/extract_thumbnail_sa.py index 7e31e3c701..f42985b560 100644 --- a/pype/plugins/global/publish/extract_thumbnail_sa.py +++ b/pype/plugins/global/publish/extract_thumbnail_sa.py @@ -119,8 +119,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "tags": [] } - # add Delete tag when temp file was rendered - if not is_jpeg: - representation["tags"].append("delete") + # # add Delete tag when temp file was rendered + # if not is_jpeg: + # representation["tags"].append("delete") instance.data["representations"].append(representation)