diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/lib.py index 6b44f97172..5f186b7a98 100644 --- a/pype/hosts/resolve/lib.py +++ b/pype/hosts/resolve/lib.py @@ -665,9 +665,11 @@ def get_otio_clip_instance_data(track_item_data): track_item = track_item_data["clip"]["item"] project = track_item_data["project"] + timeline = track_item_data["sequence"] + timeline_start = timeline.GetStartFrame() - frame_start = track_item.GetStart() - frame_duration = track_item.GetDuration() + frame_start = int(track_item.GetStart() - timeline_start) + frame_duration = int(track_item.GetDuration()) self.project_fps = project.GetSetting("timelineFrameRate") otio_clip_range = otio_export.create_otio_time_range( diff --git a/pype/hosts/resolve/otio/davinci_export.py b/pype/hosts/resolve/otio/davinci_export.py index 25c578e0a7..cffb58f960 100644 --- a/pype/hosts/resolve/otio/davinci_export.py +++ b/pype/hosts/resolve/otio/davinci_export.py @@ -26,9 +26,17 @@ def create_otio_time_range(start_frame, frame_duration, fps): def create_otio_reference(media_pool_item): + metadata = dict() mp_clip_property = media_pool_item.GetClipProperty() path = mp_clip_property["File Path"] reformat_path = utils.get_reformated_path(path, padded=False) + padding = utils.get_padding_from_path(path) + + if padding: + metadata.update({ + "isSequence": True, + "padding": padding + }) # get clip property regarding to type mp_clip_property = media_pool_item.GetClipProperty() @@ -42,7 +50,7 @@ def create_otio_reference(media_pool_item): frame_duration = int(utils.timecode_to_frames( audio_duration, float(fps))) - return otio.schema.ExternalReference( + otio_ex_ref_item = otio.schema.ExternalReference( target_url=reformat_path, available_range=create_otio_time_range( frame_start, @@ -51,6 +59,11 @@ def create_otio_reference(media_pool_item): ) ) + # add metadata to otio item + add_otio_metadata(otio_ex_ref_item, media_pool_item, **metadata) + + return otio_ex_ref_item + def create_otio_markers(track_item, fps): track_item_markers = track_item.GetMarkers() @@ -85,7 +98,7 @@ def create_otio_clip(track_item): else: fps = self.project_fps - name = utils.get_reformated_path(track_item.GetName()) + name = track_item.GetName() media_reference = create_otio_reference(media_pool_item) source_range = create_otio_time_range( @@ -160,6 +173,17 @@ def add_otio_gap(clip_start, otio_track, track_item, timeline): ) +def add_otio_metadata(otio_item, media_pool_item, **kwargs): + mp_metadata = media_pool_item.GetMetadata() + # add additional metadata from kwargs + if kwargs: + mp_metadata.update(kwargs) + + # add metadata to otio item metadata + for key, value in mp_metadata.items(): + otio_item.metadata.update({key: value}) + + def create_otio_timeline(timeline, fps): # get current timeline self.project_fps = fps diff --git a/pype/hosts/resolve/otio/utils.py b/pype/hosts/resolve/otio/utils.py index 22619d4172..88e0b3d3b4 100644 --- a/pype/hosts/resolve/otio/utils.py +++ b/pype/hosts/resolve/otio/utils.py @@ -35,3 +35,24 @@ def get_reformated_path(path, padded=True): else: path = re.sub(num_pattern, f"%d", path) return path + + +def get_padding_from_path(path): + """ + Return padding number from DaVinci Resolve sequence path style + + Args: + path (str): path url or simple file name + + Returns: + int: padding number + + Example: + get_padding_from_path("plate.[0001-1008].exr") > 4 + + """ + padding_pattern = "(\\d+)(?=-)" + if "[" in path: + return len(re.findall(padding_pattern, path).pop()) + + return None diff --git a/pype/lib/__init__.py b/pype/lib/__init__.py index 188dd68039..8cc0384032 100644 --- a/pype/lib/__init__.py +++ b/pype/lib/__init__.py @@ -46,6 +46,11 @@ from .ffmpeg_utils import ( ffprobe_streams ) +from .editorial import ( + is_overlapping, + convert_otio_range_to_frame_range +) + __all__ = [ "get_avalon_database", "set_io_database", @@ -81,5 +86,8 @@ __all__ = [ "get_ffmpeg_tool_path", "source_hash", - "_subprocess" + "_subprocess", + + "is_overlapping", + "convert_otio_range_to_frame_range" ] diff --git a/pype/lib/editorial.py b/pype/lib/editorial.py new file mode 100644 index 0000000000..41a92165c3 --- /dev/null +++ b/pype/lib/editorial.py @@ -0,0 +1,36 @@ +from opentimelineio.opentime import to_frames + + +def convert_otio_range_to_frame_range(otio_range): + start = to_frames( + otio_range.start_time, otio_range.start_time.rate) + end = start + to_frames( + otio_range.duration, otio_range.duration.rate) - 1 + return start, end + + +def is_overlapping(test_range, main_range, strict=False): + test_start, test_end = convert_otio_range_to_frame_range(test_range) + main_start, main_end = convert_otio_range_to_frame_range(main_range) + covering_exp = bool( + (test_start <= main_start) and (test_end >= main_end) + ) + inside_exp = bool( + (test_start >= main_start) and (test_end <= main_end) + ) + overlaying_right_exp = bool( + (test_start < main_end) and (test_end >= main_end) + ) + overlaying_left_exp = bool( + (test_end > main_start) and (test_start <= main_start) + ) + + if not strict: + return any(( + covering_exp, + inside_exp, + overlaying_right_exp, + overlaying_left_exp + )) + else: + return covering_exp diff --git a/pype/plugins/global/publish/collect_otio_review.py b/pype/plugins/global/publish/collect_otio_review.py index a7097b84d0..9daea4d30f 100644 --- a/pype/plugins/global/publish/collect_otio_review.py +++ b/pype/plugins/global/publish/collect_otio_review.py @@ -3,12 +3,14 @@ Requires: otioTimeline -> context data attribute review -> instance data attribute masterLayer -> instance data attribute - otioClip -> instance data attribute otioClipRange -> instance data attribute """ import opentimelineio as otio -from opentimelineio.opentime import to_frames import pyblish.api +from pype.lib import ( + is_overlapping, + convert_otio_range_to_frame_range +) class CollectOcioReview(pyblish.api.InstancePlugin): @@ -23,64 +25,36 @@ class CollectOcioReview(pyblish.api.InstancePlugin): # get basic variables review_track_name = instance.data["review"] master_layer = instance.data["masterLayer"] - otio_timeline_context = instance.context.data.get("otioTimeline") - otio_clip = instance.data["otioClip"] + otio_timeline_context = instance.context.data["otioTimeline"] otio_clip_range = instance.data["otioClipRange"] # skip if master layer is False if not master_layer: return - # get timeline time values - start_time = otio_timeline_context.global_start_time - timeline_fps = start_time.rate - playhead = start_time.value - - frame_start = to_frames( - otio_clip_range.start_time, timeline_fps) - frame_duration = to_frames( - otio_clip_range.duration, timeline_fps) - self.log.debug( - ("name: {} | " - "timeline_in: {} | timeline_out: {}").format( - otio_clip.name, frame_start, - (frame_start + frame_duration - 1))) - - orwc_fps = timeline_fps for otio_clip in otio_timeline_context.each_clip(): track_name = otio_clip.parent().name + parent_range = otio_clip.range_in_parent() if track_name not in review_track_name: continue if isinstance(otio_clip, otio.schema.Clip): - orwc_source_range = otio_clip.source_range - orwc_fps = orwc_source_range.start_time.rate - orwc_start = to_frames(orwc_source_range.start_time, orwc_fps) - orwc_duration = to_frames(orwc_source_range.duration, orwc_fps) - source_in = orwc_start - source_out = (orwc_start + orwc_duration) - 1 - timeline_in = playhead - timeline_out = (timeline_in + orwc_duration) - 1 - self.log.debug( - ("name: {} | source_in: {} | source_out: {} | " - "timeline_in: {} | timeline_out: {} " - "| orwc_fps: {}").format( - otio_clip.name, source_in, source_out, - timeline_in, timeline_out, orwc_fps)) + if is_overlapping(parent_range, otio_clip_range, strict=False): + self.create_representation( + otio_clip, otio_clip_range, instance) - # move plyhead to next available frame - playhead = timeline_out + 1 - - elif isinstance(otio_clip, otio.schema.Gap): - gap_source_range = otio_clip.source_range - gap_fps = gap_source_range.start_time.rate - gap_start = to_frames( - gap_source_range.start_time, gap_fps) - gap_duration = to_frames( - gap_source_range.duration, gap_fps) - if gap_fps != orwc_fps: - gap_duration += 1 - self.log.debug( - ("name: Gap | gap_start: {} | gap_fps: {}" - "| gap_duration: {} | timeline_fps: {}").format( - gap_start, gap_fps, gap_duration, timeline_fps)) - playhead += gap_duration + def create_representation(self, otio_clip, to_otio_range, instance): + to_timeline_start, to_timeline_end = convert_otio_range_to_frame_range( + to_otio_range) + timeline_start, timeline_end = convert_otio_range_to_frame_range( + otio_clip.range_in_parent()) + source_start, source_end = convert_otio_range_to_frame_range( + otio_clip.source_range) + media_reference = otio_clip.media_reference + available_start, available_end = convert_otio_range_to_frame_range( + media_reference.available_range) + path = media_reference.target_url + self.log.debug(path) + self.log.debug((available_start, available_end)) + self.log.debug((source_start, source_end)) + self.log.debug((timeline_start, timeline_end)) + self.log.debug((to_timeline_start, to_timeline_end))