mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
Merge pull request #1472 from pypeclub/feature/burnin_custom_filepath
This commit is contained in:
commit
697e4a2924
5 changed files with 223 additions and 156 deletions
|
|
@ -1,20 +1,24 @@
|
|||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import json
|
||||
import copy
|
||||
import tempfile
|
||||
import platform
|
||||
import shutil
|
||||
|
||||
import clique
|
||||
import six
|
||||
import pyblish
|
||||
|
||||
import openpype
|
||||
import openpype.api
|
||||
import pyblish
|
||||
from openpype.lib import (
|
||||
get_pype_execute_args,
|
||||
should_decompress,
|
||||
get_decompress_dir,
|
||||
decompress
|
||||
)
|
||||
import shutil
|
||||
|
||||
|
||||
class ExtractBurnin(openpype.api.Extractor):
|
||||
|
|
@ -49,18 +53,17 @@ class ExtractBurnin(openpype.api.Extractor):
|
|||
]
|
||||
# Default options for burnins for cases that are not set in presets.
|
||||
default_options = {
|
||||
"opacity": 1,
|
||||
"x_offset": 5,
|
||||
"y_offset": 5,
|
||||
"font_size": 42,
|
||||
"font_color": [255, 255, 255, 255],
|
||||
"bg_color": [0, 0, 0, 127],
|
||||
"bg_padding": 5,
|
||||
"bg_opacity": 0.5,
|
||||
"font_size": 42
|
||||
"x_offset": 5,
|
||||
"y_offset": 5
|
||||
}
|
||||
|
||||
# Preset attributes
|
||||
profiles = None
|
||||
options = None
|
||||
fields = None
|
||||
|
||||
def process(self, instance):
|
||||
# ffmpeg doesn't support multipart exrs
|
||||
|
|
@ -104,7 +107,7 @@ class ExtractBurnin(openpype.api.Extractor):
|
|||
return
|
||||
|
||||
# Pre-filter burnin definitions by instance families
|
||||
burnin_defs = self.filter_burnins_by_families(profile, instance)
|
||||
burnin_defs = self.filter_burnins_defs(profile, instance)
|
||||
if not burnin_defs:
|
||||
self.log.info((
|
||||
"Skipped instance. Burnin definitions are not set for profile"
|
||||
|
|
@ -112,41 +115,7 @@ class ExtractBurnin(openpype.api.Extractor):
|
|||
).format(host_name, family, task_name, profile))
|
||||
return
|
||||
|
||||
# Prepare burnin options
|
||||
profile_options = copy.deepcopy(self.default_options)
|
||||
for key, value in (self.options or {}).items():
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
if key == "bg_color" and len(value) == 4:
|
||||
bg_red, bg_green, bg_blue, bg_alpha = value
|
||||
bg_color_hex = "#{0:0>2X}{1:0>2X}{2:0>2X}".format(
|
||||
bg_red, bg_green, bg_blue
|
||||
)
|
||||
bg_color_alpha = float(bg_alpha) / 255
|
||||
profile_options["bg_opacity"] = bg_color_alpha
|
||||
profile_options["bg_color"] = bg_color_hex
|
||||
continue
|
||||
|
||||
elif key == "font_color" and len(value) == 4:
|
||||
fg_red, fg_green, fg_blue, fg_alpha = value
|
||||
fg_color_hex = "#{0:0>2X}{1:0>2X}{2:0>2X}".format(
|
||||
fg_red, fg_green, fg_blue
|
||||
)
|
||||
fg_color_alpha = float(fg_alpha) / 255
|
||||
profile_options["opacity"] = fg_color_alpha
|
||||
profile_options["font_color"] = fg_color_hex
|
||||
continue
|
||||
|
||||
profile_options[key] = value
|
||||
|
||||
# Prepare global burnin values from presets
|
||||
profile_burnins = {}
|
||||
for key, value in (self.fields or {}).items():
|
||||
key_low = key.lower()
|
||||
if key_low in self.positions:
|
||||
if value is not None:
|
||||
profile_burnins[key_low] = value
|
||||
burnin_options = self._get_burnin_options()
|
||||
|
||||
# Prepare basic data for processing
|
||||
_burnin_data, _temp_data = self.prepare_basic_data(instance)
|
||||
|
|
@ -157,11 +126,6 @@ class ExtractBurnin(openpype.api.Extractor):
|
|||
# [pype executable, *pype script, "run"]
|
||||
executable_args = get_pype_execute_args("run", scriptpath)
|
||||
|
||||
# Environments for script process
|
||||
env = os.environ.copy()
|
||||
# pop PYTHONPATH
|
||||
env.pop("PYTHONPATH", None)
|
||||
|
||||
for idx, repre in enumerate(tuple(instance.data["representations"])):
|
||||
self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"]))
|
||||
if not self.repres_is_valid(repre):
|
||||
|
|
@ -207,26 +171,11 @@ class ExtractBurnin(openpype.api.Extractor):
|
|||
elif "ftrackreview" in new_repre["tags"]:
|
||||
new_repre["tags"].remove("ftrackreview")
|
||||
|
||||
burnin_options = copy.deepcopy(profile_options)
|
||||
burnin_values = copy.deepcopy(profile_burnins)
|
||||
|
||||
# Options overrides
|
||||
for key, value in (burnin_def.get("options") or {}).items():
|
||||
# Set or override value if is valid
|
||||
if value is not None:
|
||||
burnin_options[key] = value
|
||||
|
||||
# Burnin values overrides
|
||||
for key, value in burnin_def.items():
|
||||
key_low = key.lower()
|
||||
if key_low in self.positions:
|
||||
if value is not None:
|
||||
# Set or override value if is valid
|
||||
burnin_values[key_low] = value
|
||||
|
||||
elif key_low in burnin_values:
|
||||
# Pop key if value is set to None (null in json)
|
||||
burnin_values.pop(key_low)
|
||||
burnin_values = {}
|
||||
for key in self.positions:
|
||||
value = burnin_def.get(key)
|
||||
if value:
|
||||
burnin_values[key] = value
|
||||
|
||||
# Remove "delete" tag from new representation
|
||||
if "delete" in new_repre["tags"]:
|
||||
|
|
@ -237,7 +186,8 @@ class ExtractBurnin(openpype.api.Extractor):
|
|||
# able have multiple outputs in case of more burnin presets
|
||||
# Join previous "outputName" with filename suffix
|
||||
new_name = "_".join(
|
||||
[new_repre["outputName"], filename_suffix])
|
||||
[new_repre["outputName"], filename_suffix]
|
||||
)
|
||||
new_repre["name"] = new_name
|
||||
new_repre["outputName"] = new_name
|
||||
|
||||
|
|
@ -269,7 +219,7 @@ class ExtractBurnin(openpype.api.Extractor):
|
|||
"input": temp_data["full_input_path"],
|
||||
"output": temp_data["full_output_path"],
|
||||
"burnin_data": burnin_data,
|
||||
"options": burnin_options,
|
||||
"options": copy.deepcopy(burnin_options),
|
||||
"values": burnin_values,
|
||||
"full_input_path": temp_data["full_input_paths"][0],
|
||||
"first_frame": temp_data["first_frame"]
|
||||
|
|
@ -298,10 +248,16 @@ class ExtractBurnin(openpype.api.Extractor):
|
|||
self.log.debug("Executing: {}".format(" ".join(args)))
|
||||
|
||||
# Run burnin script
|
||||
openpype.api.run_subprocess(
|
||||
args, shell=True, logger=self.log, env=env
|
||||
)
|
||||
process_kwargs = {
|
||||
"logger": self.log,
|
||||
"env": {}
|
||||
}
|
||||
if platform.system().lower() == "windows":
|
||||
process_kwargs["creationflags"] = (
|
||||
subprocess.CREATE_NO_WINDOW
|
||||
)
|
||||
|
||||
openpype.api.run_subprocess(args, **process_kwargs)
|
||||
# Remove the temporary json
|
||||
os.remove(temporary_json_filepath)
|
||||
|
||||
|
|
@ -326,6 +282,57 @@ class ExtractBurnin(openpype.api.Extractor):
|
|||
if do_decompress and os.path.exists(decompressed_dir):
|
||||
shutil.rmtree(decompressed_dir)
|
||||
|
||||
def _get_burnin_options(self):
|
||||
# Prepare burnin options
|
||||
burnin_options = copy.deepcopy(self.default_options)
|
||||
if self.options:
|
||||
for key, value in self.options.items():
|
||||
if value is not None:
|
||||
burnin_options[key] = copy.deepcopy(value)
|
||||
|
||||
# Convert colors defined as list of numbers RGBA (0-255)
|
||||
# BG Color
|
||||
bg_color = burnin_options.get("bg_color")
|
||||
if bg_color and isinstance(bg_color, list):
|
||||
bg_red, bg_green, bg_blue, bg_alpha = bg_color
|
||||
bg_color_hex = "#{0:0>2X}{1:0>2X}{2:0>2X}".format(
|
||||
bg_red, bg_green, bg_blue
|
||||
)
|
||||
bg_color_alpha = float(bg_alpha) / 255
|
||||
burnin_options["bg_opacity"] = bg_color_alpha
|
||||
burnin_options["bg_color"] = bg_color_hex
|
||||
|
||||
# FG Color
|
||||
font_color = burnin_options.get("font_color")
|
||||
if font_color and isinstance(font_color, list):
|
||||
fg_red, fg_green, fg_blue, fg_alpha = font_color
|
||||
fg_color_hex = "#{0:0>2X}{1:0>2X}{2:0>2X}".format(
|
||||
fg_red, fg_green, fg_blue
|
||||
)
|
||||
fg_color_alpha = float(fg_alpha) / 255
|
||||
burnin_options["opacity"] = fg_color_alpha
|
||||
burnin_options["font_color"] = fg_color_hex
|
||||
|
||||
# Define font filepath
|
||||
# - font filepath may be defined in settings
|
||||
font_filepath = burnin_options.get("font_filepath")
|
||||
if font_filepath and isinstance(font_filepath, dict):
|
||||
sys_name = platform.system().lower()
|
||||
font_filepath = font_filepath.get(sys_name)
|
||||
|
||||
if font_filepath and isinstance(font_filepath, six.string_types):
|
||||
font_filepath = font_filepath.format(**os.environ)
|
||||
if not os.path.exists(font_filepath):
|
||||
font_filepath = None
|
||||
|
||||
# Use OpenPype default font
|
||||
if not font_filepath:
|
||||
font_filepath = openpype.api.resources.get_liberation_font_path()
|
||||
|
||||
burnin_options["font"] = font_filepath
|
||||
|
||||
return burnin_options
|
||||
|
||||
def prepare_basic_data(self, instance):
|
||||
"""Pick data from instance for processing and for burnin strings.
|
||||
|
||||
|
|
@ -444,23 +451,15 @@ class ExtractBurnin(openpype.api.Extractor):
|
|||
list: Containg all burnin definitions matching entered tags.
|
||||
"""
|
||||
filtered_burnins = {}
|
||||
repre_tags_low = [tag.lower() for tag in tags]
|
||||
repre_tags_low = set(tag.lower() for tag in tags)
|
||||
for filename_suffix, burnin_def in burnin_defs.items():
|
||||
valid = True
|
||||
output_filters = burnin_def.get("filter")
|
||||
if output_filters:
|
||||
tag_filters = burnin_def["filter"]["tags"]
|
||||
if tag_filters:
|
||||
# Check tag filters
|
||||
tag_filters = output_filters.get("tags")
|
||||
if tag_filters:
|
||||
tag_filters_low = [tag.lower() for tag in tag_filters]
|
||||
valid = False
|
||||
for tag in repre_tags_low:
|
||||
if tag in tag_filters_low:
|
||||
valid = True
|
||||
break
|
||||
tag_filters_low = set(tag.lower() for tag in tag_filters)
|
||||
|
||||
if not valid:
|
||||
continue
|
||||
valid = bool(repre_tags_low & tag_filters_low)
|
||||
|
||||
if valid:
|
||||
filtered_burnins[filename_suffix] = burnin_def
|
||||
|
|
@ -736,17 +735,16 @@ class ExtractBurnin(openpype.api.Extractor):
|
|||
final_profile.pop("__value__")
|
||||
return final_profile
|
||||
|
||||
def filter_burnins_by_families(self, profile, instance):
|
||||
"""Filter outputs that are not supported for instance families.
|
||||
def filter_burnins_defs(self, profile, instance):
|
||||
"""Filter outputs by their values from settings.
|
||||
|
||||
Output definitions without families filter are marked as valid.
|
||||
Output definitions with at least one value are marked as valid.
|
||||
|
||||
Args:
|
||||
profile (dict): Profile from presets matching current context.
|
||||
families (list): All families of current instance.
|
||||
|
||||
Returns:
|
||||
list: Containg all output definitions matching entered families.
|
||||
list: Containg all valid output definitions.
|
||||
"""
|
||||
filtered_burnin_defs = {}
|
||||
|
||||
|
|
@ -754,21 +752,52 @@ class ExtractBurnin(openpype.api.Extractor):
|
|||
if not burnin_defs:
|
||||
return filtered_burnin_defs
|
||||
|
||||
# Prepare families
|
||||
families = self.families_from_instance(instance)
|
||||
families = [family.lower() for family in families]
|
||||
low_families = [family.lower() for family in families]
|
||||
|
||||
for filename_suffix, burnin_def in burnin_defs.items():
|
||||
burnin_filter = burnin_def.get("filter")
|
||||
# When filters not set then skip filtering process
|
||||
if burnin_filter:
|
||||
families_filters = burnin_filter.get("families")
|
||||
if not self.families_filter_validation(
|
||||
families, families_filters
|
||||
):
|
||||
continue
|
||||
for filename_suffix, orig_burnin_def in burnin_defs.items():
|
||||
burnin_def = copy.deepcopy(orig_burnin_def)
|
||||
def_filter = burnin_def.get("filter", None) or {}
|
||||
for key in ("families", "tags"):
|
||||
if key not in def_filter:
|
||||
def_filter[key] = []
|
||||
|
||||
families_filters = def_filter["families"]
|
||||
if not self.families_filter_validation(
|
||||
low_families, families_filters
|
||||
):
|
||||
self.log.debug((
|
||||
"Skipped burnin definition \"{}\". Family"
|
||||
" fiters ({}) does not match current instance families: {}"
|
||||
).format(
|
||||
filename_suffix, str(families_filters), str(families)
|
||||
))
|
||||
continue
|
||||
|
||||
# Burnin values
|
||||
burnin_values = {}
|
||||
for key, value in tuple(burnin_def.items()):
|
||||
key_low = key.lower()
|
||||
if key_low in self.positions and value:
|
||||
burnin_values[key_low] = value
|
||||
|
||||
# Skip processing if burnin values are not set
|
||||
if not burnin_values:
|
||||
self.log.warning((
|
||||
"Burnin values for Burnin definition \"{}\""
|
||||
" are not filled. Definition will be skipped."
|
||||
" Origin value: {}"
|
||||
).format(filename_suffix, str(orig_burnin_def)))
|
||||
continue
|
||||
|
||||
burnin_values["filter"] = def_filter
|
||||
|
||||
filtered_burnin_defs[filename_suffix] = burnin_values
|
||||
|
||||
self.log.debug((
|
||||
"Burnin definition \"{}\" passed first filtering."
|
||||
).format(filename_suffix))
|
||||
|
||||
filtered_burnin_defs[filename_suffix] = burnin_def
|
||||
return filtered_burnin_defs
|
||||
|
||||
def families_filter_validation(self, families, output_families_filter):
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import subprocess
|
|||
import platform
|
||||
import json
|
||||
import opentimelineio_contrib.adapters.ffmpeg_burnins as ffmpeg_burnins
|
||||
from openpype.api import resources
|
||||
import openpype.lib
|
||||
|
||||
|
||||
|
|
@ -127,11 +126,8 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins):
|
|||
if not streams:
|
||||
streams = _streams(source)
|
||||
|
||||
input_args = []
|
||||
if first_frame:
|
||||
input_args.append("-start_number {}".format(first_frame))
|
||||
|
||||
self.input_args = input_args
|
||||
self.first_frame = first_frame
|
||||
self.input_args = []
|
||||
|
||||
super().__init__(source, streams)
|
||||
|
||||
|
|
@ -244,31 +240,26 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins):
|
|||
timecode_text = options.get("timecode") or ""
|
||||
text_for_size += timecode_text
|
||||
|
||||
font_path = options.get("font")
|
||||
if not font_path or not os.path.exists(font_path):
|
||||
font_path = ffmpeg_burnins.FONT
|
||||
|
||||
options["font"] = font_path
|
||||
|
||||
data.update(options)
|
||||
|
||||
os_system = platform.system().lower()
|
||||
data_font = data.get("font")
|
||||
if not data_font:
|
||||
data_font = (
|
||||
resources.get_liberation_font_path().replace("\\", "/")
|
||||
)
|
||||
elif isinstance(data_font, dict):
|
||||
data_font = data_font[os_system]
|
||||
|
||||
if data_font:
|
||||
data["font"] = data_font
|
||||
options["font"] = data_font
|
||||
if ffmpeg_burnins._is_windows():
|
||||
data["font"] = (
|
||||
data_font
|
||||
.replace(os.sep, r'\\' + os.sep)
|
||||
.replace(':', r'\:')
|
||||
)
|
||||
|
||||
data.update(
|
||||
ffmpeg_burnins._drawtext(align, resolution, text_for_size, options)
|
||||
)
|
||||
|
||||
arg_font_path = font_path
|
||||
if platform.system().lower() == "windows":
|
||||
arg_font_path = (
|
||||
arg_font_path
|
||||
.replace(os.sep, r'\\' + os.sep)
|
||||
.replace(':', r'\:')
|
||||
)
|
||||
data["font"] = arg_font_path
|
||||
|
||||
self.filters['drawtext'].append(draw % data)
|
||||
|
||||
if options.get('bg_color') is not None:
|
||||
|
|
@ -297,6 +288,15 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins):
|
|||
if self.filter_string:
|
||||
filters = '-vf "{}"'.format(self.filter_string)
|
||||
|
||||
if self.first_frame is not None:
|
||||
start_number_arg = "-start_number {}".format(self.first_frame)
|
||||
self.input_args.append(start_number_arg)
|
||||
if "start_number" not in args:
|
||||
if not args:
|
||||
args = start_number_arg
|
||||
else:
|
||||
args = " ".join((start_number_arg, args))
|
||||
|
||||
input_args = ""
|
||||
if self.input_args:
|
||||
input_args = " {}".format(" ".join(self.input_args))
|
||||
|
|
|
|||
|
|
@ -87,7 +87,12 @@
|
|||
],
|
||||
"x_offset": 5,
|
||||
"y_offset": 5,
|
||||
"bg_padding": 5
|
||||
"bg_padding": 5,
|
||||
"font_filepath": {
|
||||
"windows": "",
|
||||
"darwin": "",
|
||||
"linux": ""
|
||||
}
|
||||
},
|
||||
"profiles": [
|
||||
{
|
||||
|
|
@ -100,7 +105,11 @@
|
|||
"TOP_RIGHT": "{anatomy[version]}",
|
||||
"BOTTOM_LEFT": "{username}",
|
||||
"BOTTOM_CENTERED": "{asset}",
|
||||
"BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}"
|
||||
"BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}",
|
||||
"filter": {
|
||||
"families": [],
|
||||
"tags": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,30 +101,8 @@
|
|||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "tags",
|
||||
"label": "Tags",
|
||||
"type": "enum",
|
||||
"multiselection": true,
|
||||
"enum_items": [
|
||||
{
|
||||
"burnin": "Add burnins"
|
||||
},
|
||||
{
|
||||
"ftrackreview": "Add to Ftrack"
|
||||
},
|
||||
{
|
||||
"delete": "Delete output"
|
||||
},
|
||||
{
|
||||
"slate-frame": "Add slate frame"
|
||||
},
|
||||
{
|
||||
"no-handles": "Skip handle frames"
|
||||
},
|
||||
{
|
||||
"sequence": "Output as image sequence"
|
||||
}
|
||||
]
|
||||
"type": "schema",
|
||||
"name": "schema_representation_tags"
|
||||
},
|
||||
{
|
||||
"key": "ffmpeg_args",
|
||||
|
|
@ -334,6 +312,13 @@
|
|||
"type": "number",
|
||||
"key": "bg_padding",
|
||||
"label": "Padding aroung text"
|
||||
},
|
||||
{
|
||||
"type": "path",
|
||||
"key": "font_filepath",
|
||||
"label": "Font file path",
|
||||
"multipath": false,
|
||||
"multiplatform": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -400,6 +385,24 @@
|
|||
"key": "BOTTOM_RIGHT",
|
||||
"label": "BottomRight",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "filter",
|
||||
"label": "Additional filtering",
|
||||
"type": "dict",
|
||||
"highlight_content": true,
|
||||
"children": [
|
||||
{
|
||||
"key": "families",
|
||||
"label": "Families",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_representation_tags"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"key": "tags",
|
||||
"label": "Tags",
|
||||
"type": "enum",
|
||||
"multiselection": true,
|
||||
"enum_items": [
|
||||
{
|
||||
"burnin": "Add burnins"
|
||||
},
|
||||
{
|
||||
"ftrackreview": "Add to Ftrack"
|
||||
},
|
||||
{
|
||||
"delete": "Delete output"
|
||||
},
|
||||
{
|
||||
"slate-frame": "Add slate frame"
|
||||
},
|
||||
{
|
||||
"no-handles": "Skip handle frames"
|
||||
},
|
||||
{
|
||||
"sequence": "Output as image sequence"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue