mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 12:54:40 +01:00
Added profiles for ExtractThumbnail
This commit is contained in:
parent
7f40b6c6a2
commit
c0ed22c4d7
1 changed files with 132 additions and 20 deletions
|
|
@ -1,8 +1,10 @@
|
|||
import copy
|
||||
from dataclasses import dataclass, field, fields
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
import re
|
||||
from typing import Dict, Any, List, Tuple
|
||||
|
||||
import pyblish.api
|
||||
from ayon_core.lib import (
|
||||
|
|
@ -15,6 +17,7 @@ from ayon_core.lib import (
|
|||
|
||||
path_to_subprocess_arg,
|
||||
run_subprocess,
|
||||
filter_profiles,
|
||||
)
|
||||
from ayon_core.lib.transcoding import (
|
||||
MissingRGBAChannelsError,
|
||||
|
|
@ -26,6 +29,63 @@ from ayon_core.lib.transcoding import (
|
|||
from ayon_core.lib.transcoding import VIDEO_EXTENSIONS, IMAGE_EXTENSIONS
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProfileConfig:
|
||||
"""
|
||||
Data class representing the full configuration for selected profile
|
||||
|
||||
Any change of controllable fields in Settings must propagate here!
|
||||
"""
|
||||
product_names: List[str] = field(default_factory=list)
|
||||
|
||||
integrate_thumbnail: bool = False
|
||||
|
||||
target_size: Dict[str, Any] = field(
|
||||
default_factory=lambda: {
|
||||
"type": "source",
|
||||
"resize": {"width": 1920, "height": 1080},
|
||||
}
|
||||
)
|
||||
|
||||
duration_split: float = 0.5
|
||||
|
||||
oiiotool_defaults: Dict[str, str] = field(
|
||||
default_factory=lambda: {
|
||||
"type": "colorspace",
|
||||
"colorspace": "color_picking"
|
||||
}
|
||||
)
|
||||
|
||||
ffmpeg_args: Dict[str, List[Any]] = field(
|
||||
default_factory=lambda: {"input": [], "output": []}
|
||||
)
|
||||
|
||||
# Background color defined as (R, G, B, A) tuple.
|
||||
# Note: Use float for alpha channel (0.0 to 1.0).
|
||||
background_color: Tuple[float, float, float, float] = (0.0, 0.0, 0.0, 0.0)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "ProfileConfig":
|
||||
"""
|
||||
Creates a ProfileConfig instance from a dictionary, safely ignoring
|
||||
any keys in the dictionary that are not fields in the dataclass.
|
||||
|
||||
Args:
|
||||
data (Dict[str, Any]): The dictionary containing configuration data
|
||||
|
||||
Returns:
|
||||
MediaConfig: A new instance of the dataclass.
|
||||
"""
|
||||
# Get all field names defined in the dataclass
|
||||
field_names = {f.name for f in fields(cls)}
|
||||
|
||||
# Filter the input dictionary to include only keys matching field names
|
||||
filtered_data = {k: v for k, v in data.items() if k in field_names}
|
||||
|
||||
# Unpack the filtered dictionary into the constructor
|
||||
return cls(**filtered_data)
|
||||
|
||||
|
||||
class ExtractThumbnail(pyblish.api.InstancePlugin):
|
||||
"""Create jpg thumbnail from sequence using ffmpeg"""
|
||||
|
||||
|
|
@ -99,6 +159,13 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
instance.data["representations"].remove(repre)
|
||||
|
||||
def _main_process(self, instance):
|
||||
if not self.profiles:
|
||||
self.log.debug("No profiles present for color transcode")
|
||||
return
|
||||
profile_config = self._get_config_from_profile(instance)
|
||||
if not profile_config:
|
||||
return
|
||||
|
||||
product_name = instance.data["productName"]
|
||||
instance_repres = instance.data.get("representations")
|
||||
if not instance_repres:
|
||||
|
|
@ -138,7 +205,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
return True
|
||||
return False
|
||||
|
||||
product_names = self.product_names
|
||||
product_names = profile_config.product_names
|
||||
if product_names:
|
||||
result = validate_string_against_patterns(
|
||||
product_name, product_names
|
||||
|
|
@ -205,8 +272,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
# exclude first frame if slate in representation tags
|
||||
if "slate-frame" in repre.get("tags", []):
|
||||
repre_files_thumb = repre_files_thumb[1:]
|
||||
file_index = int(
|
||||
float(len(repre_files_thumb)) * self.duration_split)
|
||||
file_index = int(float(len(repre_files_thumb)) * profile_config.duration_split) # noqa: E501
|
||||
input_file = repre_files[file_index]
|
||||
|
||||
full_input_path = os.path.join(src_staging, input_file)
|
||||
|
|
@ -243,13 +309,13 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
# colorspace data
|
||||
if not repre_thumb_created:
|
||||
repre_thumb_created = self._create_thumbnail_ffmpeg(
|
||||
full_input_path, full_output_path
|
||||
full_input_path, full_output_path, profile_config
|
||||
)
|
||||
|
||||
# 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
|
||||
full_input_path, full_output_path, profile_config
|
||||
)
|
||||
|
||||
if not repre_thumb_created:
|
||||
|
|
@ -277,7 +343,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
new_repre_tags = ["thumbnail"]
|
||||
# for workflows which needs to have thumbnails published as
|
||||
# separate representations `delete` tag should not be added
|
||||
if not self.integrate_thumbnail:
|
||||
if not profile_config.integrate_thumbnail:
|
||||
new_repre_tags.append("delete")
|
||||
|
||||
new_repre = {
|
||||
|
|
@ -399,6 +465,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
src_path,
|
||||
dst_path,
|
||||
colorspace_data,
|
||||
profile_config
|
||||
):
|
||||
"""Create thumbnail using OIIO tool oiiotool
|
||||
|
||||
|
|
@ -416,7 +483,9 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
str: path to created thumbnail
|
||||
"""
|
||||
self.log.info("Extracting thumbnail {}".format(dst_path))
|
||||
resolution_arg = self._get_resolution_arg("oiiotool", src_path)
|
||||
resolution_arg = self._get_resolution_arg(
|
||||
"oiiotool", src_path, profile_config
|
||||
)
|
||||
|
||||
repre_display = colorspace_data.get("display")
|
||||
repre_view = colorspace_data.get("view")
|
||||
|
|
@ -435,12 +504,13 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
)
|
||||
# if representation doesn't have display and view then use
|
||||
# oiiotool_defaults
|
||||
elif self.oiiotool_defaults:
|
||||
oiio_default_type = self.oiiotool_defaults["type"]
|
||||
elif profile_config.oiiotool_defaults:
|
||||
oiiotool_defaults = profile_config.oiiotool_defaults
|
||||
oiio_default_type = oiiotool_defaults["type"]
|
||||
if "colorspace" == oiio_default_type:
|
||||
oiio_default_colorspace = self.oiiotool_defaults["colorspace"]
|
||||
oiio_default_colorspace = oiiotool_defaults["colorspace"]
|
||||
else:
|
||||
display_and_view = self.oiiotool_defaults["display_and_view"]
|
||||
display_and_view = oiiotool_defaults["display_and_view"]
|
||||
oiio_default_display = display_and_view["display"]
|
||||
oiio_default_view = display_and_view["view"]
|
||||
|
||||
|
|
@ -467,11 +537,13 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
|
||||
return True
|
||||
|
||||
def _create_thumbnail_oiio(self, src_path, dst_path):
|
||||
def _create_thumbnail_oiio(self, src_path, dst_path, profile_config):
|
||||
self.log.debug(f"Extracting thumbnail with OIIO: {dst_path}")
|
||||
|
||||
try:
|
||||
resolution_arg = self._get_resolution_arg("oiiotool", src_path)
|
||||
resolution_arg = self._get_resolution_arg(
|
||||
"oiiotool", src_path, profile_config
|
||||
)
|
||||
except RuntimeError:
|
||||
self.log.warning(
|
||||
"Failed to create thumbnail using oiio", exc_info=True
|
||||
|
|
@ -511,9 +583,11 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
)
|
||||
return False
|
||||
|
||||
def _create_thumbnail_ffmpeg(self, src_path, dst_path):
|
||||
def _create_thumbnail_ffmpeg(self, src_path, dst_path, profile_config):
|
||||
try:
|
||||
resolution_arg = self._get_resolution_arg("ffmpeg", src_path)
|
||||
resolution_arg = self._get_resolution_arg(
|
||||
"ffmpeg", src_path, profile_config
|
||||
)
|
||||
except RuntimeError:
|
||||
self.log.warning(
|
||||
"Failed to create thumbnail using ffmpeg", exc_info=True
|
||||
|
|
@ -521,7 +595,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
return False
|
||||
|
||||
ffmpeg_path_args = get_ffmpeg_tool_args("ffmpeg")
|
||||
ffmpeg_args = self.ffmpeg_args or {}
|
||||
ffmpeg_args = profile_config.ffmpeg_args or {}
|
||||
|
||||
jpeg_items = [
|
||||
subprocess.list2cmdline(ffmpeg_path_args)
|
||||
|
|
@ -664,12 +738,13 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
self,
|
||||
application,
|
||||
input_path,
|
||||
profile_config
|
||||
):
|
||||
# get settings
|
||||
if self.target_size["type"] == "source":
|
||||
if profile_config.target_size["type"] == "source":
|
||||
return []
|
||||
|
||||
resize = self.target_size["resize"]
|
||||
resize = profile_config.target_size["resize"]
|
||||
target_width = resize["width"]
|
||||
target_height = resize["height"]
|
||||
|
||||
|
|
@ -679,6 +754,43 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
input_path,
|
||||
target_width,
|
||||
target_height,
|
||||
bg_color=self.background_color,
|
||||
bg_color=profile_config.background_color,
|
||||
log=self.log
|
||||
)
|
||||
|
||||
def _get_config_from_profile(
|
||||
self,
|
||||
instance: pyblish.api.Instance
|
||||
) -> ProfileConfig:
|
||||
"""Returns profile if and how repre should be color transcoded."""
|
||||
host_name = instance.context.data["hostName"]
|
||||
product_type = instance.data["productType"]
|
||||
product_name = instance.data["productName"]
|
||||
task_data = instance.data["anatomyData"].get("task", {})
|
||||
task_name = task_data.get("name")
|
||||
task_type = task_data.get("type")
|
||||
filtering_criteria = {
|
||||
"hosts": host_name,
|
||||
"product_types": product_type,
|
||||
"product_names": product_name,
|
||||
"task_names": task_name,
|
||||
"task_types": task_type,
|
||||
}
|
||||
profile = filter_profiles(
|
||||
self.profiles, filtering_criteria,
|
||||
logger=self.log
|
||||
)
|
||||
|
||||
if not profile:
|
||||
self.log.debug(
|
||||
(
|
||||
"Skipped instance. None of profiles in presets are for"
|
||||
' Host: "{}" | Product types: "{}" | Product names: "{}"'
|
||||
' | Task name "{}" | Task type "{}"'
|
||||
).format(
|
||||
host_name, product_type, product_name, task_name, task_type
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
return ProfileConfig.from_dict(profile)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue