mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
Merge pull request #3050 from pypeclub/bugfix/OP-988_Failed-render-publishes
General: Extract Review handle invalid characters for ffmpeg
This commit is contained in:
commit
bb294767da
2 changed files with 163 additions and 125 deletions
|
|
@ -17,6 +17,9 @@ from .vendor_bin_utils import (
|
|||
|
||||
# Max length of string that is supported by ffmpeg
|
||||
MAX_FFMPEG_STRING_LEN = 8196
|
||||
# Not allowed symbols in attributes for ffmpeg
|
||||
NOT_ALLOWED_FFMPEG_CHARS = ("\"", )
|
||||
|
||||
# OIIO known xml tags
|
||||
STRING_TAGS = {
|
||||
"format"
|
||||
|
|
@ -367,11 +370,15 @@ def should_convert_for_ffmpeg(src_filepath):
|
|||
return None
|
||||
|
||||
for attr_value in input_info["attribs"].values():
|
||||
if (
|
||||
isinstance(attr_value, str)
|
||||
and len(attr_value) > MAX_FFMPEG_STRING_LEN
|
||||
):
|
||||
if not isinstance(attr_value, str):
|
||||
continue
|
||||
|
||||
if len(attr_value) > MAX_FFMPEG_STRING_LEN:
|
||||
return True
|
||||
|
||||
for char in NOT_ALLOWED_FFMPEG_CHARS:
|
||||
if char in attr_value:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
|
|
@ -422,7 +429,12 @@ def convert_for_ffmpeg(
|
|||
compression = "none"
|
||||
|
||||
# Prepare subprocess arguments
|
||||
oiio_cmd = [get_oiio_tools_path()]
|
||||
oiio_cmd = [
|
||||
get_oiio_tools_path(),
|
||||
|
||||
# Don't add any additional attributes
|
||||
"--nosoftwareattrib",
|
||||
]
|
||||
# Add input compression if available
|
||||
if compression:
|
||||
oiio_cmd.extend(["--compression", compression])
|
||||
|
|
@ -458,23 +470,33 @@ def convert_for_ffmpeg(
|
|||
"--frames", "{}-{}".format(input_frame_start, input_frame_end)
|
||||
])
|
||||
|
||||
ignore_attr_changes_added = False
|
||||
for attr_name, attr_value in input_info["attribs"].items():
|
||||
if not isinstance(attr_value, str):
|
||||
continue
|
||||
|
||||
# Remove attributes that have string value longer than allowed length
|
||||
# for ffmpeg
|
||||
# for ffmpeg or when containt unallowed symbols
|
||||
erase_reason = "Missing reason"
|
||||
erase_attribute = False
|
||||
if len(attr_value) > MAX_FFMPEG_STRING_LEN:
|
||||
if not ignore_attr_changes_added:
|
||||
# Attrite changes won't be added to attributes itself
|
||||
ignore_attr_changes_added = True
|
||||
oiio_cmd.append("--sansattrib")
|
||||
erase_reason = "has too long value ({} chars).".format(
|
||||
len(attr_value)
|
||||
)
|
||||
|
||||
if erase_attribute:
|
||||
for char in NOT_ALLOWED_FFMPEG_CHARS:
|
||||
if char in attr_value:
|
||||
erase_attribute = True
|
||||
erase_reason = (
|
||||
"contains unsupported character \"{}\"."
|
||||
).format(char)
|
||||
break
|
||||
|
||||
if erase_attribute:
|
||||
# Set attribute to empty string
|
||||
logger.info((
|
||||
"Removed attribute \"{}\" from metadata"
|
||||
" because has too long value ({} chars)."
|
||||
).format(attr_name, len(attr_value)))
|
||||
"Removed attribute \"{}\" from metadata because {}."
|
||||
).format(attr_name, erase_reason))
|
||||
oiio_cmd.extend(["--eraseattrib", attr_name])
|
||||
|
||||
# Add last argument - path to output
|
||||
|
|
|
|||
|
|
@ -188,8 +188,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
|||
outputs_per_repres = self._get_outputs_per_representations(
|
||||
instance, profile_outputs
|
||||
)
|
||||
fill_data = copy.deepcopy(instance.data["anatomyData"])
|
||||
for repre, outputs in outputs_per_repres:
|
||||
for repre, outpu_defs in outputs_per_repres:
|
||||
# Check if input should be preconverted before processing
|
||||
# Store original staging dir (it's value may change)
|
||||
src_repre_staging_dir = repre["stagingDir"]
|
||||
|
|
@ -241,126 +240,143 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
|||
self.log
|
||||
)
|
||||
|
||||
for _output_def in outputs:
|
||||
output_def = copy.deepcopy(_output_def)
|
||||
# Make sure output definition has "tags" key
|
||||
if "tags" not in output_def:
|
||||
output_def["tags"] = []
|
||||
|
||||
if "burnins" not in output_def:
|
||||
output_def["burnins"] = []
|
||||
|
||||
# Create copy of representation
|
||||
new_repre = copy.deepcopy(repre)
|
||||
# Make sure new representation has origin staging dir
|
||||
# - this is because source representation may change
|
||||
# it's staging dir because of ffmpeg conversion
|
||||
new_repre["stagingDir"] = src_repre_staging_dir
|
||||
|
||||
# Remove "delete" tag from new repre if there is
|
||||
if "delete" in new_repre["tags"]:
|
||||
new_repre["tags"].remove("delete")
|
||||
|
||||
# Add additional tags from output definition to representation
|
||||
for tag in output_def["tags"]:
|
||||
if tag not in new_repre["tags"]:
|
||||
new_repre["tags"].append(tag)
|
||||
|
||||
# Add burnin link from output definition to representation
|
||||
for burnin in output_def["burnins"]:
|
||||
if burnin not in new_repre.get("burnins", []):
|
||||
if not new_repre.get("burnins"):
|
||||
new_repre["burnins"] = []
|
||||
new_repre["burnins"].append(str(burnin))
|
||||
|
||||
self.log.debug(
|
||||
"Linked burnins: `{}`".format(new_repre.get("burnins"))
|
||||
try:
|
||||
self._render_output_definitions(
|
||||
instance, repre, src_repre_staging_dir, outpu_defs
|
||||
)
|
||||
|
||||
self.log.debug(
|
||||
"New representation tags: `{}`".format(
|
||||
new_repre.get("tags"))
|
||||
finally:
|
||||
# Make sure temporary staging is cleaned up and representation
|
||||
# has set origin stagingDir
|
||||
if do_convert:
|
||||
# Set staging dir of source representation back to previous
|
||||
# value
|
||||
repre["stagingDir"] = src_repre_staging_dir
|
||||
if os.path.exists(new_staging_dir):
|
||||
shutil.rmtree(new_staging_dir)
|
||||
|
||||
def _render_output_definitions(
|
||||
self, instance, repre, src_repre_staging_dir, outpu_defs
|
||||
):
|
||||
fill_data = copy.deepcopy(instance.data["anatomyData"])
|
||||
for _output_def in outpu_defs:
|
||||
output_def = copy.deepcopy(_output_def)
|
||||
# Make sure output definition has "tags" key
|
||||
if "tags" not in output_def:
|
||||
output_def["tags"] = []
|
||||
|
||||
if "burnins" not in output_def:
|
||||
output_def["burnins"] = []
|
||||
|
||||
# Create copy of representation
|
||||
new_repre = copy.deepcopy(repre)
|
||||
# Make sure new representation has origin staging dir
|
||||
# - this is because source representation may change
|
||||
# it's staging dir because of ffmpeg conversion
|
||||
new_repre["stagingDir"] = src_repre_staging_dir
|
||||
|
||||
# Remove "delete" tag from new repre if there is
|
||||
if "delete" in new_repre["tags"]:
|
||||
new_repre["tags"].remove("delete")
|
||||
|
||||
# Add additional tags from output definition to representation
|
||||
for tag in output_def["tags"]:
|
||||
if tag not in new_repre["tags"]:
|
||||
new_repre["tags"].append(tag)
|
||||
|
||||
# Add burnin link from output definition to representation
|
||||
for burnin in output_def["burnins"]:
|
||||
if burnin not in new_repre.get("burnins", []):
|
||||
if not new_repre.get("burnins"):
|
||||
new_repre["burnins"] = []
|
||||
new_repre["burnins"].append(str(burnin))
|
||||
|
||||
self.log.debug(
|
||||
"Linked burnins: `{}`".format(new_repre.get("burnins"))
|
||||
)
|
||||
|
||||
self.log.debug(
|
||||
"New representation tags: `{}`".format(
|
||||
new_repre.get("tags"))
|
||||
)
|
||||
|
||||
temp_data = self.prepare_temp_data(instance, repre, output_def)
|
||||
files_to_clean = []
|
||||
if temp_data["input_is_sequence"]:
|
||||
self.log.info("Filling gaps in sequence.")
|
||||
files_to_clean = self.fill_sequence_gaps(
|
||||
temp_data["origin_repre"]["files"],
|
||||
new_repre["stagingDir"],
|
||||
temp_data["frame_start"],
|
||||
temp_data["frame_end"])
|
||||
|
||||
# create or update outputName
|
||||
output_name = new_repre.get("outputName", "")
|
||||
output_ext = new_repre["ext"]
|
||||
if output_name:
|
||||
output_name += "_"
|
||||
output_name += output_def["filename_suffix"]
|
||||
if temp_data["without_handles"]:
|
||||
output_name += "_noHandles"
|
||||
|
||||
# add outputName to anatomy format fill_data
|
||||
fill_data.update({
|
||||
"output": output_name,
|
||||
"ext": output_ext
|
||||
})
|
||||
|
||||
try: # temporary until oiiotool is supported cross platform
|
||||
ffmpeg_args = self._ffmpeg_arguments(
|
||||
output_def, instance, new_repre, temp_data, fill_data
|
||||
)
|
||||
|
||||
temp_data = self.prepare_temp_data(
|
||||
instance, repre, output_def)
|
||||
files_to_clean = []
|
||||
if temp_data["input_is_sequence"]:
|
||||
self.log.info("Filling gaps in sequence.")
|
||||
files_to_clean = self.fill_sequence_gaps(
|
||||
temp_data["origin_repre"]["files"],
|
||||
new_repre["stagingDir"],
|
||||
temp_data["frame_start"],
|
||||
temp_data["frame_end"])
|
||||
|
||||
# create or update outputName
|
||||
output_name = new_repre.get("outputName", "")
|
||||
output_ext = new_repre["ext"]
|
||||
if output_name:
|
||||
output_name += "_"
|
||||
output_name += output_def["filename_suffix"]
|
||||
if temp_data["without_handles"]:
|
||||
output_name += "_noHandles"
|
||||
|
||||
# add outputName to anatomy format fill_data
|
||||
fill_data.update({
|
||||
"output": output_name,
|
||||
"ext": output_ext
|
||||
})
|
||||
|
||||
try: # temporary until oiiotool is supported cross platform
|
||||
ffmpeg_args = self._ffmpeg_arguments(
|
||||
output_def, instance, new_repre, temp_data, fill_data
|
||||
except ZeroDivisionError:
|
||||
# TODO recalculate width and height using OIIO before
|
||||
# conversion
|
||||
if 'exr' in temp_data["origin_repre"]["ext"]:
|
||||
self.log.warning(
|
||||
(
|
||||
"Unsupported compression on input files."
|
||||
" Skipping!!!"
|
||||
),
|
||||
exc_info=True
|
||||
)
|
||||
except ZeroDivisionError:
|
||||
if 'exr' in temp_data["origin_repre"]["ext"]:
|
||||
self.log.debug("Unsupported compression on input " +
|
||||
"files. Skipping!!!")
|
||||
return
|
||||
raise NotImplementedError
|
||||
return
|
||||
raise NotImplementedError
|
||||
|
||||
subprcs_cmd = " ".join(ffmpeg_args)
|
||||
subprcs_cmd = " ".join(ffmpeg_args)
|
||||
|
||||
# run subprocess
|
||||
self.log.debug("Executing: {}".format(subprcs_cmd))
|
||||
# run subprocess
|
||||
self.log.debug("Executing: {}".format(subprcs_cmd))
|
||||
|
||||
openpype.api.run_subprocess(
|
||||
subprcs_cmd, shell=True, logger=self.log
|
||||
)
|
||||
openpype.api.run_subprocess(
|
||||
subprcs_cmd, shell=True, logger=self.log
|
||||
)
|
||||
|
||||
# delete files added to fill gaps
|
||||
if files_to_clean:
|
||||
for f in files_to_clean:
|
||||
os.unlink(f)
|
||||
# delete files added to fill gaps
|
||||
if files_to_clean:
|
||||
for f in files_to_clean:
|
||||
os.unlink(f)
|
||||
|
||||
new_repre.update({
|
||||
"name": "{}_{}".format(output_name, output_ext),
|
||||
"outputName": output_name,
|
||||
"outputDef": output_def,
|
||||
"frameStartFtrack": temp_data["output_frame_start"],
|
||||
"frameEndFtrack": temp_data["output_frame_end"],
|
||||
"ffmpeg_cmd": subprcs_cmd
|
||||
})
|
||||
new_repre.update({
|
||||
"name": "{}_{}".format(output_name, output_ext),
|
||||
"outputName": output_name,
|
||||
"outputDef": output_def,
|
||||
"frameStartFtrack": temp_data["output_frame_start"],
|
||||
"frameEndFtrack": temp_data["output_frame_end"],
|
||||
"ffmpeg_cmd": subprcs_cmd
|
||||
})
|
||||
|
||||
# Force to pop these key if are in new repre
|
||||
new_repre.pop("preview", None)
|
||||
new_repre.pop("thumbnail", None)
|
||||
if "clean_name" in new_repre.get("tags", []):
|
||||
new_repre.pop("outputName")
|
||||
# Force to pop these key if are in new repre
|
||||
new_repre.pop("preview", None)
|
||||
new_repre.pop("thumbnail", None)
|
||||
if "clean_name" in new_repre.get("tags", []):
|
||||
new_repre.pop("outputName")
|
||||
|
||||
# adding representation
|
||||
self.log.debug(
|
||||
"Adding new representation: {}".format(new_repre)
|
||||
)
|
||||
instance.data["representations"].append(new_repre)
|
||||
|
||||
# Cleanup temp staging dir after procesisng of output definitions
|
||||
if do_convert:
|
||||
temp_dir = repre["stagingDir"]
|
||||
shutil.rmtree(temp_dir)
|
||||
# Set staging dir of source representation back to previous
|
||||
# value
|
||||
repre["stagingDir"] = src_repre_staging_dir
|
||||
# adding representation
|
||||
self.log.debug(
|
||||
"Adding new representation: {}".format(new_repre)
|
||||
)
|
||||
instance.data["representations"].append(new_repre)
|
||||
|
||||
def input_is_sequence(self, repre):
|
||||
"""Deduce from representation data if input is sequence."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue