Skip to content

Commit

Permalink
Merge pull request #5938 from ynput/enhancement/OP-6659_thumbnail-col…
Browse files Browse the repository at this point in the history
…or-managed

General: Use colorspace data when creating thumbnail
  • Loading branch information
jakubjezek001 committed Nov 29, 2023
2 parents c9701ac + 7146b7b commit ced3e1e
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 73 deletions.
122 changes: 66 additions & 56 deletions openpype/lib/transcoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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:
Expand Down Expand Up @@ -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,
Expand All @@ -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.
Expand All @@ -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"
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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."
Expand Down
95 changes: 78 additions & 17 deletions openpype/plugins/publish/extract_thumbnail.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
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):
Expand All @@ -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):
Expand Down Expand Up @@ -94,17 +95,26 @@ def process(self, instance):
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(
Expand Down Expand Up @@ -138,7 +148,7 @@ def process(self, instance):
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
Expand Down Expand Up @@ -173,24 +183,75 @@ def _get_filtered_repres(self, instance):
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",
exc_info=True
)
return False

return True

def create_thumbnail_ffmpeg(self, src_path, dst_path):
self.log.debug("Extracting thumbnail with FFMPEG: {}".format(dst_path))

Expand Down
6 changes: 6 additions & 0 deletions openpype/settings/defaults/project_settings/global.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@
},
"ExtractThumbnail": {
"enabled": true,
"oiiotool_defaults": {
"type": "colorspace",
"colorspace": "color_picking",
"view": "sRGB",
"display": "default"
},
"ffmpeg_args": {
"input": [
"-apply_trc gamma22"
Expand Down
Loading

0 comments on commit ced3e1e

Please sign in to comment.