mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
Extract Review: Multilayer specification for ffmpeg (#5613)
* added function to extract more information about channels * specify layer name which should be used for ffmpeg * changed 'get_channels_info_by_layer_name' to 'get_review_info_by_layer_name' * modify docstring * fix dosctring again
This commit is contained in:
parent
6e574aff4d
commit
93fb76f359
2 changed files with 160 additions and 40 deletions
|
|
@ -315,6 +315,92 @@ def parse_oiio_xml_output(xml_string, logger=None):
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def get_review_info_by_layer_name(channel_names):
|
||||||
|
"""Get channels info grouped by layer name.
|
||||||
|
|
||||||
|
Finds all layers in channel names and returns list of dictionaries with
|
||||||
|
information about channels in layer.
|
||||||
|
Example output (not real world example):
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Main",
|
||||||
|
"review_channels": {
|
||||||
|
"R": "Main.red",
|
||||||
|
"G": "Main.green",
|
||||||
|
"B": "Main.blue",
|
||||||
|
"A": None,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Composed",
|
||||||
|
"review_channels": {
|
||||||
|
"R": "Composed.R",
|
||||||
|
"G": "Composed.G",
|
||||||
|
"B": "Composed.B",
|
||||||
|
"A": "Composed.A",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel_names (list[str]): List of channel names.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[dict]: List of channels information.
|
||||||
|
"""
|
||||||
|
|
||||||
|
layer_names_order = []
|
||||||
|
rgba_by_layer_name = collections.defaultdict(dict)
|
||||||
|
channels_by_layer_name = collections.defaultdict(dict)
|
||||||
|
|
||||||
|
for channel_name in channel_names:
|
||||||
|
layer_name = ""
|
||||||
|
last_part = channel_name
|
||||||
|
if "." in channel_name:
|
||||||
|
layer_name, last_part = channel_name.rsplit(".", 1)
|
||||||
|
|
||||||
|
channels_by_layer_name[layer_name][channel_name] = last_part
|
||||||
|
if last_part.lower() not in {
|
||||||
|
"r", "red",
|
||||||
|
"g", "green",
|
||||||
|
"b", "blue",
|
||||||
|
"a", "alpha"
|
||||||
|
}:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if layer_name not in layer_names_order:
|
||||||
|
layer_names_order.append(layer_name)
|
||||||
|
# R, G, B or A
|
||||||
|
channel = last_part[0].upper()
|
||||||
|
rgba_by_layer_name[layer_name][channel] = channel_name
|
||||||
|
|
||||||
|
# Put empty layer to the beginning of the list
|
||||||
|
# - if input has R, G, B, A channels they should be used for review
|
||||||
|
if "" in layer_names_order:
|
||||||
|
layer_names_order.remove("")
|
||||||
|
layer_names_order.insert(0, "")
|
||||||
|
|
||||||
|
output = []
|
||||||
|
for layer_name in layer_names_order:
|
||||||
|
rgba_layer_info = rgba_by_layer_name[layer_name]
|
||||||
|
red = rgba_layer_info.get("R")
|
||||||
|
green = rgba_layer_info.get("G")
|
||||||
|
blue = rgba_layer_info.get("B")
|
||||||
|
if not red or not green or not blue:
|
||||||
|
continue
|
||||||
|
output.append({
|
||||||
|
"name": layer_name,
|
||||||
|
"review_channels": {
|
||||||
|
"R": red,
|
||||||
|
"G": green,
|
||||||
|
"B": blue,
|
||||||
|
"A": rgba_layer_info.get("A"),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
def get_convert_rgb_channels(channel_names):
|
def get_convert_rgb_channels(channel_names):
|
||||||
"""Get first available RGB(A) group from channels info.
|
"""Get first available RGB(A) group from channels info.
|
||||||
|
|
||||||
|
|
@ -323,7 +409,7 @@ def get_convert_rgb_channels(channel_names):
|
||||||
# Ideal situation
|
# Ideal situation
|
||||||
channels_info: [
|
channels_info: [
|
||||||
"R", "G", "B", "A"
|
"R", "G", "B", "A"
|
||||||
}
|
]
|
||||||
```
|
```
|
||||||
Result will be `("R", "G", "B", "A")`
|
Result will be `("R", "G", "B", "A")`
|
||||||
|
|
||||||
|
|
@ -331,50 +417,60 @@ def get_convert_rgb_channels(channel_names):
|
||||||
# Not ideal situation
|
# Not ideal situation
|
||||||
channels_info: [
|
channels_info: [
|
||||||
"beauty.red",
|
"beauty.red",
|
||||||
"beuaty.green",
|
"beauty.green",
|
||||||
"beauty.blue",
|
"beauty.blue",
|
||||||
"depth.Z"
|
"depth.Z"
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
Result will be `("beauty.red", "beauty.green", "beauty.blue", None)`
|
Result will be `("beauty.red", "beauty.green", "beauty.blue", None)`
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel_names (list[str]): List of channel names.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
NoneType: There is not channel combination that matches RGB
|
Union[NoneType, tuple[str, str, str, Union[str, None]]]: Tuple of
|
||||||
combination.
|
4 channel names defying channel names for R, G, B, A or None
|
||||||
tuple: Tuple of 4 channel names defying channel names for R, G, B, A
|
if there is not any layer with RGB combination.
|
||||||
where A can be None.
|
|
||||||
"""
|
"""
|
||||||
rgb_by_main_name = collections.defaultdict(dict)
|
|
||||||
main_name_order = [""]
|
|
||||||
for channel_name in channel_names:
|
|
||||||
name_parts = channel_name.split(".")
|
|
||||||
rgb_part = name_parts.pop(-1).lower()
|
|
||||||
main_name = ".".join(name_parts)
|
|
||||||
if rgb_part in ("r", "red"):
|
|
||||||
rgb_by_main_name[main_name]["R"] = channel_name
|
|
||||||
elif rgb_part in ("g", "green"):
|
|
||||||
rgb_by_main_name[main_name]["G"] = channel_name
|
|
||||||
elif rgb_part in ("b", "blue"):
|
|
||||||
rgb_by_main_name[main_name]["B"] = channel_name
|
|
||||||
elif rgb_part in ("a", "alpha"):
|
|
||||||
rgb_by_main_name[main_name]["A"] = channel_name
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
if main_name not in main_name_order:
|
|
||||||
main_name_order.append(main_name)
|
|
||||||
|
|
||||||
output = None
|
channels_info = get_review_info_by_layer_name(channel_names)
|
||||||
for main_name in main_name_order:
|
for item in channels_info:
|
||||||
colors = rgb_by_main_name.get(main_name) or {}
|
review_channels = item["review_channels"]
|
||||||
red = colors.get("R")
|
return (
|
||||||
green = colors.get("G")
|
review_channels["R"],
|
||||||
blue = colors.get("B")
|
review_channels["G"],
|
||||||
alpha = colors.get("A")
|
review_channels["B"],
|
||||||
if red is not None and green is not None and blue is not None:
|
review_channels["A"]
|
||||||
output = (red, green, blue, alpha)
|
)
|
||||||
break
|
return None
|
||||||
|
|
||||||
return output
|
|
||||||
|
def get_review_layer_name(src_filepath):
|
||||||
|
"""Find layer name that could be used for review.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
src_filepath (str): Path to input file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Union[str, None]: Layer name of None.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ext = os.path.splitext(src_filepath)[-1].lower()
|
||||||
|
if ext != ".exr":
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Load info about file from oiio tool
|
||||||
|
input_info = get_oiio_info_for_input(src_filepath)
|
||||||
|
if not input_info:
|
||||||
|
return None
|
||||||
|
|
||||||
|
channel_names = input_info["channelnames"]
|
||||||
|
channels_info = get_review_info_by_layer_name(channel_names)
|
||||||
|
for item in channels_info:
|
||||||
|
# Layer name can be '', when review channels are 'R', 'G', 'B'
|
||||||
|
# without layer
|
||||||
|
return item["name"] or None
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def should_convert_for_ffmpeg(src_filepath):
|
def should_convert_for_ffmpeg(src_filepath):
|
||||||
|
|
@ -395,7 +491,7 @@ def should_convert_for_ffmpeg(src_filepath):
|
||||||
if not is_oiio_supported():
|
if not is_oiio_supported():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Load info about info from oiio tool
|
# Load info about file from oiio tool
|
||||||
input_info = get_oiio_info_for_input(src_filepath)
|
input_info = get_oiio_info_for_input(src_filepath)
|
||||||
if not input_info:
|
if not input_info:
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ from openpype.lib.transcoding import (
|
||||||
IMAGE_EXTENSIONS,
|
IMAGE_EXTENSIONS,
|
||||||
get_ffprobe_streams,
|
get_ffprobe_streams,
|
||||||
should_convert_for_ffmpeg,
|
should_convert_for_ffmpeg,
|
||||||
|
get_review_layer_name,
|
||||||
convert_input_paths_for_ffmpeg,
|
convert_input_paths_for_ffmpeg,
|
||||||
get_transcode_temp_directory,
|
get_transcode_temp_directory,
|
||||||
)
|
)
|
||||||
|
|
@ -266,6 +267,8 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
||||||
))
|
))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
layer_name = get_review_layer_name(first_input_path)
|
||||||
|
|
||||||
# Do conversion if needed
|
# Do conversion if needed
|
||||||
# - change staging dir of source representation
|
# - change staging dir of source representation
|
||||||
# - must be set back after output definitions processing
|
# - must be set back after output definitions processing
|
||||||
|
|
@ -284,7 +287,8 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
||||||
instance,
|
instance,
|
||||||
repre,
|
repre,
|
||||||
src_repre_staging_dir,
|
src_repre_staging_dir,
|
||||||
filtered_output_defs
|
filtered_output_defs,
|
||||||
|
layer_name
|
||||||
)
|
)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
|
|
@ -298,7 +302,12 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
||||||
shutil.rmtree(new_staging_dir)
|
shutil.rmtree(new_staging_dir)
|
||||||
|
|
||||||
def _render_output_definitions(
|
def _render_output_definitions(
|
||||||
self, instance, repre, src_repre_staging_dir, output_definitions
|
self,
|
||||||
|
instance,
|
||||||
|
repre,
|
||||||
|
src_repre_staging_dir,
|
||||||
|
output_definitions,
|
||||||
|
layer_name
|
||||||
):
|
):
|
||||||
fill_data = copy.deepcopy(instance.data["anatomyData"])
|
fill_data = copy.deepcopy(instance.data["anatomyData"])
|
||||||
for _output_def in output_definitions:
|
for _output_def in output_definitions:
|
||||||
|
|
@ -370,7 +379,12 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
||||||
|
|
||||||
try: # temporary until oiiotool is supported cross platform
|
try: # temporary until oiiotool is supported cross platform
|
||||||
ffmpeg_args = self._ffmpeg_arguments(
|
ffmpeg_args = self._ffmpeg_arguments(
|
||||||
output_def, instance, new_repre, temp_data, fill_data
|
output_def,
|
||||||
|
instance,
|
||||||
|
new_repre,
|
||||||
|
temp_data,
|
||||||
|
fill_data,
|
||||||
|
layer_name,
|
||||||
)
|
)
|
||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
# TODO recalculate width and height using OIIO before
|
# TODO recalculate width and height using OIIO before
|
||||||
|
|
@ -531,7 +545,13 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
||||||
}
|
}
|
||||||
|
|
||||||
def _ffmpeg_arguments(
|
def _ffmpeg_arguments(
|
||||||
self, output_def, instance, new_repre, temp_data, fill_data
|
self,
|
||||||
|
output_def,
|
||||||
|
instance,
|
||||||
|
new_repre,
|
||||||
|
temp_data,
|
||||||
|
fill_data,
|
||||||
|
layer_name
|
||||||
):
|
):
|
||||||
"""Prepares ffmpeg arguments for expected extraction.
|
"""Prepares ffmpeg arguments for expected extraction.
|
||||||
|
|
||||||
|
|
@ -599,6 +619,10 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
||||||
|
|
||||||
duration_seconds = float(output_frames_len / temp_data["fps"])
|
duration_seconds = float(output_frames_len / temp_data["fps"])
|
||||||
|
|
||||||
|
# Define which layer should be used
|
||||||
|
if layer_name:
|
||||||
|
ffmpeg_input_args.extend(["-layer", layer_name])
|
||||||
|
|
||||||
if temp_data["input_is_sequence"]:
|
if temp_data["input_is_sequence"]:
|
||||||
# Set start frame of input sequence (just frame in filename)
|
# Set start frame of input sequence (just frame in filename)
|
||||||
# - definition of input filepath
|
# - definition of input filepath
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue