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:
Jakub Trllo 2022-04-12 13:28:45 +02:00 committed by GitHub
commit bb294767da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 163 additions and 125 deletions

View file

@ -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

View file

@ -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."""