Merge pull request #3222 from pypeclub/bugfix/OP-3139_Hiero-published-mp4s-are-one-frame-longer-310

This commit is contained in:
Jakub Ježek 2022-05-26 15:03:58 +02:00 committed by GitHub
commit 5474af7906
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 194 additions and 132 deletions

View file

@ -27,6 +27,7 @@ from .lib import (
get_track_items,
get_current_project,
get_current_sequence,
get_timeline_selection,
get_current_track,
get_track_item_pype_tag,
set_track_item_pype_tag,
@ -80,6 +81,7 @@ __all__ = [
"get_track_items",
"get_current_project",
"get_current_sequence",
"get_timeline_selection",
"get_current_track",
"get_track_item_pype_tag",
"set_track_item_pype_tag",

View file

@ -109,8 +109,9 @@ def register_hiero_events():
# hiero.core.events.registerInterest("kShutdown", shutDown)
# hiero.core.events.registerInterest("kStartup", startupCompleted)
hiero.core.events.registerInterest(
("kSelectionChanged", "kTimeline"), selection_changed_timeline)
# INFO: was disabled because it was slowing down timeline operations
# hiero.core.events.registerInterest(
# ("kSelectionChanged", "kTimeline"), selection_changed_timeline)
# workfiles
try:

View file

@ -1,6 +1,8 @@
"""
Host specific functions where host api is connected
"""
from copy import deepcopy
import os
import re
import sys
@ -89,13 +91,19 @@ def get_current_sequence(name=None, new=False):
if not sequence:
# if nothing found create new with input name
sequence = get_current_sequence(name, True)
elif not name and not new:
else:
# if name is none and new is False then return current open sequence
sequence = hiero.ui.activeSequence()
return sequence
def get_timeline_selection():
active_sequence = hiero.ui.activeSequence()
timeline_editor = hiero.ui.getTimelineEditor(active_sequence)
return list(timeline_editor.selection())
def get_current_track(sequence, name, audio=False):
"""
Get current track in context of active project.
@ -118,7 +126,7 @@ def get_current_track(sequence, name, audio=False):
# get track by name
track = None
for _track in tracks:
if _track.name() in name:
if _track.name() == name:
track = _track
if not track:
@ -126,13 +134,14 @@ def get_current_track(sequence, name, audio=False):
track = hiero.core.VideoTrack(name)
else:
track = hiero.core.AudioTrack(name)
sequence.addTrack(track)
return track
def get_track_items(
selected=False,
selection=False,
sequence_name=None,
track_item_name=None,
track_name=None,
@ -143,7 +152,7 @@ def get_track_items(
"""Get all available current timeline track items.
Attribute:
selected (bool)[optional]: return only selected items on timeline
selection (list)[optional]: list of selected track items
sequence_name (str)[optional]: return only clips from input sequence
track_item_name (str)[optional]: return only item with input name
track_name (str)[optional]: return only items from track name
@ -155,32 +164,34 @@ def get_track_items(
Return:
list or hiero.core.TrackItem: list of track items or single track item
"""
return_list = list()
track_items = list()
track_type = track_type or "video"
selection = selection or []
return_list = []
# get selected track items or all in active sequence
if selected:
if selection:
try:
selected_items = list(hiero.selection)
for item in selected_items:
if track_name and track_name in item.parent().name():
# filter only items fitting input track name
track_items.append(item)
elif not track_name:
# or add all if no track_name was defined
track_items.append(item)
for track_item in selection:
log.info("___ track_item: {}".format(track_item))
# make sure only trackitems are selected
if not isinstance(track_item, hiero.core.TrackItem):
continue
if _validate_all_atrributes(
track_item,
track_item_name,
track_name,
track_type,
check_enabled,
check_tagged
):
log.info("___ valid trackitem: {}".format(track_item))
return_list.append(track_item)
except AttributeError:
pass
# check if any collected track items are
# `core.Hiero.Python.TrackItem` instance
if track_items:
any_track_item = track_items[0]
if not isinstance(any_track_item, hiero.core.TrackItem):
selected_items = []
# collect all available active sequence track items
if not track_items:
if not return_list:
sequence = get_current_sequence(name=sequence_name)
# get all available tracks from sequence
tracks = list(sequence.audioTracks()) + list(sequence.videoTracks())
@ -191,42 +202,76 @@ def get_track_items(
if check_enabled and not track.isEnabled():
continue
# and all items in track
for item in track.items():
if check_tagged and not item.tags():
for track_item in track.items():
# make sure no subtrackitem is also track items
if not isinstance(track_item, hiero.core.TrackItem):
continue
# check if track item is enabled
if check_enabled:
if not item.isEnabled():
continue
if track_item_name:
if track_item_name in item.name():
return item
# make sure only track items with correct track names are added
if track_name and track_name in track.name():
# filter out only defined track_name items
track_items.append(item)
elif not track_name:
# or add all if no track_name is defined
track_items.append(item)
if _validate_all_atrributes(
track_item,
track_item_name,
track_name,
track_type,
check_enabled,
check_tagged
):
return_list.append(track_item)
# filter out only track items with defined track_type
for track_item in track_items:
if track_type and track_type == "video" and isinstance(
return return_list
def _validate_all_atrributes(
track_item,
track_item_name,
track_name,
track_type,
check_enabled,
check_tagged
):
def _validate_correct_name_track_item():
if track_item_name and track_item_name in track_item.name():
return True
elif not track_item_name:
return True
def _validate_tagged_track_item():
if check_tagged and track_item.tags():
return True
elif not check_tagged:
return True
def _validate_enabled_track_item():
if check_enabled and track_item.isEnabled():
return True
elif not check_enabled:
return True
def _validate_parent_track_item():
if track_name and track_name in track_item.parent().name():
# filter only items fitting input track name
return True
elif not track_name:
# or add all if no track_name was defined
return True
def _validate_type_track_item():
if track_type == "video" and isinstance(
track_item.parent(), hiero.core.VideoTrack):
# only video track items are allowed
return_list.append(track_item)
elif track_type and track_type == "audio" and isinstance(
return True
elif track_type == "audio" and isinstance(
track_item.parent(), hiero.core.AudioTrack):
# only audio track items are allowed
return_list.append(track_item)
elif not track_type:
# add all if no track_type is defined
return_list.append(track_item)
return True
# return output list but make sure all items are TrackItems
return [_i for _i in return_list
if type(_i) == hiero.core.TrackItem]
# check if track item is enabled
return all([
_validate_enabled_track_item(),
_validate_type_track_item(),
_validate_tagged_track_item(),
_validate_parent_track_item(),
_validate_correct_name_track_item()
])
def get_track_item_pype_tag(track_item):
@ -245,7 +290,7 @@ def get_track_item_pype_tag(track_item):
return None
for tag in _tags:
# return only correct tag defined by global name
if tag.name() in self.pype_tag_name:
if tag.name() == self.pype_tag_name:
return tag
@ -266,7 +311,7 @@ def set_track_item_pype_tag(track_item, data=None):
"editable": "0",
"note": "OpenPype data container",
"icon": "openpype_icon.png",
"metadata": {k: v for k, v in data.items()}
"metadata": dict(data.items())
}
# get available pype tag if any
_tag = get_track_item_pype_tag(track_item)
@ -301,9 +346,9 @@ def get_track_item_pype_data(track_item):
return None
# get tag metadata attribute
tag_data = tag.metadata()
tag_data = deepcopy(dict(tag.metadata()))
# convert tag metadata to normal keys names and values to correct types
for k, v in dict(tag_data).items():
for k, v in tag_data.items():
key = k.replace("tag.", "")
try:
@ -324,7 +369,7 @@ def get_track_item_pype_data(track_item):
log.warning(msg)
value = v
data.update({key: value})
data[key] = value
return data
@ -497,7 +542,7 @@ class PyblishSubmission(hiero.exporters.FnSubmission.Submission):
from . import publish
# Add submission to Hiero module for retrieval in plugins.
hiero.submission = self
publish()
publish(hiero.ui.mainWindow())
def add_submission():
@ -527,7 +572,7 @@ class PublishAction(QtWidgets.QAction):
# from getting picked up when not using the "Export" dialog.
if hasattr(hiero, "submission"):
del hiero.submission
publish()
publish(hiero.ui.mainWindow())
def eventHandler(self, event):
# Add the Menu to the right-click menu
@ -893,32 +938,32 @@ def apply_colorspace_clips():
def is_overlapping(ti_test, ti_original, strict=False):
covering_exp = bool(
covering_exp = (
(ti_test.timelineIn() <= ti_original.timelineIn())
and (ti_test.timelineOut() >= ti_original.timelineOut())
)
inside_exp = bool(
inside_exp = (
(ti_test.timelineIn() >= ti_original.timelineIn())
and (ti_test.timelineOut() <= ti_original.timelineOut())
)
overlaying_right_exp = bool(
overlaying_right_exp = (
(ti_test.timelineIn() < ti_original.timelineOut())
and (ti_test.timelineOut() >= ti_original.timelineOut())
)
overlaying_left_exp = bool(
overlaying_left_exp = (
(ti_test.timelineOut() > ti_original.timelineIn())
and (ti_test.timelineIn() <= ti_original.timelineIn())
)
if not strict:
if strict:
return covering_exp
else:
return any((
covering_exp,
inside_exp,
overlaying_right_exp,
overlaying_left_exp
))
else:
return covering_exp
def get_sequence_pattern_and_padding(file):
@ -936,17 +981,13 @@ def get_sequence_pattern_and_padding(file):
"""
foundall = re.findall(
r"(#+)|(%\d+d)|(?<=[^a-zA-Z0-9])(\d+)(?=\.\w+$)", file)
if foundall:
found = sorted(list(set(foundall[0])))[-1]
if "%" in found:
padding = int(re.findall(r"\d+", found)[-1])
else:
padding = len(found)
return found, padding
else:
if not foundall:
return None, None
found = sorted(list(set(foundall[0])))[-1]
padding = int(
re.findall(r"\d+", found)[-1]) if "%" in found else len(found)
return found, padding
def sync_clip_name_to_data_asset(track_items_list):
@ -982,7 +1023,7 @@ def sync_clip_name_to_data_asset(track_items_list):
print("asset was changed in clip: {}".format(ti_name))
def check_inventory_versions():
def check_inventory_versions(track_items=None):
"""
Actual version color idetifier of Loaded containers
@ -993,14 +1034,14 @@ def check_inventory_versions():
"""
from . import parse_container
track_item = track_items or get_track_items()
# presets
clip_color_last = "green"
clip_color = "red"
# get all track items from current timeline
for track_item in get_track_items():
for track_item in track_item:
container = parse_container(track_item)
if container:
# get representation from io
representation = legacy_io.find_one({
@ -1038,29 +1079,31 @@ def selection_changed_timeline(event):
timeline_editor = event.sender
selection = timeline_editor.selection()
selection = [ti for ti in selection
if isinstance(ti, hiero.core.TrackItem)]
track_items = get_track_items(
selection=selection,
track_type="video",
check_enabled=True,
check_locked=True,
check_tagged=True
)
# run checking function
sync_clip_name_to_data_asset(selection)
# also mark old versions of loaded containers
check_inventory_versions()
sync_clip_name_to_data_asset(track_items)
def before_project_save(event):
track_items = get_track_items(
selected=False,
track_type="video",
check_enabled=True,
check_locked=True,
check_tagged=True)
check_tagged=True
)
# run checking function
sync_clip_name_to_data_asset(track_items)
# also mark old versions of loaded containers
check_inventory_versions()
check_inventory_versions(track_items)
def get_main_window():

View file

@ -151,7 +151,7 @@ def create_otio_reference(clip):
padding = media_source.filenamePadding()
file_head = media_source.filenameHead()
is_sequence = not media_source.singleFile()
frame_duration = media_source.duration() - 1
frame_duration = media_source.duration()
fps = utils.get_rate(clip) or self.project_fps
extension = os.path.splitext(path)[-1]

View file

@ -143,6 +143,11 @@ def parse_container(track_item, validate=True):
"""
# convert tag metadata to normal keys names
data = lib.get_track_item_pype_data(track_item)
if (
not data
or data.get("id") != "pyblish.avalon.container"
):
return
if validate and data and data.get("schema"):
schema.validate(data)

View file

@ -1,4 +1,5 @@
import os
from pprint import pformat
import re
from copy import deepcopy
@ -400,7 +401,8 @@ class ClipLoader:
# inject asset data to representation dict
self._get_asset_data()
log.debug("__init__ self.data: `{}`".format(self.data))
log.info("__init__ self.data: `{}`".format(pformat(self.data)))
log.info("__init__ options: `{}`".format(pformat(options)))
# add active components to class
if self.new_sequence:
@ -482,7 +484,9 @@ class ClipLoader:
"""
asset_name = self.context["representation"]["context"]["asset"]
self.data["assetData"] = openpype.get_asset(asset_name)["data"]
asset_doc = openpype.get_asset(asset_name)
log.debug("__ asset_doc: {}".format(pformat(asset_doc)))
self.data["assetData"] = asset_doc["data"]
def _make_track_item(self, source_bin_item, audio=False):
""" Create track item with """
@ -500,7 +504,7 @@ class ClipLoader:
track_item.setSource(clip)
track_item.setSourceIn(self.handle_start)
track_item.setTimelineIn(self.timeline_in)
track_item.setSourceOut(self.media_duration - self.handle_end)
track_item.setSourceOut((self.media_duration) - self.handle_end)
track_item.setTimelineOut(self.timeline_out)
track_item.setPlaybackSpeed(1)
self.active_track.addTrackItem(track_item)
@ -520,14 +524,18 @@ class ClipLoader:
self.handle_start = self.data["versionData"].get("handleStart")
self.handle_end = self.data["versionData"].get("handleEnd")
if self.handle_start is None:
self.handle_start = int(self.data["assetData"]["handleStart"])
self.handle_start = self.data["assetData"]["handleStart"]
if self.handle_end is None:
self.handle_end = int(self.data["assetData"]["handleEnd"])
self.handle_end = self.data["assetData"]["handleEnd"]
self.handle_start = int(self.handle_start)
self.handle_end = int(self.handle_end)
if self.sequencial_load:
last_track_item = lib.get_track_items(
sequence_name=self.active_sequence.name(),
track_name=self.active_track.name())
track_name=self.active_track.name()
)
if len(last_track_item) == 0:
last_timeline_out = 0
else:
@ -541,17 +549,12 @@ class ClipLoader:
self.timeline_in = int(self.data["assetData"]["clipIn"])
self.timeline_out = int(self.data["assetData"]["clipOut"])
log.debug("__ self.timeline_in: {}".format(self.timeline_in))
log.debug("__ self.timeline_out: {}".format(self.timeline_out))
# check if slate is included
# either in version data families or by calculating frame diff
slate_on = next(
# check iterate if slate is in families
(f for f in self.context["version"]["data"]["families"]
if "slate" in f),
# if nothing was found then use default None
# so other bool could be used
None) or bool(int(
(self.timeline_out - self.timeline_in + 1)
+ self.handle_start + self.handle_end) < self.media_duration)
slate_on = "slate" in self.context["version"]["data"]["families"]
log.debug("__ slate_on: {}".format(slate_on))
# if slate is on then remove the slate frame from beginning
if slate_on:
@ -572,7 +575,7 @@ class ClipLoader:
# there were some cases were hiero was not creating it
source_bin_item = None
for item in self.active_bin.items():
if self.data["clip_name"] in item.name():
if self.data["clip_name"] == item.name():
source_bin_item = item
if not source_bin_item:
log.warning("Problem with created Source clip: `{}`".format(
@ -599,8 +602,8 @@ class Creator(LegacyCreator):
rename_index = None
def __init__(self, *args, **kwargs):
import openpype.hosts.hiero.api as phiero
super(Creator, self).__init__(*args, **kwargs)
import openpype.hosts.hiero.api as phiero
self.presets = openpype.get_current_project_settings()[
"hiero"]["create"].get(self.__class__.__name__, {})
@ -609,7 +612,10 @@ class Creator(LegacyCreator):
self.sequence = phiero.get_current_sequence()
if (self.options or {}).get("useSelection"):
self.selected = phiero.get_track_items(selected=True)
timeline_selection = phiero.get_timeline_selection()
self.selected = phiero.get_track_items(
selection=timeline_selection
)
else:
self.selected = phiero.get_track_items()
@ -716,6 +722,10 @@ class PublishClip:
else:
self.tag_data.update({"reviewTrack": None})
log.debug("___ self.tag_data: {}".format(
pformat(self.tag_data)
))
# create pype tag on track_item and add data
lib.imprint(self.track_item, self.tag_data)

View file

@ -86,7 +86,7 @@ def update_tag(tag, data):
# due to hiero bug we have to make sure keys which are not existent in
# data are cleared of value by `None`
for _mk in mtd.keys():
for _mk in mtd.dict().keys():
if _mk.replace("tag.", "") not in data_mtd.keys():
mtd.setValue(_mk, str(None))

View file

@ -3,10 +3,6 @@ from openpype.pipeline import (
get_representation_path,
)
import openpype.hosts.hiero.api as phiero
# from openpype.hosts.hiero.api import plugin, lib
# reload(lib)
# reload(plugin)
# reload(phiero)
class LoadClip(phiero.SequenceLoader):
@ -106,7 +102,7 @@ class LoadClip(phiero.SequenceLoader):
name = container['name']
namespace = container['namespace']
track_item = phiero.get_track_items(
track_item_name=namespace)
track_item_name=namespace).pop()
version = legacy_io.find_one({
"type": "version",
"_id": representation["parent"]
@ -157,7 +153,7 @@ class LoadClip(phiero.SequenceLoader):
# load clip to timeline and get main variables
namespace = container['namespace']
track_item = phiero.get_track_items(
track_item_name=namespace)
track_item_name=namespace).pop()
track = track_item.parent()
# remove track item from track

View file

@ -19,9 +19,12 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
def process(self, context):
self.otio_timeline = context.data["otioTimeline"]
timeline_selection = phiero.get_timeline_selection()
selected_timeline_items = phiero.get_track_items(
selected=True, check_tagged=True, check_enabled=True)
selection=timeline_selection,
check_tagged=True,
check_enabled=True
)
# only return enabled track items
if not selected_timeline_items:
@ -292,9 +295,9 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
for otio_clip in self.otio_timeline.each_clip():
track_name = otio_clip.parent().name
parent_range = otio_clip.range_in_parent()
if ti_track_name not in track_name:
if ti_track_name != track_name:
continue
if otio_clip.name not in track_item.name():
if otio_clip.name != track_item.name():
continue
self.log.debug("__ parent_range: {}".format(parent_range))
self.log.debug("__ timeline_range: {}".format(timeline_range))
@ -314,7 +317,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
speed = track_item.playbackSpeed()
timeline = phiero.get_current_sequence()
frame_start = int(track_item.timelineIn())
frame_duration = int(track_item.sourceDuration() / speed)
frame_duration = int((track_item.duration() - 1) / speed)
fps = timeline.framerate().toFloat()
return hiero_export.create_otio_time_range(

View file

@ -16,7 +16,7 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin):
"""Inject the current working file into context"""
label = "Precollect Workfile"
order = pyblish.api.CollectorOrder - 0.5
order = pyblish.api.CollectorOrder - 0.491
def process(self, context):
@ -84,6 +84,7 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin):
"colorspace": self.get_colorspace(project),
"fps": fps
}
self.log.debug("__ context_data: {}".format(pformat(context_data)))
context.data.update(context_data)
self.log.info("Creating instance: {}".format(instance))

View file

@ -168,7 +168,7 @@ def make_sequence_collection(path, otio_range, metadata):
first, last = otio_range_to_frame_range(otio_range)
collection = clique.Collection(
head=head, tail=tail, padding=metadata["padding"])
collection.indexes.update([i for i in range(first, (last + 1))])
collection.indexes.update([i for i in range(first, last)])
return dir_path, collection
@ -218,6 +218,7 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end):
"name": name
}
tw_node.update(metadata)
tw_node["lookup"] = list(lookup)
# get first and last frame offsets
offset_in += lookup[0]
@ -254,7 +255,7 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end):
media_in + source_in + offset_in)
media_out_trimmed = (
media_in + source_in + (
(source_range.duration.value * abs(
((source_range.duration.value - 1) * abs(
time_scalar)) + offset_out))
# calculate available handles

View file

@ -62,7 +62,7 @@ class CollectHierarchy(pyblish.api.ContextPlugin):
"frameEnd": instance.data["frameEnd"],
"clipIn": instance.data["clipIn"],
"clipOut": instance.data["clipOut"],
'fps': instance.context.data["fps"],
"fps": instance.data["fps"],
"resolutionWidth": instance.data["resolutionWidth"],
"resolutionHeight": instance.data["resolutionHeight"],
"pixelAspect": instance.data["pixelAspect"]

View file

@ -55,13 +55,13 @@ class CollectOtioFrameRanges(pyblish.api.InstancePlugin):
"frameStart": frame_start,
"frameEnd": frame_end,
"clipIn": tl_start,
"clipOut": tl_end,
"clipOut": tl_end - 1,
"clipInH": tl_start_h,
"clipOutH": tl_end_h,
"clipOutH": tl_end_h - 1,
"sourceStart": src_starting_from + src_start,
"sourceEnd": src_starting_from + src_end,
"sourceEnd": src_starting_from + src_end - 1,
"sourceStartH": src_starting_from + src_start_h,
"sourceEndH": src_starting_from + src_end_h,
"sourceEndH": src_starting_from + src_end_h - 1,
}
instance.data.update(data)
self.log.debug(

View file

@ -66,7 +66,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin):
# create trimmed otio time range
trimmed_media_range_h = editorial.range_from_frames(
a_frame_start_h, (a_frame_end_h - a_frame_start_h + 1),
a_frame_start_h, (a_frame_end_h - a_frame_start_h) + 1,
media_fps
)
trimmed_duration = trimmed_media_range_h.duration.value

View file

@ -80,7 +80,7 @@ class ExtractOTIOTrimmingVideo(openpype.api.Extractor):
video_path = input_file_path
frame_start = otio_range.start_time.value
input_fps = otio_range.start_time.rate
frame_duration = (otio_range.duration.value + 1)
frame_duration = otio_range.duration.value - 1
sec_start = openpype.lib.frames_to_secons(frame_start, input_fps)
sec_duration = openpype.lib.frames_to_secons(frame_duration, input_fps)

View file

@ -113,7 +113,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
"usdOverride",
"simpleUnrealTexture"
]
exclude_families = ["clip", "render.farm"]
exclude_families = ["render.farm"]
db_representation_context_keys = [
"project", "asset", "task", "subset", "version", "representation",
"family", "hierarchy", "task", "username"