Merge pull request #1268 from ynput/bugfix/ocio-v2-aces1.3-display-resolving-error

Handles OCIO shared view token
This commit is contained in:
Jakub Ježek 2025-09-11 10:37:16 +02:00 committed by GitHub
commit 3f549a0ecf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 223 additions and 40 deletions

View file

@ -6,6 +6,8 @@ import collections
import tempfile import tempfile
import subprocess import subprocess
import platform import platform
import warnings
import functools
from typing import Optional from typing import Optional
import xml.etree.ElementTree import xml.etree.ElementTree
@ -67,6 +69,47 @@ VIDEO_EXTENSIONS = {
} }
def deprecated(new_destination):
"""Mark functions as deprecated.
It will result in a warning being emitted when the function is used.
"""
func = None
if callable(new_destination):
func = new_destination
new_destination = None
def _decorator(decorated_func):
if new_destination is None:
warning_message = (
" Please check content of deprecated function to figure out"
" possible replacement."
)
else:
warning_message = " Please replace your usage with '{}'.".format(
new_destination
)
@functools.wraps(decorated_func)
def wrapper(*args, **kwargs):
warnings.simplefilter("always", DeprecationWarning)
warnings.warn(
(
"Call to deprecated function '{}'"
"\nFunction was moved or removed.{}"
).format(decorated_func.__name__, warning_message),
category=DeprecationWarning,
stacklevel=4
)
return decorated_func(*args, **kwargs)
return wrapper
if func is None:
return _decorator
return _decorator(func)
def get_transcode_temp_directory(): def get_transcode_temp_directory():
"""Creates temporary folder for transcoding. """Creates temporary folder for transcoding.
@ -966,6 +1009,8 @@ def convert_ffprobe_fps_to_float(value):
return dividend / divisor return dividend / divisor
# --- Deprecated functions ---
@deprecated("oiio_color_convert")
def convert_colorspace( def convert_colorspace(
input_path, input_path,
output_path, output_path,
@ -977,7 +1022,62 @@ def convert_colorspace(
additional_command_args=None, additional_command_args=None,
logger=None, logger=None,
): ):
"""Convert source file from one color space to another. """DEPRECATED function use `oiio_color_convert` instead
Args:
input_path (str): Path to input file that should be converted.
output_path (str): Path to output file where result will be stored.
config_path (str): Path to OCIO config file.
source_colorspace (str): OCIO valid color space of source files.
target_colorspace (str, optional): OCIO valid target color space.
If filled, 'view' and 'display' must be empty.
view (str, optional): Name for target viewer space (OCIO valid).
Both 'view' and 'display' must be filled
(if not 'target_colorspace').
display (str, optional): Name for target display-referred
reference space. Both 'view' and 'display' must be filled
(if not 'target_colorspace').
additional_command_args (list, optional): Additional arguments
for oiiotool (like binary depth for .dpx).
logger (logging.Logger, optional): Logger used for logging.
Returns:
None: Function returns None.
Raises:
ValueError: If parameters are misconfigured.
"""
return oiio_color_convert(
input_path,
output_path,
config_path,
source_colorspace,
target_colorspace=target_colorspace,
target_display=display,
target_view=view,
additional_command_args=additional_command_args,
logger=logger,
)
def oiio_color_convert(
input_path,
output_path,
config_path,
source_colorspace,
source_display=None,
source_view=None,
target_colorspace=None,
target_display=None,
target_view=None,
additional_command_args=None,
logger=None,
):
"""Transcode source file to other with colormanagement.
Oiiotool also support additional arguments for transcoding.
For more information, see the official documentation:
https://openimageio.readthedocs.io/en/latest/oiiotool.html
Args: Args:
input_path (str): Path that should be converted. It is expected that input_path (str): Path that should be converted. It is expected that
@ -989,17 +1089,26 @@ def convert_colorspace(
sequence in 'file.FRAMESTART-FRAMEEND#.ext', `output.1-3#.tif`) sequence in 'file.FRAMESTART-FRAMEEND#.ext', `output.1-3#.tif`)
config_path (str): path to OCIO config file config_path (str): path to OCIO config file
source_colorspace (str): ocio valid color space of source files source_colorspace (str): ocio valid color space of source files
source_display (str, optional): name for source display-referred
reference space (ocio valid). If provided, source_view must also be
provided, and source_colorspace will be ignored
source_view (str, optional): name for source viewer space (ocio valid)
If provided, source_display must also be provided, and
source_colorspace will be ignored
target_colorspace (str): ocio valid target color space target_colorspace (str): ocio valid target color space
if filled, 'view' and 'display' must be empty if filled, 'view' and 'display' must be empty
view (str): name for viewer space (ocio valid) target_display (str): name for target display-referred reference space
both 'view' and 'display' must be filled (if 'target_colorspace') (ocio valid) both 'view' and 'display' must be filled (if
display (str): name for display-referred reference space (ocio valid) 'target_colorspace')
target_view (str): name for target viewer space (ocio valid)
both 'view' and 'display' must be filled (if 'target_colorspace') both 'view' and 'display' must be filled (if 'target_colorspace')
additional_command_args (list): arguments for oiiotool (like binary additional_command_args (list): arguments for oiiotool (like binary
depth for .dpx) depth for .dpx)
logger (logging.Logger): Logger used for logging. logger (logging.Logger): Logger used for logging.
Raises: Raises:
ValueError: if misconfigured ValueError: if misconfigured
""" """
if logger is None: if logger is None:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -1024,23 +1133,82 @@ def convert_colorspace(
"--ch", channels_arg "--ch", channels_arg
]) ])
if all([target_colorspace, view, display]): # Validate input parameters
raise ValueError("Colorspace and both screen and display" if target_colorspace and target_view and target_display:
" cannot be set together." raise ValueError(
"Choose colorspace or screen and display") "Colorspace and both view and display cannot be set together."
if not target_colorspace and not all([view, display]): "Choose colorspace or screen and display"
raise ValueError("Both screen and display must be set.") )
if not target_colorspace and not target_view and not target_display:
raise ValueError(
"Both view and display must be set if target_colorspace is not "
"provided."
)
if (
(source_view and not source_display)
or (source_display and not source_view)
):
raise ValueError(
"Both source_view and source_display must be provided if using "
"display/view inputs."
)
if source_view and source_display and source_colorspace:
logger.warning(
"Both source display/view and source_colorspace provided. "
"Using source display/view pair and ignoring source_colorspace."
)
if additional_command_args: if additional_command_args:
oiio_cmd.extend(additional_command_args) oiio_cmd.extend(additional_command_args)
if target_colorspace: # Handle the different conversion cases
oiio_cmd.extend(["--colorconvert:subimages=0", # Source view and display are known
source_colorspace, if source_view and source_display:
target_colorspace]) if target_colorspace:
if view and display: # This is a two-step conversion process since there's no direct
oiio_cmd.extend(["--iscolorspace", source_colorspace]) # display/view to colorspace command
oiio_cmd.extend(["--ociodisplay:subimages=0", display, view]) # This could be a config parameter or determined from OCIO config
# Use temporarty role space 'scene_linear'
color_convert_args = ("scene_linear", target_colorspace)
elif source_display != target_display or source_view != target_view:
# Complete display/view pair conversion
# - go through a reference space
color_convert_args = (target_display, target_view)
else:
color_convert_args = None
logger.debug(
"Source and target display/view pairs are identical."
" No color conversion needed."
)
if color_convert_args:
oiio_cmd.extend([
"--ociodisplay:inverse=1:subimages=0",
source_display,
source_view,
"--colorconvert:subimages=0",
*color_convert_args
])
elif target_colorspace:
# Standard color space to color space conversion
oiio_cmd.extend([
"--colorconvert:subimages=0",
source_colorspace,
target_colorspace,
])
else:
# Standard conversion from colorspace to display/view
oiio_cmd.extend([
"--iscolorspace",
source_colorspace,
"--ociodisplay:subimages=0",
target_display,
target_view,
])
oiio_cmd.extend(["-o", output_path]) oiio_cmd.extend(["-o", output_path])

View file

@ -1404,7 +1404,7 @@ def _get_display_view_colorspace_name(config_path, display, view):
""" """
config = _get_ocio_config(config_path) config = _get_ocio_config(config_path)
colorspace = config.getDisplayViewColorSpaceName(display, view) colorspace = config.getDisplayViewColorSpaceName(display, view)
# Special token. See https://opencolorio.readthedocs.io/en/latest/guides/authoring/authoring.html#shared-views # noqa # Special token. See https://opencolorio.readthedocs.io/en/latest/guides/authoring/authoring.html#shared-views # noqa
if colorspace == "<USE_DISPLAY_NAME>": if colorspace == "<USE_DISPLAY_NAME>":
colorspace = display colorspace = display

View file

@ -11,7 +11,7 @@ from ayon_core.lib import (
is_oiio_supported, is_oiio_supported,
) )
from ayon_core.lib.transcoding import ( from ayon_core.lib.transcoding import (
convert_colorspace, oiio_color_convert,
) )
from ayon_core.lib.profiles_filtering import filter_profiles from ayon_core.lib.profiles_filtering import filter_profiles
@ -87,6 +87,14 @@ class ExtractOIIOTranscode(publish.Extractor):
new_representations = [] new_representations = []
repres = instance.data["representations"] repres = instance.data["representations"]
for idx, repre in enumerate(list(repres)): for idx, repre in enumerate(list(repres)):
# target space, display and view might be defined upstream
# TODO: address https://github.com/ynput/ayon-core/pull/1268#discussion_r2156555474
# Implement upstream logic to handle target_colorspace,
# target_display, target_view in other DCCs
target_colorspace = False
target_display = instance.data.get("colorspaceDisplay")
target_view = instance.data.get("colorspaceView")
self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"]))
if not self._repre_is_valid(repre): if not self._repre_is_valid(repre):
continue continue
@ -96,6 +104,8 @@ class ExtractOIIOTranscode(publish.Extractor):
colorspace_data = repre["colorspaceData"] colorspace_data = repre["colorspaceData"]
source_colorspace = colorspace_data["colorspace"] source_colorspace = colorspace_data["colorspace"]
source_display = colorspace_data.get("display")
source_view = colorspace_data.get("view")
config_path = colorspace_data.get("config", {}).get("path") config_path = colorspace_data.get("config", {}).get("path")
if not config_path or not os.path.exists(config_path): if not config_path or not os.path.exists(config_path):
self.log.warning("Config file doesn't exist, skipping") self.log.warning("Config file doesn't exist, skipping")
@ -126,7 +136,6 @@ class ExtractOIIOTranscode(publish.Extractor):
transcoding_type = output_def["transcoding_type"] transcoding_type = output_def["transcoding_type"]
target_colorspace = view = display = None
# NOTE: we use colorspace_data as the fallback values for # NOTE: we use colorspace_data as the fallback values for
# the target colorspace. # the target colorspace.
if transcoding_type == "colorspace": if transcoding_type == "colorspace":
@ -138,18 +147,20 @@ class ExtractOIIOTranscode(publish.Extractor):
colorspace_data.get("colorspace")) colorspace_data.get("colorspace"))
elif transcoding_type == "display_view": elif transcoding_type == "display_view":
display_view = output_def["display_view"] display_view = output_def["display_view"]
view = display_view["view"] or colorspace_data.get("view") target_view = (
display = ( display_view["view"]
or colorspace_data.get("view"))
target_display = (
display_view["display"] display_view["display"]
or colorspace_data.get("display") or colorspace_data.get("display")
) )
# both could be already collected by DCC, # both could be already collected by DCC,
# but could be overwritten when transcoding # but could be overwritten when transcoding
if view: if target_view:
new_repre["colorspaceData"]["view"] = view new_repre["colorspaceData"]["view"] = target_view
if display: if target_display:
new_repre["colorspaceData"]["display"] = display new_repre["colorspaceData"]["display"] = target_display
if target_colorspace: if target_colorspace:
new_repre["colorspaceData"]["colorspace"] = \ new_repre["colorspaceData"]["colorspace"] = \
target_colorspace target_colorspace
@ -168,16 +179,18 @@ class ExtractOIIOTranscode(publish.Extractor):
new_staging_dir, new_staging_dir,
output_extension) output_extension)
convert_colorspace( oiio_color_convert(
input_path, input_path=input_path,
output_path, output_path=output_path,
config_path, config_path=config_path,
source_colorspace, source_colorspace=source_colorspace,
target_colorspace, target_colorspace=target_colorspace,
view, target_display=target_display,
display, target_view=target_view,
additional_command_args, source_display=source_display,
self.log source_view=source_view,
additional_command_args=additional_command_args,
logger=self.log
) )
# cleanup temporary transcoded files # cleanup temporary transcoded files

View file

@ -15,7 +15,7 @@ from ayon_core.lib import (
path_to_subprocess_arg, path_to_subprocess_arg,
run_subprocess, run_subprocess,
) )
from ayon_core.lib.transcoding import convert_colorspace from ayon_core.lib.transcoding import oiio_color_convert
from ayon_core.lib.transcoding import VIDEO_EXTENSIONS, IMAGE_EXTENSIONS from ayon_core.lib.transcoding import VIDEO_EXTENSIONS, IMAGE_EXTENSIONS
@ -433,13 +433,15 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
oiio_default_view = display_and_view["view"] oiio_default_view = display_and_view["view"]
try: try:
convert_colorspace( oiio_color_convert(
src_path, src_path,
dst_path, dst_path,
colorspace_data["config"]["path"], colorspace_data["config"]["path"],
colorspace_data["colorspace"], colorspace_data["colorspace"],
display=repre_display or oiio_default_display, source_display=colorspace_data.get("display"),
view=repre_view or oiio_default_view, source_view=colorspace_data.get("view"),
target_display=repre_display or oiio_default_display,
target_view=repre_view or oiio_default_view,
target_colorspace=oiio_default_colorspace, target_colorspace=oiio_default_colorspace,
additional_command_args=resolution_arg, additional_command_args=resolution_arg,
logger=self.log, logger=self.log,