Introduced dataclass for config of selected profile

It makes it nicer than using dictionary
This commit is contained in:
Petr Kalis 2025-12-04 13:08:11 +01:00
parent 08c03e980b
commit ad83f76318

View file

@ -13,7 +13,9 @@ Todos:
"""
import os
from dataclasses import dataclass, field, fields
import tempfile
from typing import Dict, Any, List, Tuple
import pyblish.api
from ayon_core.lib import (
@ -22,9 +24,57 @@ from ayon_core.lib import (
is_oiio_supported,
run_subprocess,
get_rescaled_command_arguments,
filter_profiles,
)
@dataclass
class ProfileConfig:
"""
Data class representing the full configuration for selected profile
Any change of controllable fields in Settings must propagate here!
"""
integrate_thumbnail: bool = False
target_size: Dict[str, Any] = field(
default_factory=lambda: {
"type": "source",
"resize": {"width": 1920, "height": 1080},
}
)
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 that match 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 ExtractThumbnailFromSource(pyblish.api.InstancePlugin):
"""Create jpg thumbnail for instance based on 'thumbnailSource'.
@ -56,7 +106,7 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin):
return
dst_filepath = self._create_thumbnail(
instance.context, thumbnail_source
instance.context, thumbnail_source, profile_config
)
if not dst_filepath:
return
@ -79,7 +129,12 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin):
instance.data["representations"].append(new_repre)
instance.data["thumbnailPath"] = dst_filepath
def _create_thumbnail(self, context, thumbnail_source):
def _create_thumbnail(
self,
context: pyblish.api.Context,
thumbnail_source: str,
profile_config: ProfileConfig
) -> str:
if not thumbnail_source:
self.log.debug("Thumbnail source not filled. Skipping.")
return
@ -112,7 +167,7 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin):
# If the input can read by OIIO then use OIIO method for
# conversion otherwise use ffmpeg
thumbnail_created = self.create_thumbnail_oiio(
thumbnail_source, full_output_path
thumbnail_source, full_output_path, profile_config
)
# Try to use FFMPEG if OIIO is not supported or for cases when
@ -125,7 +180,7 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin):
)
thumbnail_created = self.create_thumbnail_ffmpeg(
thumbnail_source, full_output_path
thumbnail_source, full_output_path, profile_config
)
# Skip representation and try next one if wasn't created
@ -146,13 +201,15 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin):
return True
return False
def create_thumbnail_oiio(self, src_path, dst_path):
def create_thumbnail_oiio(
self,
src_path: str,
dst_path: str,
profile_config: ProfileConfig
) -> bool:
self.log.debug("Outputting thumbnail with OIIO: {}".format(dst_path))
oiio_cmd = get_oiio_tool_args(
"oiiotool",
"-a", src_path,
"--ch", "R,G,B",
"-o", dst_path
resolution_arg = self._get_resolution_arg(
"oiiotool", src_path, profile_config
)
self.log.debug("Running: {}".format(" ".join(oiio_cmd)))
try:
@ -165,7 +222,16 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin):
)
return False
def create_thumbnail_ffmpeg(self, src_path, dst_path):
def create_thumbnail_ffmpeg(
self,
src_path: str,
dst_path: str,
profile_config: ProfileConfig
) -> bool:
resolution_arg = self._get_resolution_arg(
"ffmpeg", src_path, profile_config
)
max_int = str(2147483647)
ffmpeg_cmd = get_ffmpeg_tool_args(
"ffmpeg",
@ -188,10 +254,78 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin):
)
return False
def _create_context_thumbnail(self, context):
if "thumbnailPath" in context.data:
def _create_context_thumbnail(
self,
context: pyblish.api.Context,
profile: ProfileConfig
) -> str:
hasContextThumbnail = "thumbnailPath" in context.data
if hasContextThumbnail:
return
thumbnail_source = context.data.get("thumbnailSource")
thumbnail_path = self._create_thumbnail(context, thumbnail_source)
context.data["thumbnailPath"] = thumbnail_path
thumbnail_path = self._create_thumbnail(
context, thumbnail_source, profile
)
return thumbnail_path
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)
def _get_resolution_arg(
self,
application,
input_path,
profile
):
# get settings
if profile.target_size["type"] == "source":
return []
resize = profile.target_size["resize"]
target_width = resize["width"]
target_height = resize["height"]
# form arg string per application
return get_rescaled_command_arguments(
application,
input_path,
target_width,
target_height,
bg_color=profile.background_color,
log=self.log,
)