diff --git a/client/ayon_core/lib/transcoding.py b/client/ayon_core/lib/transcoding.py index 75f0c8bc4d..127bd3bac4 100644 --- a/client/ayon_core/lib/transcoding.py +++ b/client/ayon_core/lib/transcoding.py @@ -420,11 +420,14 @@ def get_review_info_by_layer_name(channel_names): channel = last_part[0].upper() rgba_by_layer_name[layer_name][channel] = channel_name - # Put empty layer to the beginning of the list + # Put empty layer or 'rgba' to the beginning of the list # - if input has R, G, B, A channels they should be used for review - if "" in layer_names_order: - layer_names_order.remove("") - layer_names_order.insert(0, "") + # NOTE They are iterated in reversed order because they're inserted to + # the beginning of 'layer_names_order' -> last added will be first. + for name in reversed(["", "rgba"]): + if name in layer_names_order: + layer_names_order.remove(name) + layer_names_order.insert(0, name) output = [] for layer_name in layer_names_order: diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index 943f169b1c..b5885178d0 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -6,6 +6,7 @@ import re import pyblish.api from ayon_core.lib import ( + get_oiio_tool_args, get_ffmpeg_tool_args, get_ffprobe_data, @@ -15,7 +16,11 @@ from ayon_core.lib import ( path_to_subprocess_arg, run_subprocess, ) -from ayon_core.lib.transcoding import oiio_color_convert +from ayon_core.lib.transcoding import ( + oiio_color_convert, + get_oiio_input_and_channel_args, + get_oiio_info_for_input, +) from ayon_core.lib.transcoding import VIDEO_EXTENSIONS, IMAGE_EXTENSIONS @@ -210,6 +215,12 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): full_output_path = os.path.join(dst_staging, jpeg_file) colorspace_data = repre.get("colorspaceData") + # NOTE We should find out what is happening here. Why don't we + # use oiiotool all the time if it is available? Only possible + # reason might be that video files should be converted using + # ffmpeg, but other then that, we should use oiio all the time. + # - We should also probably get rid of the ffmpeg settings... + # only use OIIO if it is supported and representation has # colorspace data if oiio_supported and colorspace_data: @@ -219,7 +230,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): ) # If the input can read by OIIO then use OIIO method for # conversion otherwise use ffmpeg - repre_thumb_created = self._create_thumbnail_oiio( + repre_thumb_created = self._create_colorspace_thumbnail( full_input_path, full_output_path, colorspace_data @@ -229,17 +240,16 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # oiiotool isn't available or representation is not having # colorspace data if not repre_thumb_created: - if oiio_supported: - self.log.debug( - "Converting with FFMPEG because input" - " can't be read by OIIO." - ) - repre_thumb_created = self._create_thumbnail_ffmpeg( full_input_path, full_output_path ) - # Skip representation and try next one if wasn't created + # Skip representation and try next one if wasn't created + if not repre_thumb_created and oiio_supported: + repre_thumb_created = self._create_thumbnail_oiio( + full_input_path, full_output_path + ) + if not repre_thumb_created: continue @@ -382,7 +392,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): return ext in IMAGE_EXTENSIONS or ext in VIDEO_EXTENSIONS - def _create_thumbnail_oiio( + def _create_colorspace_thumbnail( self, src_path, dst_path, @@ -455,9 +465,50 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): return True + def _create_thumbnail_oiio(self, src_path, dst_path): + self.log.debug(f"Extracting thumbnail with OIIO: {dst_path}") + + try: + resolution_arg = self._get_resolution_arg("oiiotool", src_path) + except RuntimeError: + self.log.warning( + "Failed to create thumbnail using oiio", exc_info=True + ) + return False + + input_info = get_oiio_info_for_input(src_path, logger=self.log) + input_arg, channels_arg = get_oiio_input_and_channel_args(input_info) + oiio_cmd = get_oiio_tool_args( + "oiiotool", + input_arg, src_path, + # Tell oiiotool which channels should be put to top stack + # (and output) + "--ch", channels_arg, + # Use first subimage + "--subimage", "0" + ) + oiio_cmd.extend(resolution_arg) + oiio_cmd.extend(("-o", dst_path)) + self.log.debug("Running: {}".format(" ".join(oiio_cmd))) + try: + run_subprocess(oiio_cmd, logger=self.log) + return True + except Exception: + self.log.warning( + "Failed to create thumbnail using oiiotool", + exc_info=True + ) + return False + def _create_thumbnail_ffmpeg(self, src_path, dst_path): - self.log.debug("Extracting thumbnail with FFMPEG: {}".format(dst_path)) - resolution_arg = self._get_resolution_arg("ffmpeg", src_path) + try: + resolution_arg = self._get_resolution_arg("ffmpeg", src_path) + except RuntimeError: + self.log.warning( + "Failed to create thumbnail using ffmpeg", exc_info=True + ) + return False + ffmpeg_path_args = get_ffmpeg_tool_args("ffmpeg") ffmpeg_args = self.ffmpeg_args or {} diff --git a/client/ayon_core/scripts/otio_burnin.py b/client/ayon_core/scripts/otio_burnin.py index a4e8848e10..737faa7fe4 100644 --- a/client/ayon_core/scripts/otio_burnin.py +++ b/client/ayon_core/scripts/otio_burnin.py @@ -10,6 +10,8 @@ try: from otio_burnins_adapter import ffmpeg_burnins except ImportError: import opentimelineio_contrib.adapters.ffmpeg_burnins as ffmpeg_burnins +from PIL import ImageFont + from ayon_core.lib import ( get_ffmpeg_tool_args, get_ffmpeg_codec_args, @@ -39,6 +41,39 @@ TIMECODE_KEY = "{timecode}" SOURCE_TIMECODE_KEY = "{source_timecode}" +def _drawtext(align, resolution, text, options): + """ + :rtype: {'x': int, 'y': int} + """ + x_pos = "0" + if align in (ffmpeg_burnins.TOP_CENTERED, ffmpeg_burnins.BOTTOM_CENTERED): + x_pos = "w/2-tw/2" + + elif align in (ffmpeg_burnins.TOP_RIGHT, ffmpeg_burnins.BOTTOM_RIGHT): + ifont = ImageFont.truetype(options["font"], options["font_size"]) + if hasattr(ifont, "getbox"): + left, top, right, bottom = ifont.getbbox(text) + box_size = right - left, bottom - top + else: + box_size = ifont.getsize(text) + x_pos = resolution[0] - (box_size[0] + options["x_offset"]) + elif align in (ffmpeg_burnins.TOP_LEFT, ffmpeg_burnins.BOTTOM_LEFT): + x_pos = options["x_offset"] + + if align in ( + ffmpeg_burnins.TOP_CENTERED, + ffmpeg_burnins.TOP_RIGHT, + ffmpeg_burnins.TOP_LEFT + ): + y_pos = "%d" % options["y_offset"] + else: + y_pos = "h-text_h-%d" % (options["y_offset"]) + return {"x": x_pos, "y": y_pos} + + +ffmpeg_burnins._drawtext = _drawtext + + def _get_ffprobe_data(source): """Reimplemented from otio burnins to be able use full path to ffprobe :param str source: source media file