Merge pull request #5938 from ynput/enhancement/OP-6659_thumbnail-color-managed

General: Use colorspace data when creating thumbnail
This commit is contained in:
Jakub Ježek 2023-11-29 21:25:58 +01:00 committed by GitHub
commit ced3e1ecc2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 182 additions and 73 deletions

View file

@ -536,7 +536,7 @@ def convert_for_ffmpeg(
input_frame_end=None,
logger=None
):
"""Contert source file to format supported in ffmpeg.
"""Convert source file to format supported in ffmpeg.
Currently can convert only exrs.
@ -592,29 +592,7 @@ def convert_for_ffmpeg(
oiio_cmd.extend(["--compression", compression])
# Collect channels to export
channel_names = input_info["channelnames"]
review_channels = get_convert_rgb_channels(channel_names)
if review_channels is None:
raise ValueError(
"Couldn't find channels that can be used for conversion."
)
red, green, blue, alpha = review_channels
input_channels = [red, green, blue]
channels_arg = "R={},G={},B={}".format(red, green, blue)
if alpha is not None:
channels_arg += ",A={}".format(alpha)
input_channels.append(alpha)
input_channels_str = ",".join(input_channels)
subimages = input_info.get("subimages")
input_arg = "-i"
if subimages is None or subimages == 1:
# Tell oiiotool which channels should be loaded
# - other channels are not loaded to memory so helps to avoid memory
# leak issues
# - this option is crashing if used on multipart/subimages exrs
input_arg += ":ch={}".format(input_channels_str)
input_arg, channels_arg = get_oiio_input_and_channel_args(input_info)
oiio_cmd.extend([
input_arg, first_input_path,
@ -635,7 +613,7 @@ def convert_for_ffmpeg(
continue
# Remove attributes that have string value longer than allowed length
# for ffmpeg or when contain unallowed symbols
# for ffmpeg or when contain prohibited symbols
erase_reason = "Missing reason"
erase_attribute = False
if len(attr_value) > MAX_FFMPEG_STRING_LEN:
@ -677,6 +655,47 @@ def convert_for_ffmpeg(
run_subprocess(oiio_cmd, logger=logger)
def get_oiio_input_and_channel_args(oiio_input_info):
"""Get input and channel arguments for oiiotool.
Args:
oiio_input_info (dict): Information about input from oiio tool.
Should be output of function `get_oiio_info_for_input`.
Returns:
tuple[str, str]: Tuple of input and channel arguments.
"""
channel_names = oiio_input_info["channelnames"]
review_channels = get_convert_rgb_channels(channel_names)
if review_channels is None:
raise ValueError(
"Couldn't find channels that can be used for conversion."
)
red, green, blue, alpha = review_channels
input_channels = [red, green, blue]
# TODO find subimage where rgba is available for multipart exrs
channels_arg = "R={},G={},B={}".format(red, green, blue)
if alpha is not None:
channels_arg += ",A={}".format(alpha)
input_channels.append(alpha)
input_channels_str = ",".join(input_channels)
subimages = oiio_input_info.get("subimages")
input_arg = "-i"
if subimages is None or subimages == 1:
# Tell oiiotool which channels should be loaded
# - other channels are not loaded to memory so helps to avoid memory
# leak issues
# - this option is crashing if used on multipart exrs
input_arg += ":ch={}".format(input_channels_str)
return input_arg, channels_arg
def convert_input_paths_for_ffmpeg(
input_paths,
output_dir,
@ -695,7 +714,7 @@ def convert_input_paths_for_ffmpeg(
Args:
input_paths (str): Paths that should be converted. It is expected that
contains single file or image sequence of samy type.
contains single file or image sequence of same type.
output_dir (str): Path to directory where output will be rendered.
Must not be same as input's directory.
logger (logging.Logger): Logger used for logging.
@ -709,6 +728,7 @@ def convert_input_paths_for_ffmpeg(
first_input_path = input_paths[0]
ext = os.path.splitext(first_input_path)[1].lower()
if ext != ".exr":
raise ValueError((
"Function 'convert_for_ffmpeg' currently support only"
@ -724,30 +744,7 @@ def convert_input_paths_for_ffmpeg(
compression = "none"
# Collect channels to export
channel_names = input_info["channelnames"]
review_channels = get_convert_rgb_channels(channel_names)
if review_channels is None:
raise ValueError(
"Couldn't find channels that can be used for conversion."
)
red, green, blue, alpha = review_channels
input_channels = [red, green, blue]
# TODO find subimage inder where rgba is available for multipart exrs
channels_arg = "R={},G={},B={}".format(red, green, blue)
if alpha is not None:
channels_arg += ",A={}".format(alpha)
input_channels.append(alpha)
input_channels_str = ",".join(input_channels)
subimages = input_info.get("subimages")
input_arg = "-i"
if subimages is None or subimages == 1:
# Tell oiiotool which channels should be loaded
# - other channels are not loaded to memory so helps to avoid memory
# leak issues
# - this option is crashing if used on multipart exrs
input_arg += ":ch={}".format(input_channels_str)
input_arg, channels_arg = get_oiio_input_and_channel_args(input_info)
for input_path in input_paths:
# Prepare subprocess arguments
@ -774,7 +771,7 @@ def convert_input_paths_for_ffmpeg(
continue
# Remove attributes that have string value longer than allowed
# length for ffmpeg or when containing unallowed symbols
# length for ffmpeg or when containing prohibited symbols
erase_reason = "Missing reason"
erase_attribute = False
if len(attr_value) > MAX_FFMPEG_STRING_LEN:
@ -1021,9 +1018,7 @@ def _ffmpeg_h264_codec_args(stream_data, source_ffmpeg_cmd):
if pix_fmt:
output.extend(["-pix_fmt", pix_fmt])
output.extend(["-intra"])
output.extend(["-g", "1"])
output.extend(["-intra", "-g", "1"])
return output
@ -1150,7 +1145,7 @@ def convert_colorspace(
view=None,
display=None,
additional_command_args=None,
logger=None
logger=None,
):
"""Convert source file from one color space to another.
@ -1169,6 +1164,7 @@ def convert_colorspace(
view (str): name for viewer space (ocio valid)
both 'view' and 'display' must be filled (if 'target_colorspace')
display (str): name for display-referred reference space (ocio valid)
both 'view' and 'display' must be filled (if 'target_colorspace')
additional_command_args (list): arguments for oiiotool (like binary
depth for .dpx)
logger (logging.Logger): Logger used for logging.
@ -1178,14 +1174,28 @@ def convert_colorspace(
if logger is None:
logger = logging.getLogger(__name__)
input_info = get_oiio_info_for_input(input_path, logger=logger)
# Collect channels to export
input_arg, channels_arg = get_oiio_input_and_channel_args(input_info)
# Prepare subprocess arguments
oiio_cmd = get_oiio_tool_args(
"oiiotool",
input_path,
# Don't add any additional attributes
"--nosoftwareattrib",
"--colorconfig", config_path
)
oiio_cmd.extend([
input_arg, input_path,
# Tell oiiotool which channels should be put to top stack
# (and output)
"--ch", channels_arg,
# Use first subimage
"--subimage", "0"
])
if all([target_colorspace, view, display]):
raise ValueError("Colorspace and both screen and display"
" cannot be set together."

View file

@ -5,12 +5,12 @@ import tempfile
import pyblish.api
from openpype.lib import (
get_ffmpeg_tool_args,
get_oiio_tool_args,
is_oiio_supported,
run_subprocess,
path_to_subprocess_arg,
)
from openpype.lib.transcoding import convert_colorspace
class ExtractThumbnail(pyblish.api.InstancePlugin):
@ -25,7 +25,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
hosts = ["shell", "fusion", "resolve", "traypublisher", "substancepainter"]
enabled = False
# presetable attribute
# attribute presets from settings
oiiotool_defaults = None
ffmpeg_args = None
def process(self, instance):
@ -94,17 +95,26 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
filename = os.path.splitext(input_file)[0]
jpeg_file = filename + "_thumb.jpg"
full_output_path = os.path.join(dst_staging, jpeg_file)
colorspace_data = repre.get("colorspaceData")
if oiio_supported:
self.log.debug("Trying to convert with OIIO")
# only use OIIO if it is supported and representation has
# colorspace data
if oiio_supported and colorspace_data:
self.log.debug(
"Trying to convert with OIIO "
"with colorspace data: {}".format(colorspace_data)
)
# If the input can read by OIIO then use OIIO method for
# conversion otherwise use ffmpeg
thumbnail_created = self.create_thumbnail_oiio(
full_input_path, full_output_path
full_input_path,
full_output_path,
colorspace_data
)
# Try to use FFMPEG if OIIO is not supported or for cases when
# oiiotool isn't available
# oiiotool isn't available or representation is not having
# colorspace data
if not thumbnail_created:
if oiio_supported:
self.log.debug(
@ -138,7 +148,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
break
if not thumbnail_created:
self.log.warning("Thumbanil has not been created.")
self.log.warning("Thumbnail has not been created.")
def _is_review_instance(self, instance):
# TODO: We should probably handle "not creating" of thumbnail
@ -173,17 +183,66 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
filtered_repres.append(repre)
return filtered_repres
def create_thumbnail_oiio(self, src_path, dst_path):
self.log.debug("Extracting thumbnail with OIIO: {}".format(dst_path))
oiio_cmd = get_oiio_tool_args(
"oiiotool",
"-a", src_path,
"-o", dst_path
)
self.log.debug("running: {}".format(" ".join(oiio_cmd)))
def create_thumbnail_oiio(
self,
src_path,
dst_path,
colorspace_data,
):
"""Create thumbnail using OIIO tool oiiotool
Args:
src_path (str): path to source file
dst_path (str): path to destination file
colorspace_data (dict): colorspace data from representation
keys:
colorspace (str)
config (dict)
display (Optional[str])
view (Optional[str])
Returns:
str: path to created thumbnail
"""
self.log.info("Extracting thumbnail {}".format(dst_path))
repre_display = colorspace_data.get("display")
repre_view = colorspace_data.get("view")
oiio_default_type = None
oiio_default_display = None
oiio_default_view = None
oiio_default_colorspace = None
# first look into representation colorspaceData, perhaps it has
# display and view
if all([repre_display, repre_view]):
self.log.info(
"Using Display & View from "
"representation: '{} ({})'".format(
repre_view,
repre_display
)
)
# if representation doesn't have display and view then use
# oiiotool_defaults
elif self.oiiotool_defaults:
oiio_default_type = self.oiiotool_defaults["type"]
if "colorspace" in oiio_default_type:
oiio_default_colorspace = self.oiiotool_defaults["colorspace"]
else:
oiio_default_display = self.oiiotool_defaults["display"]
oiio_default_view = self.oiiotool_defaults["view"]
try:
run_subprocess(oiio_cmd, logger=self.log)
return True
convert_colorspace(
src_path,
dst_path,
colorspace_data["config"]["path"],
colorspace_data["colorspace"],
display=repre_display or oiio_default_display,
view=repre_view or oiio_default_view,
target_colorspace=oiio_default_colorspace,
logger=self.log,
)
except Exception:
self.log.warning(
"Failed to create thumbnail using oiiotool",
@ -191,6 +250,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
)
return False
return True
def create_thumbnail_ffmpeg(self, src_path, dst_path):
self.log.debug("Extracting thumbnail with FFMPEG: {}".format(dst_path))

View file

@ -70,6 +70,12 @@
},
"ExtractThumbnail": {
"enabled": true,
"oiiotool_defaults": {
"type": "colorspace",
"colorspace": "color_picking",
"view": "sRGB",
"display": "default"
},
"ffmpeg_args": {
"input": [
"-apply_trc gamma22"

View file

@ -202,6 +202,38 @@
"key": "enabled",
"label": "Enabled"
},
{
"type": "dict",
"collapsible": true,
"key": "oiiotool_defaults",
"label": "OIIOtool defaults",
"children": [
{
"type": "enum",
"key": "type",
"label": "Target type",
"enum_items": [
{ "colorspace": "Colorspace" },
{ "display_and_view": "Display & View" }
]
},
{
"type": "text",
"key": "colorspace",
"label": "Colorspace"
},
{
"type": "text",
"key": "view",
"label": "View"
},
{
"type": "text",
"key": "display",
"label": "Display"
}
]
},
{
"type": "dict",
"key": "ffmpeg_args",