mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-02 08:54:53 +01:00
Merge branch 'develop' into enhancement/OP-5468_3dsMax-render-dialogue-needs-to-be-closed
This commit is contained in:
commit
7512052b93
11 changed files with 336 additions and 17 deletions
15
.github/pr-branch-labeler.yml
vendored
Normal file
15
.github/pr-branch-labeler.yml
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# Apply label "feature" if head matches "feature/*"
|
||||
'type: feature':
|
||||
head: "feature/*"
|
||||
|
||||
# Apply label "feature" if head matches "feature/*"
|
||||
'type: enhancement':
|
||||
head: "enhancement/*"
|
||||
|
||||
# Apply label "bugfix" if head matches one of "bugfix/*" or "hotfix/*"
|
||||
'type: bug':
|
||||
head: ["bugfix/*", "hotfix/*"]
|
||||
|
||||
# Apply label "release" if base matches "release/*"
|
||||
'Bump Minor':
|
||||
base: "release/next-minor"
|
||||
102
.github/pr-glob-labeler.yml
vendored
Normal file
102
.github/pr-glob-labeler.yml
vendored
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
# Add type: unittest label if any changes in tests folders
|
||||
'type: unittest':
|
||||
- '*/*tests*/**/*'
|
||||
|
||||
# any changes in documentation structure
|
||||
'type: documentation':
|
||||
- '*/**/*website*/**/*'
|
||||
- '*/**/*docs*/**/*'
|
||||
|
||||
# hosts triage
|
||||
'host: Nuke':
|
||||
- '*/**/*nuke*'
|
||||
- '*/**/*nuke*/**/*'
|
||||
|
||||
'host: Photoshop':
|
||||
- '*/**/*photoshop*'
|
||||
- '*/**/*photoshop*/**/*'
|
||||
|
||||
'host: Harmony':
|
||||
- '*/**/*harmony*'
|
||||
- '*/**/*harmony*/**/*'
|
||||
|
||||
'host: UE':
|
||||
- '*/**/*unreal*'
|
||||
- '*/**/*unreal*/**/*'
|
||||
|
||||
'host: Houdini':
|
||||
- '*/**/*houdini*'
|
||||
- '*/**/*houdini*/**/*'
|
||||
|
||||
'host: Maya':
|
||||
- '*/**/*maya*'
|
||||
- '*/**/*maya*/**/*'
|
||||
|
||||
'host: Resolve':
|
||||
- '*/**/*resolve*'
|
||||
- '*/**/*resolve*/**/*'
|
||||
|
||||
'host: Blender':
|
||||
- '*/**/*blender*'
|
||||
- '*/**/*blender*/**/*'
|
||||
|
||||
'host: Hiero':
|
||||
- '*/**/*hiero*'
|
||||
- '*/**/*hiero*/**/*'
|
||||
|
||||
'host: Fusion':
|
||||
- '*/**/*fusion*'
|
||||
- '*/**/*fusion*/**/*'
|
||||
|
||||
'host: Flame':
|
||||
- '*/**/*flame*'
|
||||
- '*/**/*flame*/**/*'
|
||||
|
||||
'host: TrayPublisher':
|
||||
- '*/**/*traypublisher*'
|
||||
- '*/**/*traypublisher*/**/*'
|
||||
|
||||
'host: 3dsmax':
|
||||
- '*/**/*max*'
|
||||
- '*/**/*max*/**/*'
|
||||
|
||||
'host: TV Paint':
|
||||
- '*/**/*tvpaint*'
|
||||
- '*/**/*tvpaint*/**/*'
|
||||
|
||||
'host: CelAction':
|
||||
- '*/**/*celaction*'
|
||||
- '*/**/*celaction*/**/*'
|
||||
|
||||
'host: After Effects':
|
||||
- '*/**/*aftereffects*'
|
||||
- '*/**/*aftereffects*/**/*'
|
||||
|
||||
'host: Substance Painter':
|
||||
- '*/**/*substancepainter*'
|
||||
- '*/**/*substancepainter*/**/*'
|
||||
|
||||
# modules triage
|
||||
'module: Deadline':
|
||||
- '*/**/*deadline*'
|
||||
- '*/**/*deadline*/**/*'
|
||||
|
||||
'module: RoyalRender':
|
||||
- '*/**/*royalrender*'
|
||||
- '*/**/*royalrender*/**/*'
|
||||
|
||||
'module: Sitesync':
|
||||
- '*/**/*sync_server*'
|
||||
- '*/**/*sync_server*/**/*'
|
||||
|
||||
'module: Ftrack':
|
||||
- '*/**/*ftrack*'
|
||||
- '*/**/*ftrack*/**/*'
|
||||
|
||||
'module: Shotgrid':
|
||||
- '*/**/*shotgrid*'
|
||||
- '*/**/*shotgrid*/**/*'
|
||||
|
||||
'module: Kitsu':
|
||||
- '*/**/*kitsu*'
|
||||
- '*/**/*kitsu*/**/*'
|
||||
54
.github/workflows/project_actions.yml
vendored
54
.github/workflows/project_actions.yml
vendored
|
|
@ -1,8 +1,8 @@
|
|||
name: project-actions
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [review_requested]
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, assigned, review_requested]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
|
|
@ -20,3 +20,53 @@ jobs:
|
|||
project_id: 11
|
||||
resource_node_id: ${{ github.event.pull_request.node_id }}
|
||||
status_value: Change Requested
|
||||
|
||||
size-label:
|
||||
name: pr_size_label
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
${{(github.event_name == 'pull_request' && github.event.action == 'assigned')
|
||||
|| (github.event_name == 'pull_request' && github.event.action == 'opened')}}
|
||||
|
||||
steps:
|
||||
- name: Add size label
|
||||
uses: "pascalgn/size-label-action@v0.4.3"
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}"
|
||||
IGNORED: ".gitignore\n*.md\n*.json"
|
||||
with:
|
||||
sizes: >
|
||||
{
|
||||
"0": "XS",
|
||||
"100": "S",
|
||||
"500": "M",
|
||||
"1000": "L",
|
||||
"1500": "XL",
|
||||
"2500": "XXL"
|
||||
}
|
||||
|
||||
label_prs_branch:
|
||||
name: pr_branch_label
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
${{(github.event_name == 'pull_request' && github.event.action == 'assigned')
|
||||
|| (github.event_name == 'pull_request' && github.event.action == 'opened')}}
|
||||
steps:
|
||||
- name: Label PRs - Branch name detection
|
||||
uses: ffittschen/pr-branch-labeler@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.YNPUT_BOT_TOKEN }}
|
||||
|
||||
label_prs_globe:
|
||||
name: pr_globe_label
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
${{(github.event_name == 'pull_request' && github.event.action == 'assigned')
|
||||
|| (github.event_name == 'pull_request' && github.event.action == 'opened')}}
|
||||
steps:
|
||||
- name: Label PRs - Globe detection
|
||||
uses: actions/labeler@v4.0.3
|
||||
with:
|
||||
repo-token: ${{ secrets.YNPUT_BOT_TOKEN }}
|
||||
configuration-path: ".github/pr-glob-labeler.yml"
|
||||
sync-labels: false
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import os
|
||||
import hou
|
||||
|
||||
from openpype.pipeline import legacy_io
|
||||
import pyblish.api
|
||||
|
||||
|
||||
|
|
@ -11,7 +10,7 @@ class CollectHoudiniCurrentFile(pyblish.api.InstancePlugin):
|
|||
order = pyblish.api.CollectorOrder - 0.01
|
||||
label = "Houdini Current File"
|
||||
hosts = ["houdini"]
|
||||
family = ["workfile"]
|
||||
families = ["workfile"]
|
||||
|
||||
def process(self, instance):
|
||||
"""Inject the current working file"""
|
||||
|
|
@ -21,7 +20,7 @@ class CollectHoudiniCurrentFile(pyblish.api.InstancePlugin):
|
|||
# By default, Houdini will even point a new scene to a path.
|
||||
# However if the file is not saved at all and does not exist,
|
||||
# we assume the user never set it.
|
||||
filepath = ""
|
||||
current_file = ""
|
||||
|
||||
elif os.path.basename(current_file) == "untitled.hip":
|
||||
# Due to even a new file being called 'untitled.hip' we are unable
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import pyblish.api
|
|||
|
||||
from openpype.client import get_subset_by_name
|
||||
from openpype.pipeline import legacy_io
|
||||
from openpype.hosts.maya.api.lib import get_attribute_input
|
||||
|
||||
|
||||
class CollectReview(pyblish.api.InstancePlugin):
|
||||
|
|
@ -146,3 +147,21 @@ class CollectReview(pyblish.api.InstancePlugin):
|
|||
"filename": node.filename.get()
|
||||
}
|
||||
)
|
||||
|
||||
# Collect focal length.
|
||||
attr = camera + ".focalLength"
|
||||
focal_length = None
|
||||
if get_attribute_input(attr):
|
||||
start = instance.data["frameStart"]
|
||||
end = instance.data["frameEnd"] + 1
|
||||
focal_length = [
|
||||
cmds.getAttr(attr, time=t) for t in range(int(start), int(end))
|
||||
]
|
||||
else:
|
||||
focal_length = cmds.getAttr(attr)
|
||||
|
||||
key = "focalLength"
|
||||
try:
|
||||
instance.data["burninDataMembers"][key] = focal_length
|
||||
except KeyError:
|
||||
instance.data["burninDataMembers"] = {key: focal_length}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import tempfile
|
|||
from .log import Logger
|
||||
from .vendor_bin_utils import find_executable
|
||||
|
||||
from .openpype_version import is_running_from_build
|
||||
|
||||
# MSDN process creation flag (Windows only)
|
||||
CREATE_NO_WINDOW = 0x08000000
|
||||
|
||||
|
|
@ -200,6 +202,11 @@ def run_openpype_process(*args, **kwargs):
|
|||
# Skip envs that can affect OpenPype process
|
||||
# - fill more if you find more
|
||||
env = clean_envs_for_openpype_process(os.environ)
|
||||
|
||||
# Only keep OpenPype version if we are running from build.
|
||||
if not is_running_from_build():
|
||||
env.pop("OPENPYPE_VERSION", None)
|
||||
|
||||
return run_subprocess(args, env=env, **kwargs)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -252,6 +252,9 @@ class ExtractBurnin(publish.Extractor):
|
|||
# Add context data burnin_data.
|
||||
burnin_data["custom"] = custom_data
|
||||
|
||||
# Add data members.
|
||||
burnin_data.update(instance.data.get("burninDataMembers", {}))
|
||||
|
||||
# Add source camera name to burnin data
|
||||
camera_name = repre.get("camera_name")
|
||||
if camera_name:
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ import re
|
|||
import subprocess
|
||||
import platform
|
||||
import json
|
||||
import opentimelineio_contrib.adapters.ffmpeg_burnins as ffmpeg_burnins
|
||||
import tempfile
|
||||
from string import Formatter
|
||||
|
||||
import opentimelineio_contrib.adapters.ffmpeg_burnins as ffmpeg_burnins
|
||||
from openpype.lib import (
|
||||
get_ffmpeg_tool_path,
|
||||
get_ffmpeg_codec_args,
|
||||
|
|
@ -23,7 +25,7 @@ FFMPEG = (
|
|||
).format(ffmpeg_path)
|
||||
|
||||
DRAWTEXT = (
|
||||
"drawtext=fontfile='%(font)s':text=\\'%(text)s\\':"
|
||||
"drawtext@'%(label)s'=fontfile='%(font)s':text=\\'%(text)s\\':"
|
||||
"x=%(x)s:y=%(y)s:fontcolor=%(color)s@%(opacity).1f:fontsize=%(size)d"
|
||||
)
|
||||
TIMECODE = (
|
||||
|
|
@ -39,6 +41,45 @@ TIMECODE_KEY = "{timecode}"
|
|||
SOURCE_TIMECODE_KEY = "{source_timecode}"
|
||||
|
||||
|
||||
def convert_list_to_command(list_to_convert, fps, label=""):
|
||||
"""Convert a list of values to a drawtext command file for ffmpeg `sendcmd`
|
||||
|
||||
The list of values is expected to have a value per frame. If the video
|
||||
file ends up being longer than the amount of samples per frame than the
|
||||
last value will be held.
|
||||
|
||||
Args:
|
||||
list_to_convert (list): List of values per frame.
|
||||
fps (float or int): The expected frame per seconds of the output file.
|
||||
label (str): Label for the drawtext, if specific drawtext filter is
|
||||
required
|
||||
|
||||
Returns:
|
||||
str: Filepath to the temporary drawtext command file.
|
||||
|
||||
"""
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
|
||||
for i, value in enumerate(list_to_convert):
|
||||
seconds = i / fps
|
||||
|
||||
# Escape special character
|
||||
value = str(value).replace(":", "\\:")
|
||||
|
||||
filter = "drawtext"
|
||||
if label:
|
||||
filter += "@" + label
|
||||
|
||||
line = (
|
||||
"{start} {filter} reinit text='{value}';"
|
||||
"\n".format(start=seconds, filter=filter, value=value)
|
||||
)
|
||||
|
||||
f.write(line)
|
||||
f.flush()
|
||||
return f.name
|
||||
|
||||
|
||||
def _get_ffprobe_data(source):
|
||||
"""Reimplemented from otio burnins to be able use full path to ffprobe
|
||||
:param str source: source media file
|
||||
|
|
@ -144,7 +185,13 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins):
|
|||
self.options_init.update(options_init)
|
||||
|
||||
def add_text(
|
||||
self, text, align, frame_start=None, frame_end=None, options=None
|
||||
self,
|
||||
text,
|
||||
align,
|
||||
frame_start=None,
|
||||
frame_end=None,
|
||||
options=None,
|
||||
cmd=""
|
||||
):
|
||||
"""
|
||||
Adding static text to a filter.
|
||||
|
|
@ -165,7 +212,13 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins):
|
|||
if frame_end is not None:
|
||||
options["frame_end"] = frame_end
|
||||
|
||||
self._add_burnin(text, align, options, DRAWTEXT)
|
||||
draw_text = DRAWTEXT
|
||||
if cmd:
|
||||
draw_text = "{}, {}".format(cmd, DRAWTEXT)
|
||||
|
||||
options["label"] = align
|
||||
|
||||
self._add_burnin(text, align, options, draw_text)
|
||||
|
||||
def add_timecode(
|
||||
self, align, frame_start=None, frame_end=None, frame_start_tc=None,
|
||||
|
|
@ -408,11 +461,13 @@ def burnins_from_data(
|
|||
True by default.
|
||||
|
||||
Presets must be set separately. Should be dict with 2 keys:
|
||||
- "options" - sets look of burnins - colors, opacity,...(more info: ModifiedBurnins doc)
|
||||
- "options" - sets look of burnins - colors, opacity,...
|
||||
(more info: ModifiedBurnins doc)
|
||||
- *OPTIONAL* default values are used when not included
|
||||
- "burnins" - contains dictionary with burnins settings
|
||||
- *OPTIONAL* burnins won't be added (easier is not to use this)
|
||||
- each key of "burnins" represents Alignment, there are 6 possibilities:
|
||||
- each key of "burnins" represents Alignment,
|
||||
there are 6 possibilities:
|
||||
TOP_LEFT TOP_CENTERED TOP_RIGHT
|
||||
BOTTOM_LEFT BOTTOM_CENTERED BOTTOM_RIGHT
|
||||
- value must be string with text you want to burn-in
|
||||
|
|
@ -491,13 +546,14 @@ def burnins_from_data(
|
|||
if source_timecode is not None:
|
||||
data[SOURCE_TIMECODE_KEY[1:-1]] = SOURCE_TIMECODE_KEY
|
||||
|
||||
clean_up_paths = []
|
||||
for align_text, value in burnin_values.items():
|
||||
if not value:
|
||||
continue
|
||||
|
||||
if isinstance(value, (dict, list, tuple)):
|
||||
if isinstance(value, dict):
|
||||
raise TypeError((
|
||||
"Expected string or number type."
|
||||
"Expected string, number or list type."
|
||||
" Got: {} - \"{}\""
|
||||
" (Make sure you have new burnin presets)."
|
||||
).format(str(type(value)), str(value)))
|
||||
|
|
@ -533,8 +589,48 @@ def burnins_from_data(
|
|||
print("Source does not have set timecode value.")
|
||||
value = value.replace(SOURCE_TIMECODE_KEY, MISSING_KEY_VALUE)
|
||||
|
||||
key_pattern = re.compile(r"(\{.*?[^{0]*\})")
|
||||
# Convert lists.
|
||||
cmd = ""
|
||||
text = None
|
||||
keys = [i[1] for i in Formatter().parse(value) if i[1] is not None]
|
||||
list_to_convert = []
|
||||
|
||||
# Warn about nested dictionary support for lists. Ei. we dont support
|
||||
# it.
|
||||
if "[" in "".join(keys):
|
||||
print(
|
||||
"We dont support converting nested dictionaries to lists,"
|
||||
" so skipping {}".format(value)
|
||||
)
|
||||
else:
|
||||
for key in keys:
|
||||
data_value = data[key]
|
||||
|
||||
# Multiple lists are not supported.
|
||||
if isinstance(data_value, list) and list_to_convert:
|
||||
raise ValueError(
|
||||
"Found multiple lists to convert, which is not "
|
||||
"supported: {}".format(value)
|
||||
)
|
||||
|
||||
if isinstance(data_value, list):
|
||||
print("Found list to convert: {}".format(data_value))
|
||||
for v in data_value:
|
||||
data[key] = v
|
||||
list_to_convert.append(value.format(**data))
|
||||
|
||||
if list_to_convert:
|
||||
value = list_to_convert[0]
|
||||
path = convert_list_to_command(
|
||||
list_to_convert, data["fps"], label=align
|
||||
)
|
||||
cmd = "sendcmd=f='{}'".format(path)
|
||||
cmd = cmd.replace("\\", "/")
|
||||
cmd = cmd.replace(":", "\\:")
|
||||
clean_up_paths.append(path)
|
||||
|
||||
# Failsafe for missing keys.
|
||||
key_pattern = re.compile(r"(\{.*?[^{0]*\})")
|
||||
missing_keys = []
|
||||
for group in key_pattern.findall(value):
|
||||
try:
|
||||
|
|
@ -568,7 +664,8 @@ def burnins_from_data(
|
|||
continue
|
||||
|
||||
text = value.format(**data)
|
||||
burnin.add_text(text, align, frame_start, frame_end)
|
||||
|
||||
burnin.add_text(text, align, frame_start, frame_end, cmd=cmd)
|
||||
|
||||
ffmpeg_args = []
|
||||
if codec_data:
|
||||
|
|
@ -599,6 +696,8 @@ def burnins_from_data(
|
|||
burnin.render(
|
||||
output_path, args=ffmpeg_args_str, overwrite=overwrite, **data
|
||||
)
|
||||
for path in clean_up_paths:
|
||||
os.remove(path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -249,6 +249,29 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"families": [],
|
||||
"hosts": [
|
||||
"maya"
|
||||
],
|
||||
"task_types": [],
|
||||
"task_names": [],
|
||||
"subsets": [],
|
||||
"burnins": {
|
||||
"maya_burnin": {
|
||||
"TOP_LEFT": "{yy}-{mm}-{dd}",
|
||||
"TOP_CENTERED": "{focalLength:.2f} mm",
|
||||
"TOP_RIGHT": "{anatomy[version]}",
|
||||
"BOTTOM_LEFT": "{username}",
|
||||
"BOTTOM_CENTERED": "{asset}",
|
||||
"BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}",
|
||||
"filter": {
|
||||
"families": [],
|
||||
"tags": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@
|
|||
{
|
||||
"type": "text",
|
||||
"key": "compression",
|
||||
"label": "Compression type"
|
||||
"label": "Encoding"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "format",
|
||||
"label": "Data format"
|
||||
"label": "Format"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
|
|
|
|||
|
|
@ -293,6 +293,7 @@ If source representation has suffix **"h264"** and burnin suffix is **"client"**
|
|||
- It is allowed to use [Anatomy templates](admin_config#anatomy) themselves in burnins if they can be filled with available data.
|
||||
|
||||
- Additional keys in burnins:
|
||||
|
||||
| Burnin key | Description |
|
||||
| --- | --- |
|
||||
| frame_start | First frame number. |
|
||||
|
|
@ -303,6 +304,7 @@ If source representation has suffix **"h264"** and burnin suffix is **"client"**
|
|||
| resolution_height | Resolution height. |
|
||||
| fps | Fps of an output. |
|
||||
| timecode | Timecode by frame start and fps. |
|
||||
| focalLength | **Only available in Maya**<br /><br />Camera focal length per frame. Use syntax `{focalLength:.2f}` for decimal truncating. Eg. `35.234985` with `{focalLength:.2f}` would produce `35.23`, whereas `{focalLength:.0f}` would produce `35`. |
|
||||
|
||||
:::warning
|
||||
`timecode` is specific key that can be **only at the end of content**. (`"BOTTOM_RIGHT": "TC: {timecode}"`)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue