From 2db6bb534b1a04a5762a6d201c9d003a62ec2c17 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 8 Apr 2021 20:57:47 +0200 Subject: [PATCH] Hiero: final changes to otio import export modules --- openpype/hosts/hiero/otio/hiero_export.py | 65 ++- openpype/hosts/hiero/otio/hiero_export__.py | 386 ------------------ openpype/hosts/hiero/otio/hiero_import.py | 25 +- openpype/hosts/hiero/otio/utils.py | 21 +- .../Startup/otioexporter/OTIOExportTask.py | 2 +- .../Startup/otioexporter/OTIOExportUI.py | 5 +- .../Python/StartupUI/otioimporter/__init__.py | 2 +- 7 files changed, 58 insertions(+), 448 deletions(-) delete mode 100644 openpype/hosts/hiero/otio/hiero_export__.py diff --git a/openpype/hosts/hiero/otio/hiero_export.py b/openpype/hosts/hiero/otio/hiero_export.py index 65c4ae13e1..b2847ff6cb 100644 --- a/openpype/hosts/hiero/otio/hiero_export.py +++ b/openpype/hosts/hiero/otio/hiero_export.py @@ -4,10 +4,12 @@ import os import re import sys +import ast import opentimelineio as otio from . import utils import hiero.core import hiero.ui +reload(utils) self = sys.modules[__name__] self.track_types = { @@ -43,7 +45,7 @@ def create_otio_time_range(start_frame, frame_duration, fps): def _get_metadata(item): if hasattr(item, 'metadata'): - return {key: value for key, value in item.metadata().items()} + return {key: value for key, value in dict(item.metadata()).items()} return {} @@ -61,7 +63,7 @@ def create_otio_reference(clip): file_head = media_source.filenameHead() is_sequence = not media_source.singleFile() frame_duration = media_source.duration() - fps = utils.get_rate(clip) + fps = utils.get_rate(clip) or self.project_fps extension = os.path.splitext(path)[-1] if is_sequence: @@ -70,6 +72,13 @@ def create_otio_reference(clip): "padding": padding }) + # add resolution metadata + metadata.update({ + "width": int(media_source.width()), + "height": int(media_source.height()), + "pixelAspect": float(media_source.pixelAspect()) + }) + otio_ex_ref_item = None if is_sequence: @@ -133,7 +142,7 @@ def create_otio_markers(otio_item, item): # Hiero adds this tag to a lot of clips continue - frame_rate = utils.get_rate(item) + frame_rate = utils.get_rate(item) or self.project_fps marked_range = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( @@ -145,12 +154,22 @@ def create_otio_markers(otio_item, item): frame_rate ) ) + # add tag metadata but remove "tag." string + metadata = {} + + for key, value in tag.metadata().dict().items(): + _key = key.replace("tag.", "") + + try: + # capture exceptions which are related to strings only + _value = ast.literal_eval(value) + except (ValueError, SyntaxError): + _value = value + + metadata.update({_key: _value}) - metadata = dict( - Hiero=tag.metadata().dict() - ) # Store the source item for future import assignment - metadata['Hiero']['source_type'] = item.__class__.__name__ + metadata['hiero_source_type'] = item.__class__.__name__ marker = otio.schema.Marker( name=tag.name(), @@ -166,7 +185,7 @@ def create_otio_clip(track_item): clip = track_item.source() source_in = track_item.sourceIn() duration = track_item.sourceDuration() - fps = utils.get_rate(track_item) + fps = utils.get_rate(track_item) or self.project_fps name = track_item.name() media_reference = create_otio_reference(clip) @@ -212,29 +231,6 @@ def _create_otio_timeline(): ) -def _get_metadata_media_pool_item(media_pool_item): - data = dict() - data.update({k: v for k, v in media_pool_item.GetMetadata().items()}) - property = media_pool_item.GetClipProperty() or {} - for name, value in property.items(): - if "Resolution" in name and "" != value: - width, height = value.split("x") - data.update({ - "width": int(width), - "height": int(height) - }) - if "PAR" in name and "" != value: - try: - data.update({"pixelAspect": float(value)}) - except ValueError: - if "Square" in value: - data.update({"pixelAspect": float(1)}) - else: - data.update({"pixelAspect": float(1)}) - - return data - - def create_otio_track(track_type, track_name): return otio.schema.Track( name=track_name, @@ -271,6 +267,7 @@ def add_otio_metadata(otio_item, media_source, **kwargs): def create_otio_timeline(): + print(">>>>>> self.include_tags: {}".format(self.include_tags)) # get current timeline self.timeline = hiero.ui.activeSequence() self.project_fps = self.timeline.framerate().toFloat() @@ -278,12 +275,8 @@ def create_otio_timeline(): # convert timeline to otio otio_timeline = _create_otio_timeline() - # Add tags as markers - if self.include_tags: - create_otio_markers(otio_timeline, self.timeline) - # loop all defined track types - for track in self.hiero_sequence.items(): + for track in self.timeline.items(): # skip if track is disabled if not track.isEnabled(): continue diff --git a/openpype/hosts/hiero/otio/hiero_export__.py b/openpype/hosts/hiero/otio/hiero_export__.py deleted file mode 100644 index 8e19b26741..0000000000 --- a/openpype/hosts/hiero/otio/hiero_export__.py +++ /dev/null @@ -1,386 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -__author__ = "Daniel Flehner Heen" -__credits__ = ["Jakub Jezek", "Daniel Flehner Heen"] - -import os -import sys -import re -import hiero.core -import hiero.ui -import opentimelineio as otio - - -# build modul class -self = sys.modules[__name__] - -self.marker_color_map = { - "magenta": otio.schema.MarkerColor.MAGENTA, - "red": otio.schema.MarkerColor.RED, - "yellow": otio.schema.MarkerColor.YELLOW, - "green": otio.schema.MarkerColor.GREEN, - "cyan": otio.schema.MarkerColor.CYAN, - "blue": otio.schema.MarkerColor.BLUE, -} -self.hiero_sequence = None -self.include_tags = None - - -def get_rate(item): - if not hasattr(item, 'framerate'): - item = item.sequence() - - num, den = item.framerate().toRational() - rate = float(num) / float(den) - - if rate.is_integer(): - return rate - - return round(rate, 2) - - -def get_clip_ranges(trackitem): - # Get rate from source or sequence - if trackitem.source().mediaSource().hasVideo(): - rate_item = trackitem.source() - - else: - rate_item = trackitem.sequence() - - source_rate = get_rate(rate_item) - - # Reversed video/audio - if trackitem.playbackSpeed() < 0: - start = trackitem.sourceOut() - - else: - start = trackitem.sourceIn() - - source_start_time = otio.opentime.RationalTime( - start, - source_rate - ) - source_duration = otio.opentime.RationalTime( - trackitem.duration(), - source_rate - ) - - source_range = otio.opentime.TimeRange( - start_time=source_start_time, - duration=source_duration - ) - - hiero_clip = trackitem.source() - - available_range = None - if hiero_clip.mediaSource().isMediaPresent(): - start_time = otio.opentime.RationalTime( - hiero_clip.mediaSource().startTime(), - source_rate - ) - duration = otio.opentime.RationalTime( - hiero_clip.mediaSource().duration(), - source_rate - ) - available_range = otio.opentime.TimeRange( - start_time=start_time, - duration=duration - ) - - return source_range, available_range - - -def add_gap(trackitem, otio_track, prev_out): - gap_length = trackitem.timelineIn() - prev_out - if prev_out != 0: - gap_length -= 1 - - rate = get_rate(trackitem.sequence()) - gap = otio.opentime.TimeRange( - duration=otio.opentime.RationalTime( - gap_length, - rate - ) - ) - otio_gap = otio.schema.Gap(source_range=gap) - otio_track.append(otio_gap) - - -def get_marker_color(tag): - icon = tag.icon() - pat = r'icons:Tag(?P\w+)\.\w+' - - res = re.search(pat, icon) - if res: - color = res.groupdict().get('color') - if color.lower() in self.marker_color_map: - return self.marker_color_map[color.lower()] - - return otio.schema.MarkerColor.RED - - -def add_markers(hiero_item, otio_item): - for tag in hiero_item.tags(): - if not tag.visible(): - continue - - if tag.name() == 'Copy': - # Hiero adds this tag to a lot of clips - continue - - frame_rate = get_rate(hiero_item) - - marked_range = otio.opentime.TimeRange( - start_time=otio.opentime.RationalTime( - tag.inTime(), - frame_rate - ), - duration=otio.opentime.RationalTime( - int(tag.metadata().dict().get('tag.length', '0')), - frame_rate - ) - ) - - metadata = dict( - Hiero=tag.metadata().dict() - ) - # Store the source item for future import assignment - metadata['Hiero']['source_type'] = hiero_item.__class__.__name__ - - marker = otio.schema.Marker( - name=tag.name(), - color=get_marker_color(tag), - marked_range=marked_range, - metadata=metadata - ) - - otio_item.markers.append(marker) - - -def add_clip(trackitem, otio_track, itemindex): - hiero_clip = trackitem.source() - - # Add Gap if needed - if itemindex == 0: - prev_item = trackitem - - else: - prev_item = trackitem.parent().items()[itemindex - 1] - - clip_diff = trackitem.timelineIn() - prev_item.timelineOut() - - if itemindex == 0 and trackitem.timelineIn() > 0: - add_gap(trackitem, otio_track, 0) - - elif itemindex and clip_diff != 1: - add_gap(trackitem, otio_track, prev_item.timelineOut()) - - # Create Clip - source_range, available_range = get_clip_ranges(trackitem) - - otio_clip = otio.schema.Clip( - name=trackitem.name(), - source_range=source_range - ) - - media_reference = create_otio_reference(hiero_clip) - - otio_clip.media_reference = media_reference - - # Add Time Effects - playbackspeed = trackitem.playbackSpeed() - if playbackspeed != 1: - if playbackspeed == 0: - time_effect = otio.schema.FreezeFrame() - - else: - time_effect = otio.schema.LinearTimeWarp( - time_scalar=playbackspeed - ) - otio_clip.effects.append(time_effect) - - # Add tags as markers - if self.include_tags: - add_markers(trackitem, otio_clip) - add_markers(trackitem.source(), otio_clip) - - otio_track.append(otio_clip) - - # Add Transition if needed - if trackitem.inTransition() or trackitem.outTransition(): - add_transition(trackitem, otio_track) - -def _get_metadata(hiero_object): - metadata = hiero_object.metadata() - return {key: value for key, value in metadata.items()} - -def create_otio_reference(hiero_clip): - metadata = _get_metadata(hiero_clip) - mp_clip_property = media_pool_item.GetClipProperty() - path = mp_clip_property["File Path"] - reformat_path = utils.get_reformated_path(path, padded=True) - 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() - fps = float(mp_clip_property["FPS"]) - if mp_clip_property["Type"] == "Video": - frame_start = int(mp_clip_property["Start"]) - frame_duration = int(mp_clip_property["Frames"]) - else: - audio_duration = str(mp_clip_property["Duration"]) - frame_start = 0 - frame_duration = int(utils.timecode_to_frames( - audio_duration, float(fps))) - - otio_ex_ref_item = None - - if padding: - # if it is file sequence try to create `ImageSequenceReference` - # the OTIO might not be compatible so return nothing and do it old way - try: - dirname, filename = os.path.split(path) - collection = clique.parse(filename, '{head}[{ranges}]{tail}') - padding_num = len(re.findall("(\\d+)(?=-)", filename).pop()) - otio_ex_ref_item = otio.schema.ImageSequenceReference( - target_url_base=dirname + os.sep, - name_prefix=collection.format("{head}"), - name_suffix=collection.format("{tail}"), - start_frame=frame_start, - frame_zero_padding=padding_num, - rate=fps, - available_range=create_otio_time_range( - frame_start, - frame_duration, - fps - ) - ) - except AttributeError: - pass - - if not otio_ex_ref_item: - # in case old OTIO or video file create `ExternalReference` - otio_ex_ref_item = otio.schema.ExternalReference( - target_url=reformat_path, - available_range=create_otio_time_range( - frame_start, - frame_duration, - fps - ) - ) - - # add metadata to otio item - add_otio_metadata(otio_ex_ref_item, hiero_clip, **metadata) - - return otio_ex_ref_item - - -def add_otio_metadata(otio_item, hiero_clip, **kwargs): - mp_metadata = hiero_clip.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 add_transition(trackitem, otio_track): - transitions = [] - - if trackitem.inTransition(): - if trackitem.inTransition().alignment().name == 'kFadeIn': - transitions.append(trackitem.inTransition()) - - if trackitem.outTransition(): - transitions.append(trackitem.outTransition()) - - for transition in transitions: - alignment = transition.alignment().name - - if alignment == 'kFadeIn': - in_offset_frames = 0 - out_offset_frames = ( - transition.timelineOut() - transition.timelineIn() - ) + 1 - - elif alignment == 'kFadeOut': - in_offset_frames = ( - trackitem.timelineOut() - transition.timelineIn() - ) + 1 - out_offset_frames = 0 - - elif alignment == 'kDissolve': - in_offset_frames = ( - transition.inTrackItem().timelineOut() - - transition.timelineIn() - ) - out_offset_frames = ( - transition.timelineOut() - - transition.outTrackItem().timelineIn() - ) - - else: - # kUnknown transition is ignored - continue - - rate = trackitem.source().framerate().toFloat() - in_time = otio.opentime.RationalTime(in_offset_frames, rate) - out_time = otio.opentime.RationalTime(out_offset_frames, rate) - - otio_transition = otio.schema.Transition( - name=alignment, # Consider placing Hiero name in metadata - transition_type=otio.schema.TransitionTypes.SMPTE_Dissolve, - in_offset=in_time, - out_offset=out_time - ) - - if alignment == 'kFadeIn': - otio_track.insert(-1, otio_transition) - - else: - otio_track.append(otio_transition) - - -def add_tracks(): - for track in self.hiero_sequence.items(): - if isinstance(track, hiero.core.AudioTrack): - kind = otio.schema.TrackKind.Audio - - else: - kind = otio.schema.TrackKind.Video - - otio_track = otio.schema.Track(name=track.name(), kind=kind) - - for itemindex, trackitem in enumerate(track): - if isinstance(trackitem.source(), hiero.core.Clip): - add_clip(trackitem, otio_track, itemindex) - - self.otio_timeline.tracks.append(otio_track) - - # Add tags as markers - if self.include_tags: - add_markers(self.hiero_sequence, self.otio_timeline.tracks) - - -def create_OTIO(sequence=None): - self.hiero_sequence = sequence or hiero.ui.activeSequence() - self.otio_timeline = otio.schema.Timeline() - - # Set global start time based on sequence - self.otio_timeline.global_start_time = otio.opentime.RationalTime( - self.hiero_sequence.timecodeStart(), - self.hiero_sequence.framerate().toFloat() - ) - self.otio_timeline.name = self.hiero_sequence.name() - - add_tracks() - - return self.otio_timeline diff --git a/openpype/hosts/hiero/otio/hiero_import.py b/openpype/hosts/hiero/otio/hiero_import.py index c5c72984bc..db9ebdfc90 100644 --- a/openpype/hosts/hiero/otio/hiero_import.py +++ b/openpype/hosts/hiero/otio/hiero_import.py @@ -19,6 +19,7 @@ except ImportError: import opentimelineio as otio +_otio_old = False def inform(messages): if isinstance(messages, type('')): @@ -180,14 +181,23 @@ def prep_url(url_in): def create_offline_mediasource(otio_clip, path=None): + global _otio_old + hiero_rate = hiero.core.TimeBase( otio_clip.source_range.start_time.rate ) - legal_media_refs = ( - otio.schema.ExternalReference, - otio.schema.ImageSequenceReference - ) + try: + legal_media_refs = ( + otio.schema.ExternalReference, + otio.schema.ImageSequenceReference + ) + except AttributeError: + _otio_old = True + legal_media_refs = ( + otio.schema.ExternalReference + ) + if isinstance(otio_clip.media_reference, legal_media_refs): source_range = otio_clip.available_range() @@ -331,9 +341,10 @@ def create_clip(otio_clip, tagsbin, sequencebin): url = prep_url(otio_media.target_url) media = hiero.core.MediaSource(url) - elif isinstance(otio_media, otio.schema.ImageSequenceReference): - url = prep_url(otio_media.abstract_target_url('#')) - media = hiero.core.MediaSource(url) + elif not _otio_old: + if isinstance(otio_media, otio.schema.ImageSequenceReference): + url = prep_url(otio_media.abstract_target_url('#')) + media = hiero.core.MediaSource(url) if media is None or media.isOffline(): media = create_offline_mediasource(otio_clip, url) diff --git a/openpype/hosts/hiero/otio/utils.py b/openpype/hosts/hiero/otio/utils.py index 12f963fe97..f882a5d1f2 100644 --- a/openpype/hosts/hiero/otio/utils.py +++ b/openpype/hosts/hiero/otio/utils.py @@ -17,7 +17,7 @@ def frames_to_secons(frames, framerate): return otio.opentime.to_seconds(rt) -def get_reformated_path(path, padded=True, first=False): +def get_reformated_path(path, padded=True): """ Return fixed python expression path @@ -31,19 +31,12 @@ def get_reformated_path(path, padded=True, first=False): get_reformated_path("plate.[0001-1008].exr") > plate.%04d.exr """ - num_pattern = r"(\[\d+\-\d+\])" - padding_pattern = r"(\d+)(?=-)" - first_frame_pattern = re.compile(r"\[(\d+)\-\d+\]") - - if "[" in path: - padding = len(re.findall(padding_pattern, path).pop()) + if "%" in path: + padding_pattern = r"(\d+)" + padding = int(re.findall(padding_pattern, path).pop()) + num_pattern = r"(%\d+d)" if padded: - path = re.sub(num_pattern, f"%0{padding}d", path) - elif first: - first_frame = re.findall(first_frame_pattern, path, flags=0) - if len(first_frame) >= 1: - first_frame = first_frame[0] - path = re.sub(num_pattern, first_frame, path) + path = re.sub(num_pattern, "%0{}d".format(padding), path) else: path = re.sub(num_pattern, "%d", path) return path @@ -72,7 +65,7 @@ def get_padding_from_path(path): def get_rate(item): if not hasattr(item, 'framerate'): - item = item.sequence() + return None num, den = item.framerate().toRational() rate = float(num) / float(den) diff --git a/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py index 51265a3daf..7e1a8df2dc 100644 --- a/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py +++ b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py @@ -60,7 +60,7 @@ class OTIOExportPreset(hiero.core.TaskPresetBase): """Initialise presets to default values""" hiero.core.TaskPresetBase.__init__(self, OTIOExportTask, name) - self.properties()["includeTags"] = True + self.properties()["includeTags"] = hiero_export.include_tags = True self.properties().update(properties) def supportedItems(self): diff --git a/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportUI.py b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportUI.py index 7f11de074d..9b83eefedf 100644 --- a/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportUI.py +++ b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportUI.py @@ -20,8 +20,7 @@ except ImportError: FormLayout = QFormLayout # lint:ok -from pype.hosts.hiero.otio import hiero_export - +from openpype.hosts.hiero.otio import hiero_export class OTIOExportUI(hiero.ui.TaskUIBase): def __init__(self, preset): @@ -35,7 +34,7 @@ class OTIOExportUI(hiero.ui.TaskUIBase): def includeMarkersCheckboxChanged(self, state): # Slot to handle change of checkbox state - hiero_export.hiero_sequence = state == QtCore.Qt.Checked + hiero_export.include_tags = state == QtCore.Qt.Checked def populateUI(self, widget, exportTemplate): layout = widget.layout() diff --git a/openpype/hosts/hiero/startup/Python/StartupUI/otioimporter/__init__.py b/openpype/hosts/hiero/startup/Python/StartupUI/otioimporter/__init__.py index a778d558b2..0f0a643909 100644 --- a/openpype/hosts/hiero/startup/Python/StartupUI/otioimporter/__init__.py +++ b/openpype/hosts/hiero/startup/Python/StartupUI/otioimporter/__init__.py @@ -9,7 +9,7 @@ import hiero.core import PySide2.QtWidgets as qw -from pype.hosts.hiero.otio.hiero_import import load_otio +from openpype.hosts.hiero.otio.hiero_import import load_otio class OTIOProjectSelect(qw.QDialog):