From 07771a40521a63111b2e69ebe12e9686a0a443d7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Oct 2021 15:53:23 +0200 Subject: [PATCH 01/21] Flame: starting with otio export modul --- openpype/hosts/flame/otio/__init__.py | 0 openpype/hosts/flame/otio/flame_export.py | 448 ++++++++++++++++++++++ openpype/hosts/flame/otio/utils.py | 80 ++++ 3 files changed, 528 insertions(+) create mode 100644 openpype/hosts/flame/otio/__init__.py create mode 100644 openpype/hosts/flame/otio/flame_export.py create mode 100644 openpype/hosts/flame/otio/utils.py diff --git a/openpype/hosts/flame/otio/__init__.py b/openpype/hosts/flame/otio/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py new file mode 100644 index 0000000000..af4322e3d9 --- /dev/null +++ b/openpype/hosts/flame/otio/flame_export.py @@ -0,0 +1,448 @@ +""" compatibility OpenTimelineIO 0.12.0 and newer +""" + +import os +import re +import sys +import ast +from compiler.ast import flatten +import opentimelineio as otio +from . import utils +import hiero.core +import hiero.ui + +self = sys.modules[__name__] +self.track_types = { + hiero.core.VideoTrack: otio.schema.TrackKind.Video, + hiero.core.AudioTrack: otio.schema.TrackKind.Audio +} +self.project_fps = None +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.timeline = None +self.include_tags = True + + +def get_current_hiero_project(remove_untitled=False): + projects = flatten(hiero.core.projects()) + if not remove_untitled: + return next(iter(projects)) + + # if remove_untitled + for proj in projects: + if "Untitled" in proj.name(): + proj.close() + else: + return proj + + +def create_otio_rational_time(frame, fps): + return otio.opentime.RationalTime( + float(frame), + float(fps) + ) + + +def create_otio_time_range(start_frame, frame_duration, fps): + return otio.opentime.TimeRange( + start_time=create_otio_rational_time(start_frame, fps), + duration=create_otio_rational_time(frame_duration, fps) + ) + + +def _get_metadata(item): + if hasattr(item, 'metadata'): + return {key: value for key, value in dict(item.metadata()).items()} + return {} + + +def create_time_effects(otio_clip, track_item): + # get all subtrack items + subTrackItems = flatten(track_item.parent().subTrackItems()) + speed = track_item.playbackSpeed() + + otio_effect = None + # retime on track item + if speed != 1.: + # make effect + otio_effect = otio.schema.LinearTimeWarp() + otio_effect.name = "Speed" + otio_effect.time_scalar = speed + otio_effect.metadata = {} + + # freeze frame effect + if speed == 0.: + otio_effect = otio.schema.FreezeFrame() + otio_effect.name = "FreezeFrame" + otio_effect.metadata = {} + + if otio_effect: + # add otio effect to clip effects + otio_clip.effects.append(otio_effect) + + # loop trought and get all Timewarps + for effect in subTrackItems: + if ((track_item not in effect.linkedItems()) + and (len(effect.linkedItems()) > 0)): + continue + # avoid all effect which are not TimeWarp and disabled + if "TimeWarp" not in effect.name(): + continue + + if not effect.isEnabled(): + continue + + node = effect.node() + name = node["name"].value() + + # solve effect class as effect name + _name = effect.name() + if "_" in _name: + effect_name = re.sub(r"(?:_)[_0-9]+", "", _name) # more numbers + else: + effect_name = re.sub(r"\d+", "", _name) # one number + + metadata = {} + # add knob to metadata + for knob in ["lookup", "length"]: + value = node[knob].value() + animated = node[knob].isAnimated() + if animated: + value = [ + ((node[knob].getValueAt(i)) - i) + for i in range( + track_item.timelineIn(), track_item.timelineOut() + 1) + ] + + metadata[knob] = value + + # make effect + otio_effect = otio.schema.TimeEffect() + otio_effect.name = name + otio_effect.effect_name = effect_name + otio_effect.metadata = metadata + + # add otio effect to clip effects + otio_clip.effects.append(otio_effect) + + +def create_otio_reference(clip): + metadata = _get_metadata(clip) + media_source = clip.mediaSource() + + # get file info for path and start frame + file_info = media_source.fileinfos().pop() + frame_start = file_info.startFrame() + path = file_info.filename() + + # get padding and other file infos + padding = media_source.filenamePadding() + file_head = media_source.filenameHead() + is_sequence = not media_source.singleFile() + frame_duration = media_source.duration() + fps = utils.get_rate(clip) or self.project_fps + extension = os.path.splitext(path)[-1] + + if is_sequence: + metadata.update({ + "isSequence": True, + "padding": padding + }) + + # add resolution metadata + metadata.update({ + "openpype.source.colourtransform": clip.sourceMediaColourTransform(), + "openpype.source.width": int(media_source.width()), + "openpype.source.height": int(media_source.height()), + "openpype.source.pixelAspect": float(media_source.pixelAspect()) + }) + + otio_ex_ref_item = None + + if is_sequence: + # 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 = os.path.dirname(path) + otio_ex_ref_item = otio.schema.ImageSequenceReference( + target_url_base=dirname + os.sep, + name_prefix=file_head, + name_suffix=extension, + start_frame=frame_start, + frame_zero_padding=padding, + rate=fps, + available_range=create_otio_time_range( + frame_start, + frame_duration, + fps + ) + ) + except AttributeError: + pass + + if not otio_ex_ref_item: + reformat_path = utils.get_reformated_path(path, padded=False) + # 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, media_source, **metadata) + + return otio_ex_ref_item + + +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 create_otio_markers(otio_item, item): + for tag in item.tags(): + if not tag.visible(): + continue + + if tag.name() == 'Copy': + # Hiero adds this tag to a lot of clips + continue + + frame_rate = utils.get_rate(item) or self.project_fps + + 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 + ) + ) + # 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}) + + # Store the source item for future import assignment + metadata['hiero_source_type'] = 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 create_otio_clip(track_item): + clip = track_item.source() + speed = track_item.playbackSpeed() + # flip if speed is in minus + source_in = track_item.sourceIn() if speed > 0 else track_item.sourceOut() + + duration = int(track_item.duration()) + + fps = utils.get_rate(track_item) or self.project_fps + name = track_item.name() + + media_reference = create_otio_reference(clip) + source_range = create_otio_time_range( + int(source_in), + int(duration), + fps + ) + + otio_clip = otio.schema.Clip( + name=name, + source_range=source_range, + media_reference=media_reference + ) + + # Add tags as markers + if self.include_tags: + create_otio_markers(otio_clip, track_item) + create_otio_markers(otio_clip, track_item.source()) + + # only if video + if not clip.mediaSource().hasAudio(): + # Add effects to clips + create_time_effects(otio_clip, track_item) + + return otio_clip + + +def create_otio_gap(gap_start, clip_start, tl_start_frame, fps): + return otio.schema.Gap( + source_range=create_otio_time_range( + gap_start, + (clip_start - tl_start_frame) - gap_start, + fps + ) + ) + + +def _create_otio_timeline(): + project = get_current_hiero_project(remove_untitled=False) + metadata = _get_metadata(self.timeline) + + metadata.update({ + "openpype.timeline.width": int(self.timeline.format().width()), + "openpype.timeline.height": int(self.timeline.format().height()), + "openpype.timeline.pixelAspect": int(self.timeline.format().pixelAspect()), # noqa + "openpype.project.useOCIOEnvironmentOverride": project.useOCIOEnvironmentOverride(), # noqa + "openpype.project.lutSetting16Bit": project.lutSetting16Bit(), + "openpype.project.lutSetting8Bit": project.lutSetting8Bit(), + "openpype.project.lutSettingFloat": project.lutSettingFloat(), + "openpype.project.lutSettingLog": project.lutSettingLog(), + "openpype.project.lutSettingViewer": project.lutSettingViewer(), + "openpype.project.lutSettingWorkingSpace": project.lutSettingWorkingSpace(), # noqa + "openpype.project.lutUseOCIOForExport": project.lutUseOCIOForExport(), + "openpype.project.ocioConfigName": project.ocioConfigName(), + "openpype.project.ocioConfigPath": project.ocioConfigPath() + }) + + start_time = create_otio_rational_time( + self.timeline.timecodeStart(), self.project_fps) + + return otio.schema.Timeline( + name=self.timeline.name(), + global_start_time=start_time, + metadata=metadata + ) + + +def create_otio_track(track_type, track_name): + return otio.schema.Track( + name=track_name, + kind=self.track_types[track_type] + ) + + +def add_otio_gap(track_item, otio_track, prev_out): + gap_length = track_item.timelineIn() - prev_out + if prev_out != 0: + gap_length -= 1 + + gap = otio.opentime.TimeRange( + duration=otio.opentime.RationalTime( + gap_length, + self.project_fps + ) + ) + otio_gap = otio.schema.Gap(source_range=gap) + otio_track.append(otio_gap) + + +def add_otio_metadata(otio_item, media_source, **kwargs): + metadata = _get_metadata(media_source) + + # add additional metadata from kwargs + if kwargs: + metadata.update(kwargs) + + # add metadata to otio item metadata + for key, value in metadata.items(): + otio_item.metadata.update({key: value}) + + +def create_otio_timeline(): + + def set_prev_item(itemindex, track_item): + # Add Gap if needed + if itemindex == 0: + # if it is first track item at track then add + # it to previouse item + return track_item + + else: + # get previouse item + return track_item.parent().items()[itemindex - 1] + + # get current timeline + self.timeline = hiero.ui.activeSequence() + self.project_fps = self.timeline.framerate().toFloat() + + # convert timeline to otio + otio_timeline = _create_otio_timeline() + + # loop all defined track types + for track in self.timeline.items(): + # skip if track is disabled + if not track.isEnabled(): + continue + + # convert track to otio + otio_track = create_otio_track( + type(track), track.name()) + + for itemindex, track_item in enumerate(track): + # Add Gap if needed + if itemindex == 0: + # if it is first track item at track then add + # it to previouse item + prev_item = track_item + + else: + # get previouse item + prev_item = track_item.parent().items()[itemindex - 1] + + # calculate clip frame range difference from each other + clip_diff = track_item.timelineIn() - prev_item.timelineOut() + + # add gap if first track item is not starting + # at first timeline frame + if itemindex == 0 and track_item.timelineIn() > 0: + add_otio_gap(track_item, otio_track, 0) + + # or add gap if following track items are having + # frame range differences from each other + elif itemindex and clip_diff != 1: + add_otio_gap(track_item, otio_track, prev_item.timelineOut()) + + # create otio clip and add it to track + otio_clip = create_otio_clip(track_item) + otio_track.append(otio_clip) + + # Add tags as markers + if self.include_tags: + create_otio_markers(otio_track, track) + + # add track to otio timeline + otio_timeline.tracks.append(otio_track) + + return otio_timeline + + +def write_to_file(otio_timeline, path): + otio.adapters.write_to_file(otio_timeline, path) diff --git a/openpype/hosts/flame/otio/utils.py b/openpype/hosts/flame/otio/utils.py new file mode 100644 index 0000000000..4c5d46bd51 --- /dev/null +++ b/openpype/hosts/flame/otio/utils.py @@ -0,0 +1,80 @@ +import re +import opentimelineio as otio + + +def timecode_to_frames(timecode, framerate): + rt = otio.opentime.from_timecode(timecode, 24) + return int(otio.opentime.to_frames(rt)) + + +def frames_to_timecode(frames, framerate): + rt = otio.opentime.from_frames(frames, framerate) + return otio.opentime.to_timecode(rt) + + +def frames_to_secons(frames, framerate): + rt = otio.opentime.from_frames(frames, framerate) + return otio.opentime.to_seconds(rt) + + +def get_reformated_path(path, padded=True): + """ + Return fixed python expression path + + Args: + path (str): path url or simple file name + + Returns: + type: string with reformated path + + Example: + get_reformated_path("plate.[0001-1008].exr") > plate.%04d.exr + + """ + 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, "%0{}d".format(padding), path) + else: + path = re.sub(num_pattern, "%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 + + +def get_rate(item): + if not hasattr(item, 'framerate'): + return None + + num, den = item.framerate().toRational() + + try: + rate = float(num) / float(den) + except ZeroDivisionError: + return None + + if rate.is_integer(): + return rate + + return round(rate, 4) From f38e4ce44ca09b7390aa4380671f38f14d3c0e8a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 22 Oct 2021 11:26:21 +0200 Subject: [PATCH 02/21] Flame: testing passing selection from timeline --- openpype/hosts/flame/__init__.py | 2 ++ openpype/hosts/flame/api/menu.py | 15 ++++++++------- .../plugins/publish/collect_test_selection.py | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 openpype/hosts/flame/plugins/publish/collect_test_selection.py diff --git a/openpype/hosts/flame/__init__.py b/openpype/hosts/flame/__init__.py index dc3d3e7cba..28b7aa8ea5 100644 --- a/openpype/hosts/flame/__init__.py +++ b/openpype/hosts/flame/__init__.py @@ -51,6 +51,7 @@ INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") app_framework = None apps = [] +selection = None __all__ = [ @@ -65,6 +66,7 @@ __all__ = [ "app_framework", "apps", + "selection", # pipeline "install", diff --git a/openpype/hosts/flame/api/menu.py b/openpype/hosts/flame/api/menu.py index 65d1535beb..d6788f3f52 100644 --- a/openpype/hosts/flame/api/menu.py +++ b/openpype/hosts/flame/api/menu.py @@ -7,7 +7,6 @@ from copy import deepcopy from .lib import rescan_hooks from openpype.tools.utils.host_tools import HostToolsHelper - menu_group_name = 'OpenPype' default_flame_export_presets = { @@ -16,6 +15,10 @@ default_flame_export_presets = { 'Thumbnail': {'PresetVisibility': 3, 'PresetType': 0, 'PresetFile': 'Generate Thumbnail.xml'} } +def send_selection(selection): + import openpype.hosts.flame as opflame + opflame.selection = selection + print(opflame.selection) class _FlameMenuApp(object): def __init__(self, framework): @@ -99,10 +102,12 @@ class FlameMenuProjectconnect(_FlameMenuApp): }) menu['actions'].append({ "name": "Create ...", + "isVisible": send_selection, "execute": lambda x: self.tools_helper.show_creator() }) menu['actions'].append({ "name": "Publish ...", + "isVisible": send_selection, "execute": lambda x: self.tools_helper.show_publish() }) menu['actions'].append({ @@ -119,9 +124,6 @@ class FlameMenuProjectconnect(_FlameMenuApp): }) return menu - def get_projects(self, *args, **kwargs): - pass - def refresh(self, *args, **kwargs): self.rescan() @@ -163,10 +165,12 @@ class FlameMenuTimeline(_FlameMenuApp): menu['actions'].append({ "name": "Create ...", + "isVisible": send_selection, "execute": lambda x: self.tools_helper.show_creator() }) menu['actions'].append({ "name": "Publish ...", + "isVisible": send_selection, "execute": lambda x: self.tools_helper.show_publish() }) menu['actions'].append({ @@ -180,9 +184,6 @@ class FlameMenuTimeline(_FlameMenuApp): return menu - def get_projects(self, *args, **kwargs): - pass - def refresh(self, *args, **kwargs): self.rescan() diff --git a/openpype/hosts/flame/plugins/publish/collect_test_selection.py b/openpype/hosts/flame/plugins/publish/collect_test_selection.py new file mode 100644 index 0000000000..6534894f2e --- /dev/null +++ b/openpype/hosts/flame/plugins/publish/collect_test_selection.py @@ -0,0 +1,15 @@ +import pyblish.api +import openpype.hosts.flame as opflame +import flame + +@pyblish.api.log +class CollectTestSelection(pyblish.api.ContextPlugin): + """testing selection sharing + """ + + order = pyblish.api.CollectorOrder + label = "test selection" + hosts = ["flame"] + + def process(self, context): + self.log.info(opflame.selection) \ No newline at end of file From 4013412c638f7bafb050e941b757ca69ddf7a2c2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 13 Dec 2021 13:42:40 +0100 Subject: [PATCH 03/21] send_selection returns True --- openpype/hosts/flame/api/menu.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/flame/api/menu.py b/openpype/hosts/flame/api/menu.py index 85fcb86026..f216428e18 100644 --- a/openpype/hosts/flame/api/menu.py +++ b/openpype/hosts/flame/api/menu.py @@ -28,6 +28,7 @@ def send_selection(selection): import openpype.hosts.flame as opflame opflame.selection = selection print(opflame.selection) + return True class _FlameMenuApp(object): def __init__(self, framework): From 7dc4264d834dd4262a5ebeeca5552a0d9c1c5062 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 14 Dec 2021 16:46:25 +0100 Subject: [PATCH 04/21] flame otio export wip --- openpype/hosts/flame/otio/flame_export.py | 186 ++++++++++++++-------- 1 file changed, 118 insertions(+), 68 deletions(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index af4322e3d9..13199fe6c7 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -5,17 +5,16 @@ import os import re import sys import ast -from compiler.ast import flatten + import opentimelineio as otio from . import utils -import hiero.core -import hiero.ui +import flame self = sys.modules[__name__] -self.track_types = { - hiero.core.VideoTrack: otio.schema.TrackKind.Video, - hiero.core.AudioTrack: otio.schema.TrackKind.Audio -} +# self.track_types = { +# hiero.core.VideoTrack: otio.schema.TrackKind.Video, +# hiero.core.AudioTrack: otio.schema.TrackKind.Audio +# } self.project_fps = None self.marker_color_map = { "magenta": otio.schema.MarkerColor.MAGENTA, @@ -29,17 +28,17 @@ self.timeline = None self.include_tags = True -def get_current_hiero_project(remove_untitled=False): - projects = flatten(hiero.core.projects()) - if not remove_untitled: - return next(iter(projects)) - - # if remove_untitled - for proj in projects: - if "Untitled" in proj.name(): - proj.close() +def flatten(_list): + for item in _list: + if isinstance(item, (list, tuple)): + for sub_item in flatten(item): + yield sub_item else: - return proj + yield item + + +def get_current_flame_project(): + return flame.project.current_project def create_otio_rational_time(frame, fps): @@ -313,7 +312,7 @@ def create_otio_gap(gap_start, clip_start, tl_start_frame, fps): def _create_otio_timeline(): - project = get_current_hiero_project(remove_untitled=False) + project = get_current_flame_project() metadata = _get_metadata(self.timeline) metadata.update({ @@ -375,73 +374,124 @@ def add_otio_metadata(otio_item, media_source, **kwargs): for key, value in metadata.items(): otio_item.metadata.update({key: value}) +def get_segment_attributes(segment, frame_rate): + print(segment.attributes) + if str(segment.name)[1:-1] == "": + return None -def create_otio_timeline(): + # get clip frame duration + record_duration = str(segment.record_duration)[1:-1] + clip_duration = utils.timecode_to_frames( + record_duration, frame_rate) - def set_prev_item(itemindex, track_item): - # Add Gap if needed - if itemindex == 0: - # if it is first track item at track then add - # it to previouse item - return track_item + # populate shot source metadata + shot_description = "" + for attr in ["tape_name", "source_name", "head", + "tail", "file_path"]: + if not hasattr(segment, attr): + continue + _value = getattr(segment, attr) + _label = attr.replace("_", " ").capitalize() + row = "{}: {}\n".format(_label, _value) + shot_description += row + + # Add timeline segment to tree + clip_data = { + "clip_name": str(segment.name)[1:-1], + "clip_duration": str(clip_duration), + "clip_comment": str(segment.comment)[1:-1], + "shot_description": shot_description + } + print(clip_data) + +def create_otio_timeline(selection): + process_timeline = None + process_segments = [] + + if len(selection) == 1: + if isinstance(selection[0], flame.PySequence): + process_timeline = selection[0] + else: + for item in selection: + if not isinstance(item, flame.PySegment): + continue + process_segments.append(item) + + if process_timeline: + print("___________________timeline__________________") + frame_rate = float(str(process_timeline.frame_rate)[:-4]) + print(frame_rate) + for ver in process_timeline.versions: + for tracks in ver.tracks: + for segment in tracks.segments: + # process all segments + get_segment_attributes(segment, frame_rate) + + if process_segments: + print("___________________segments__________________") + # get segments sequence parent + track = process_segments[0].parent + version = track.parent + flame_timeline = version.parent + frame_rate = float(str(flame_timeline.frame_rate)[:-4]) + + for segment in process_segments: + get_segment_attributes(segment, frame_rate) - else: - # get previouse item - return track_item.parent().items()[itemindex - 1] # get current timeline - self.timeline = hiero.ui.activeSequence() - self.project_fps = self.timeline.framerate().toFloat() + # self.timeline = hiero.ui.activeSequence() + # self.project_fps = self.timeline.framerate().toFloat() - # convert timeline to otio - otio_timeline = _create_otio_timeline() + # # convert timeline to otio + # otio_timeline = _create_otio_timeline() - # loop all defined track types - for track in self.timeline.items(): - # skip if track is disabled - if not track.isEnabled(): - continue + # # loop all defined track types + # for track in self.timeline.items(): + # # skip if track is disabled + # if not track.isEnabled(): + # continue - # convert track to otio - otio_track = create_otio_track( - type(track), track.name()) + # # convert track to otio + # otio_track = create_otio_track( + # type(track), track.name()) - for itemindex, track_item in enumerate(track): - # Add Gap if needed - if itemindex == 0: - # if it is first track item at track then add - # it to previouse item - prev_item = track_item + # for itemindex, track_item in enumerate(track): + # # Add Gap if needed + # if itemindex == 0: + # # if it is first track item at track then add + # # it to previouse item + # prev_item = track_item - else: - # get previouse item - prev_item = track_item.parent().items()[itemindex - 1] + # else: + # # get previouse item + # prev_item = track_item.parent().items()[itemindex - 1] - # calculate clip frame range difference from each other - clip_diff = track_item.timelineIn() - prev_item.timelineOut() + # # calculate clip frame range difference from each other + # clip_diff = track_item.timelineIn() - prev_item.timelineOut() - # add gap if first track item is not starting - # at first timeline frame - if itemindex == 0 and track_item.timelineIn() > 0: - add_otio_gap(track_item, otio_track, 0) + # # add gap if first track item is not starting + # # at first timeline frame + # if itemindex == 0 and track_item.timelineIn() > 0: + # add_otio_gap(track_item, otio_track, 0) - # or add gap if following track items are having - # frame range differences from each other - elif itemindex and clip_diff != 1: - add_otio_gap(track_item, otio_track, prev_item.timelineOut()) + # # or add gap if following track items are having + # # frame range differences from each other + # elif itemindex and clip_diff != 1: + # add_otio_gap(track_item, otio_track, prev_item.timelineOut()) - # create otio clip and add it to track - otio_clip = create_otio_clip(track_item) - otio_track.append(otio_clip) + # # create otio clip and add it to track + # otio_clip = create_otio_clip(track_item) + # otio_track.append(otio_clip) - # Add tags as markers - if self.include_tags: - create_otio_markers(otio_track, track) + # # Add tags as markers + # if self.include_tags: + # create_otio_markers(otio_track, track) - # add track to otio timeline - otio_timeline.tracks.append(otio_track) + # # add track to otio timeline + # otio_timeline.tracks.append(otio_track) - return otio_timeline + # return otio_timeline def write_to_file(otio_timeline, path): From 731a9f4dd961022fbb7633abc3a6437acda4b01c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 14 Dec 2021 18:00:49 +0100 Subject: [PATCH 05/21] flame extract otio wip --- openpype/hosts/flame/otio/flame_export.py | 82 ++++++++++--------- openpype/hosts/flame/otio/utils.py | 2 +- .../plugins/publish/collect_test_selection.py | 5 +- 3 files changed, 47 insertions(+), 42 deletions(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index 13199fe6c7..818cd4586f 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -5,10 +5,15 @@ import os import re import sys import ast - +import logging import opentimelineio as otio from . import utils import flame +from pprint import pformat + +reload(utils) + +log = logging.getLogger(__name__) self = sys.modules[__name__] # self.track_types = { @@ -375,70 +380,67 @@ def add_otio_metadata(otio_item, media_source, **kwargs): otio_item.metadata.update({key: value}) def get_segment_attributes(segment, frame_rate): - print(segment.attributes) + log.info(segment) + # log.info(dir(segment)) + # log.info(segment.attributes) + # track = segment.parent + # log.info("track: {}".format(track)) + # log.info(dir(track)) + # log.info(track.attributes) + # log.info(track.name) + if str(segment.name)[1:-1] == "": return None - # get clip frame duration - record_duration = str(segment.record_duration)[1:-1] - clip_duration = utils.timecode_to_frames( - record_duration, frame_rate) - - # populate shot source metadata - shot_description = "" - for attr in ["tape_name", "source_name", "head", - "tail", "file_path"]: - if not hasattr(segment, attr): - continue - _value = getattr(segment, attr) - _label = attr.replace("_", " ").capitalize() - row = "{}: {}\n".format(_label, _value) - shot_description += row - # Add timeline segment to tree clip_data = { "clip_name": str(segment.name)[1:-1], - "clip_duration": str(clip_duration), "clip_comment": str(segment.comment)[1:-1], - "shot_description": shot_description + "tape_name": str(segment.tape_name), + "source_name": str(segment.source_name), + "file_path": str(segment.file_path) } - print(clip_data) + + # populate shot source metadata + segment_attrs = [ + "record_duration", "record_in", "record_out", + "source_duration", "source_in", "source_out" + ] + segment_attrs_data = {} + for attr in segment_attrs: + if not hasattr(segment, attr): + continue + _value = getattr(segment, attr) + segment_attrs_data[attr] = _value + _value = str(_value)[1:-1] + clip_data[attr] = utils.timecode_to_frames( + _value, frame_rate) + + clip_data["segment_attrs"] = segment_attrs_data + + log.info(pformat(clip_data)) def create_otio_timeline(selection): process_timeline = None - process_segments = [] if len(selection) == 1: if isinstance(selection[0], flame.PySequence): process_timeline = selection[0] else: - for item in selection: - if not isinstance(item, flame.PySegment): - continue - process_segments.append(item) + track = selection[0].parent + version = track.parent + process_timeline = version.parent if process_timeline: - print("___________________timeline__________________") + log.info("___________________timeline__________________") frame_rate = float(str(process_timeline.frame_rate)[:-4]) - print(frame_rate) + log.info(frame_rate) for ver in process_timeline.versions: for tracks in ver.tracks: for segment in tracks.segments: # process all segments get_segment_attributes(segment, frame_rate) - if process_segments: - print("___________________segments__________________") - # get segments sequence parent - track = process_segments[0].parent - version = track.parent - flame_timeline = version.parent - frame_rate = float(str(flame_timeline.frame_rate)[:-4]) - - for segment in process_segments: - get_segment_attributes(segment, frame_rate) - - # get current timeline # self.timeline = hiero.ui.activeSequence() # self.project_fps = self.timeline.framerate().toFloat() diff --git a/openpype/hosts/flame/otio/utils.py b/openpype/hosts/flame/otio/utils.py index 4c5d46bd51..2d927957c9 100644 --- a/openpype/hosts/flame/otio/utils.py +++ b/openpype/hosts/flame/otio/utils.py @@ -3,7 +3,7 @@ import opentimelineio as otio def timecode_to_frames(timecode, framerate): - rt = otio.opentime.from_timecode(timecode, 24) + rt = otio.opentime.from_timecode(timecode, framerate) return int(otio.opentime.to_frames(rt)) diff --git a/openpype/hosts/flame/plugins/publish/collect_test_selection.py b/openpype/hosts/flame/plugins/publish/collect_test_selection.py index 6534894f2e..dbbbc2f0bd 100644 --- a/openpype/hosts/flame/plugins/publish/collect_test_selection.py +++ b/openpype/hosts/flame/plugins/publish/collect_test_selection.py @@ -1,6 +1,8 @@ import pyblish.api import openpype.hosts.flame as opflame +from openpype.hosts.flame.otio import flame_export as otio_export import flame +reload(otio_export) @pyblish.api.log class CollectTestSelection(pyblish.api.ContextPlugin): @@ -12,4 +14,5 @@ class CollectTestSelection(pyblish.api.ContextPlugin): hosts = ["flame"] def process(self, context): - self.log.info(opflame.selection) \ No newline at end of file + self.log.info(opflame.selection) + otio_export.create_otio_timeline(opflame.selection) \ No newline at end of file From cab1f32a5ac4e1d5dac51f4e47f419b2381b33ae Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Dec 2021 10:41:14 +0100 Subject: [PATCH 06/21] abstracting get timeline to lib --- openpype/hosts/flame/__init__.py | 4 +-- openpype/hosts/flame/api/lib.py | 26 +++++++++++++---- openpype/hosts/flame/otio/flame_export.py | 29 ++++++------------- .../plugins/publish/collect_test_selection.py | 8 +++-- 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/openpype/hosts/flame/__init__.py b/openpype/hosts/flame/__init__.py index f04b73baba..da28170679 100644 --- a/openpype/hosts/flame/__init__.py +++ b/openpype/hosts/flame/__init__.py @@ -19,7 +19,7 @@ from .api.lib import ( maintain_current_timeline, get_project_manager, get_current_project, - get_current_timeline, + get_current_sequence, create_bin, ) @@ -88,7 +88,7 @@ __all__ = [ "maintain_current_timeline", "get_project_manager", "get_current_project", - "get_current_timeline", + "get_current_sequence", "create_bin", # menu diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 89e020b329..941f6b7d75 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -220,10 +220,10 @@ def maintain_current_timeline(to_timeline, from_timeline=None): timeline2 >>> with maintain_current_timeline(to_timeline): - ... print(get_current_timeline().GetName()) + ... print(get_current_sequence().GetName()) timeline2 - >>> print(get_current_timeline().GetName()) + >>> print(get_current_sequence().GetName()) timeline1 """ # todo: this is still Resolve's implementation @@ -256,9 +256,25 @@ def get_current_project(): return -def get_current_timeline(new=False): - # TODO: get_current_timeline - return +def get_current_sequence(selection): + import flame + + process_timeline = None + + if len(selection) == 1: + if isinstance(selection[0], flame.PySequence): + process_timeline = selection[0] + else: + process_segment = None + for segment in selection: + if isinstance(segment, flame.PySegment): + process_segment = segment + break + track = process_segment.parent + version = track.parent + process_timeline = version.parent + + return process_timeline def create_bin(name, root=None): diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index 818cd4586f..9fb219a545 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -8,6 +8,7 @@ import ast import logging import opentimelineio as otio from . import utils + import flame from pprint import pformat @@ -420,26 +421,14 @@ def get_segment_attributes(segment, frame_rate): log.info(pformat(clip_data)) -def create_otio_timeline(selection): - process_timeline = None - - if len(selection) == 1: - if isinstance(selection[0], flame.PySequence): - process_timeline = selection[0] - else: - track = selection[0].parent - version = track.parent - process_timeline = version.parent - - if process_timeline: - log.info("___________________timeline__________________") - frame_rate = float(str(process_timeline.frame_rate)[:-4]) - log.info(frame_rate) - for ver in process_timeline.versions: - for tracks in ver.tracks: - for segment in tracks.segments: - # process all segments - get_segment_attributes(segment, frame_rate) +def create_otio_timeline(sequence): + frame_rate = float(str(sequence.frame_rate)[:-4]) + log.info(frame_rate) + for ver in sequence.versions: + for tracks in ver.tracks: + for segment in tracks.segments: + # process all segments + get_segment_attributes(segment, frame_rate) # get current timeline # self.timeline = hiero.ui.activeSequence() diff --git a/openpype/hosts/flame/plugins/publish/collect_test_selection.py b/openpype/hosts/flame/plugins/publish/collect_test_selection.py index dbbbc2f0bd..cefd9ee7cf 100644 --- a/openpype/hosts/flame/plugins/publish/collect_test_selection.py +++ b/openpype/hosts/flame/plugins/publish/collect_test_selection.py @@ -1,7 +1,7 @@ import pyblish.api import openpype.hosts.flame as opflame from openpype.hosts.flame.otio import flame_export as otio_export -import flame +from pprint import pformat reload(otio_export) @pyblish.api.log @@ -15,4 +15,8 @@ class CollectTestSelection(pyblish.api.ContextPlugin): def process(self, context): self.log.info(opflame.selection) - otio_export.create_otio_timeline(opflame.selection) \ No newline at end of file + + sequence = opflame.get_current_sequence(opflame.selection) + otio_timeline = otio_export.create_otio_timeline(sequence) + + self.log.info(pformat(otio_timeline)) \ No newline at end of file From 1ffdf801cc51e7da1eae496cff1342ddb1f99def Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Dec 2021 14:52:04 +0100 Subject: [PATCH 07/21] otio export modul wip --- openpype/hosts/flame/otio/flame_export.py | 122 ++++++++++++++-------- 1 file changed, 79 insertions(+), 43 deletions(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index 9fb219a545..e7735be701 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -21,7 +21,7 @@ self = sys.modules[__name__] # hiero.core.VideoTrack: otio.schema.TrackKind.Video, # hiero.core.AudioTrack: otio.schema.TrackKind.Audio # } -self.project_fps = None +self.fps = None self.marker_color_map = { "magenta": otio.schema.MarkerColor.MAGENTA, "red": otio.schema.MarkerColor.RED, @@ -30,7 +30,6 @@ self.marker_color_map = { "cyan": otio.schema.MarkerColor.CYAN, "blue": otio.schema.MarkerColor.BLUE, } -self.timeline = None self.include_tags = True @@ -151,7 +150,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) or self.project_fps + fps = utils.get_rate(clip) or self.fps extension = os.path.splitext(path)[-1] if is_sequence: @@ -231,7 +230,7 @@ def create_otio_markers(otio_item, item): # Hiero adds this tag to a lot of clips continue - frame_rate = utils.get_rate(item) or self.project_fps + frame_rate = utils.get_rate(item) or self.fps marked_range = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( @@ -278,7 +277,7 @@ def create_otio_clip(track_item): duration = int(track_item.duration()) - fps = utils.get_rate(track_item) or self.project_fps + fps = utils.get_rate(track_item) or self.fps name = track_item.name() media_reference = create_otio_reference(clip) @@ -317,28 +316,28 @@ def create_otio_gap(gap_start, clip_start, tl_start_frame, fps): ) -def _create_otio_timeline(): +def _create_otio_timeline(sequence): project = get_current_flame_project() - metadata = _get_metadata(self.timeline) + metadata = _get_metadata(sequence) metadata.update({ - "openpype.timeline.width": int(self.timeline.format().width()), - "openpype.timeline.height": int(self.timeline.format().height()), - "openpype.timeline.pixelAspect": int(self.timeline.format().pixelAspect()), # noqa - "openpype.project.useOCIOEnvironmentOverride": project.useOCIOEnvironmentOverride(), # noqa - "openpype.project.lutSetting16Bit": project.lutSetting16Bit(), - "openpype.project.lutSetting8Bit": project.lutSetting8Bit(), - "openpype.project.lutSettingFloat": project.lutSettingFloat(), - "openpype.project.lutSettingLog": project.lutSettingLog(), - "openpype.project.lutSettingViewer": project.lutSettingViewer(), - "openpype.project.lutSettingWorkingSpace": project.lutSettingWorkingSpace(), # noqa - "openpype.project.lutUseOCIOForExport": project.lutUseOCIOForExport(), - "openpype.project.ocioConfigName": project.ocioConfigName(), - "openpype.project.ocioConfigPath": project.ocioConfigPath() + "openpype.timeline.width": int(sequence.width), + "openpype.timeline.height": int(sequence.height), + "openpype.timeline.pixelAspect": 1, # noqa + # "openpype.project.useOCIOEnvironmentOverride": project.useOCIOEnvironmentOverride(), # noqa + # "openpype.project.lutSetting16Bit": project.lutSetting16Bit(), + # "openpype.project.lutSetting8Bit": project.lutSetting8Bit(), + # "openpype.project.lutSettingFloat": project.lutSettingFloat(), + # "openpype.project.lutSettingLog": project.lutSettingLog(), + # "openpype.project.lutSettingViewer": project.lutSettingViewer(), + # "openpype.project.lutSettingWorkingSpace": project.lutSettingWorkingSpace(), # noqa + # "openpype.project.lutUseOCIOForExport": project.lutUseOCIOForExport(), + # "openpype.project.ocioConfigName": project.ocioConfigName(), + # "openpype.project.ocioConfigPath": project.ocioConfigPath() }) start_time = create_otio_rational_time( - self.timeline.timecodeStart(), self.project_fps) + self.timeline.timecodeStart(), self.fps) return otio.schema.Timeline( name=self.timeline.name(), @@ -362,7 +361,7 @@ def add_otio_gap(track_item, otio_track, prev_out): gap = otio.opentime.TimeRange( duration=otio.opentime.RationalTime( gap_length, - self.project_fps + self.fps ) ) otio_gap = otio.schema.Gap(source_range=gap) @@ -382,24 +381,17 @@ def add_otio_metadata(otio_item, media_source, **kwargs): def get_segment_attributes(segment, frame_rate): log.info(segment) - # log.info(dir(segment)) - # log.info(segment.attributes) - # track = segment.parent - # log.info("track: {}".format(track)) - # log.info(dir(track)) - # log.info(track.attributes) - # log.info(track.name) if str(segment.name)[1:-1] == "": return None # Add timeline segment to tree clip_data = { - "clip_name": str(segment.name)[1:-1], - "clip_comment": str(segment.comment)[1:-1], + "name": str(segment.name)[1:-1], + "comment": str(segment.comment)[1:-1], "tape_name": str(segment.tape_name), "source_name": str(segment.source_name), - "file_path": str(segment.file_path) + "fpath": str(segment.file_path) } # populate shot source metadata @@ -417,25 +409,69 @@ def get_segment_attributes(segment, frame_rate): clip_data[attr] = utils.timecode_to_frames( _value, frame_rate) - clip_data["segment_attrs"] = segment_attrs_data + clip_data["segment_timecodes"] = segment_attrs_data log.info(pformat(clip_data)) + return clip_data + +def get_track_attributes(track, frame_rate): + log.info(track) + log.info(dir(track)) + log.info(track.attributes) + + if len(track.segments) == 0: + return None + + # Add timeline segment to tree + track_data = { + "name": str(track.name)[1:-1] + } + + # # populate shot source metadata + # segment_attrs = [ + # "record_duration", "record_in", "record_out", + # "source_duration", "source_in", "source_out" + # ] + # segment_attrs_data = {} + # for attr in segment_attrs: + # if not hasattr(segment, attr): + # continue + # _value = getattr(segment, attr) + # segment_attrs_data[attr] = _value + # _value = str(_value)[1:-1] + # clip_data[attr] = utils.timecode_to_frames( + # _value, frame_rate) + + # clip_data["segment_timecodes"] = segment_attrs_data + + log.info(pformat(track_data)) + return track_data def create_otio_timeline(sequence): - frame_rate = float(str(sequence.frame_rate)[:-4]) - log.info(frame_rate) - for ver in sequence.versions: - for tracks in ver.tracks: - for segment in tracks.segments: - # process all segments - get_segment_attributes(segment, frame_rate) + log.info(dir(sequence)) + log.info(sequence.attributes) # get current timeline - # self.timeline = hiero.ui.activeSequence() - # self.project_fps = self.timeline.framerate().toFloat() + self.fps = float(str(sequence.frame_rate)[:-4]) # # convert timeline to otio - # otio_timeline = _create_otio_timeline() + otio_timeline = _create_otio_timeline(sequence) + + # create otio tracks and clips + for ver in sequence.versions: + for track in ver.tracks: + track_data = get_track_attributes(track, self.fps) + if not track_data: + continue + for segment in track.segments: + # process all segments + clip_data = get_segment_attributes( + segment, self.fps) + # create otio clip + # create otio reference + # create otio marker + # create otio metadata + # # loop all defined track types # for track in self.timeline.items(): From 6909db9d7ca3c1ff057c8bf4ec6ff950ca2361fc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Dec 2021 23:04:51 +0100 Subject: [PATCH 08/21] otio export module wip --- openpype/hosts/flame/api/lib.py | 12 +- openpype/hosts/flame/otio/flame_export.py | 241 +++++++++------------- openpype/hosts/flame/otio/utils.py | 46 ++--- 3 files changed, 121 insertions(+), 178 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 941f6b7d75..fba2d8f5c8 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -258,21 +258,23 @@ def get_current_project(): def get_current_sequence(selection): import flame + def segment_to_sequence(_segment): + track = _segment.parent + version = track.parent + return version.parent process_timeline = None if len(selection) == 1: if isinstance(selection[0], flame.PySequence): process_timeline = selection[0] + if isinstance(selection[0], flame.PySegment): + process_timeline = segment_to_sequence(selection[0]) else: - process_segment = None for segment in selection: if isinstance(segment, flame.PySegment): - process_segment = segment + process_timeline = segment_to_sequence(segment) break - track = process_segment.parent - version = track.parent - process_timeline = version.parent return process_timeline diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index e7735be701..e55e72f55e 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -17,11 +17,13 @@ reload(utils) log = logging.getLogger(__name__) self = sys.modules[__name__] -# self.track_types = { -# hiero.core.VideoTrack: otio.schema.TrackKind.Video, -# hiero.core.AudioTrack: otio.schema.TrackKind.Audio -# } +self.track_types = { + "video": otio.schema.TrackKind.Video, + "audio": otio.schema.TrackKind.Audio +} self.fps = None +self.seq_frame_start = None + self.marker_color_map = { "magenta": otio.schema.MarkerColor.MAGENTA, "red": otio.schema.MarkerColor.RED, @@ -59,10 +61,12 @@ def create_otio_time_range(start_frame, frame_duration, fps): duration=create_otio_rational_time(frame_duration, fps) ) - def _get_metadata(item): if hasattr(item, 'metadata'): - return {key: value for key, value in dict(item.metadata()).items()} + log.debug(item.metadata) + if not item.metadata: + return {} + return {key: value for key, value in dict(item.metadata)} return {} @@ -136,22 +140,23 @@ def create_time_effects(otio_clip, track_item): otio_clip.effects.append(otio_effect) -def create_otio_reference(clip): - metadata = _get_metadata(clip) - media_source = clip.mediaSource() +def create_otio_reference(clip_data): + metadata = _get_metadata(clip_data) # get file info for path and start frame - file_info = media_source.fileinfos().pop() - frame_start = file_info.startFrame() - path = file_info.filename() + frame_start = 0 + path = clip_data["fpath"] + file_name = os.path.basename(path) + file_head, extension = os.path.splitext(file_name) # get padding and other file infos - padding = media_source.filenamePadding() - file_head = media_source.filenameHead() - is_sequence = not media_source.singleFile() - frame_duration = media_source.duration() - fps = utils.get_rate(clip) or self.fps - extension = os.path.splitext(path)[-1] + is_sequence = padding = utils.get_padding_from_path(path) + if is_sequence: + padding_pattern = re.compile(r"[._](\d+)[.]") + number = re.findall(padding_pattern, path).pop() + file_head = file_name.split(number)[:-1] + + frame_duration = clip_data["source_duration"] if is_sequence: metadata.update({ @@ -159,13 +164,6 @@ def create_otio_reference(clip): "padding": padding }) - # add resolution metadata - metadata.update({ - "openpype.source.colourtransform": clip.sourceMediaColourTransform(), - "openpype.source.width": int(media_source.width()), - "openpype.source.height": int(media_source.height()), - "openpype.source.pixelAspect": float(media_source.pixelAspect()) - }) otio_ex_ref_item = None @@ -180,11 +178,11 @@ def create_otio_reference(clip): name_suffix=extension, start_frame=frame_start, frame_zero_padding=padding, - rate=fps, + rate=self.fps, available_range=create_otio_time_range( frame_start, frame_duration, - fps + self.fps ) ) except AttributeError: @@ -198,12 +196,12 @@ def create_otio_reference(clip): available_range=create_otio_time_range( frame_start, frame_duration, - fps + self.fps ) ) # add metadata to otio item - add_otio_metadata(otio_ex_ref_item, media_source, **metadata) + # add_otio_metadata(otio_ex_ref_item, media_source, **metadata) return otio_ex_ref_item @@ -269,26 +267,17 @@ def create_otio_markers(otio_item, item): otio_item.markers.append(marker) -def create_otio_clip(track_item): - clip = track_item.source() - speed = track_item.playbackSpeed() - # flip if speed is in minus - source_in = track_item.sourceIn() if speed > 0 else track_item.sourceOut() +def create_otio_clip(clip_data): - duration = int(track_item.duration()) - - fps = utils.get_rate(track_item) or self.fps - name = track_item.name() - - media_reference = create_otio_reference(clip) + media_reference = create_otio_reference(clip_data) source_range = create_otio_time_range( - int(source_in), - int(duration), - fps + clip_data["source_in"], + clip_data["record_duration"], + self.fps ) otio_clip = otio.schema.Clip( - name=name, + name=clip_data["name"], source_range=source_range, media_reference=media_reference ) @@ -336,12 +325,12 @@ def _create_otio_timeline(sequence): # "openpype.project.ocioConfigPath": project.ocioConfigPath() }) - start_time = create_otio_rational_time( - self.timeline.timecodeStart(), self.fps) + rt_start_time = create_otio_rational_time( + self.seq_frame_start, self.fps) return otio.schema.Timeline( - name=self.timeline.name(), - global_start_time=start_time, + name=sequence.name, + global_start_time=rt_start_time, metadata=metadata ) @@ -353,8 +342,8 @@ def create_otio_track(track_type, track_name): ) -def add_otio_gap(track_item, otio_track, prev_out): - gap_length = track_item.timelineIn() - prev_out +def add_otio_gap(clip_data, otio_track, prev_out): + gap_length = clip_data["record_in"] - prev_out if prev_out != 0: gap_length -= 1 @@ -379,7 +368,7 @@ def add_otio_metadata(otio_item, media_source, **kwargs): for key, value in metadata.items(): otio_item.metadata.update({key: value}) -def get_segment_attributes(segment, frame_rate): +def get_segment_attributes(segment): log.info(segment) if str(segment.name)[1:-1] == "": @@ -391,7 +380,8 @@ def get_segment_attributes(segment, frame_rate): "comment": str(segment.comment)[1:-1], "tape_name": str(segment.tape_name), "source_name": str(segment.source_name), - "fpath": str(segment.file_path) + "fpath": str(segment.file_path), + "segment": segment } # populate shot source metadata @@ -406,119 +396,84 @@ def get_segment_attributes(segment, frame_rate): _value = getattr(segment, attr) segment_attrs_data[attr] = _value _value = str(_value)[1:-1] - clip_data[attr] = utils.timecode_to_frames( - _value, frame_rate) + + if attr in ["record_in", "record_out"]: + # exclude timeline start + frame = utils.timecode_to_frames( + _value, self.fps) + clip_data[attr] = frame - self.seq_frame_start + else: + clip_data[attr] = utils.timecode_to_frames( + _value, self.fps) clip_data["segment_timecodes"] = segment_attrs_data log.info(pformat(clip_data)) return clip_data -def get_track_attributes(track, frame_rate): - log.info(track) - log.info(dir(track)) - log.info(track.attributes) - - if len(track.segments) == 0: - return None - - # Add timeline segment to tree - track_data = { - "name": str(track.name)[1:-1] - } - - # # populate shot source metadata - # segment_attrs = [ - # "record_duration", "record_in", "record_out", - # "source_duration", "source_in", "source_out" - # ] - # segment_attrs_data = {} - # for attr in segment_attrs: - # if not hasattr(segment, attr): - # continue - # _value = getattr(segment, attr) - # segment_attrs_data[attr] = _value - # _value = str(_value)[1:-1] - # clip_data[attr] = utils.timecode_to_frames( - # _value, frame_rate) - - # clip_data["segment_timecodes"] = segment_attrs_data - - log.info(pformat(track_data)) - return track_data - def create_otio_timeline(sequence): log.info(dir(sequence)) log.info(sequence.attributes) # get current timeline self.fps = float(str(sequence.frame_rate)[:-4]) - + self.seq_frame_start = utils.timecode_to_frames( + str(sequence.start_time), self.fps) # # convert timeline to otio otio_timeline = _create_otio_timeline(sequence) # create otio tracks and clips for ver in sequence.versions: for track in ver.tracks: - track_data = get_track_attributes(track, self.fps) - if not track_data: - continue - for segment in track.segments: - # process all segments - clip_data = get_segment_attributes( - segment, self.fps) - # create otio clip - # create otio reference + if len(track.segments) == 0 and track.hidden: + return None + + # convert track to otio + otio_track = create_otio_track( + "video", str(track.name)[1:-1]) + + segments_ordered = { + itemindex: get_segment_attributes(segment) + for itemindex, segment in enumerate( + track.segments) + } + + for itemindex, segment_data in segments_ordered.items(): + # Add Gap if needed + if itemindex == 0: + # if it is first track item at track then add + # it to previouse item + prev_item = segment_data + + else: + # get previouse item + prev_item = segments_ordered[itemindex - 1] + + # calculate clip frame range difference from each other + clip_diff = segment_data["record_in"] - prev_item["record_out"] + + # add gap if first track item is not starting + # at first timeline frame + if itemindex == 0 and segment_data["record_in"] > 0: + add_otio_gap(segment_data, otio_track, 0) + + # or add gap if following track items are having + # frame range differences from each other + elif itemindex and clip_diff != 1: + add_otio_gap( + segment_data, otio_track, prev_item["record_out"]) + + # create otio clip and add it to track + otio_clip = create_otio_clip(segment_data) + otio_track.append(otio_clip) + # create otio marker # create otio metadata + # add track to otio timeline + otio_timeline.tracks.append(otio_track) - # # loop all defined track types - # for track in self.timeline.items(): - # # skip if track is disabled - # if not track.isEnabled(): - # continue - - # # convert track to otio - # otio_track = create_otio_track( - # type(track), track.name()) - - # for itemindex, track_item in enumerate(track): - # # Add Gap if needed - # if itemindex == 0: - # # if it is first track item at track then add - # # it to previouse item - # prev_item = track_item - - # else: - # # get previouse item - # prev_item = track_item.parent().items()[itemindex - 1] - - # # calculate clip frame range difference from each other - # clip_diff = track_item.timelineIn() - prev_item.timelineOut() - - # # add gap if first track item is not starting - # # at first timeline frame - # if itemindex == 0 and track_item.timelineIn() > 0: - # add_otio_gap(track_item, otio_track, 0) - - # # or add gap if following track items are having - # # frame range differences from each other - # elif itemindex and clip_diff != 1: - # add_otio_gap(track_item, otio_track, prev_item.timelineOut()) - - # # create otio clip and add it to track - # otio_clip = create_otio_clip(track_item) - # otio_track.append(otio_clip) - - # # Add tags as markers - # if self.include_tags: - # create_otio_markers(otio_track, track) - - # # add track to otio timeline - # otio_timeline.tracks.append(otio_track) - - # return otio_timeline + return otio_timeline def write_to_file(otio_timeline, path): diff --git a/openpype/hosts/flame/otio/utils.py b/openpype/hosts/flame/otio/utils.py index 2d927957c9..57c8e4fecd 100644 --- a/openpype/hosts/flame/otio/utils.py +++ b/openpype/hosts/flame/otio/utils.py @@ -28,17 +28,17 @@ def get_reformated_path(path, padded=True): type: string with reformated path Example: - get_reformated_path("plate.[0001-1008].exr") > plate.%04d.exr + get_reformated_path("plate.1001.exr") > plate.%04d.exr """ - 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, "%0{}d".format(padding), path) - else: - path = re.sub(num_pattern, "%d", path) + num_pattern = re.compile(r"[._](\d+)[.]") + padding = get_padding_from_path(path) + + if padded: + path = re.sub(num_pattern, "%0{}d".format(padding), path) + else: + path = re.sub(num_pattern, "%d", path) + return path @@ -53,28 +53,14 @@ def get_padding_from_path(path): int: padding number Example: - get_padding_from_path("plate.[0001-1008].exr") > 4 + get_padding_from_path("plate.0001.exr") > 4 """ - padding_pattern = "(\\d+)(?=-)" - if "[" in path: - return len(re.findall(padding_pattern, path).pop()) + padding_pattern = re.compile(r"[._](\d+)[.]") - return None + found = re.findall(padding_pattern, path).pop() - -def get_rate(item): - if not hasattr(item, 'framerate'): - return None - - num, den = item.framerate().toRational() - - try: - rate = float(num) / float(den) - except ZeroDivisionError: - return None - - if rate.is_integer(): - return rate - - return round(rate, 4) + if found: + return len(found) + else: + return None \ No newline at end of file From 5989af258a6733ab9dcfdf5da8338a416a9671e8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 16 Dec 2021 12:38:38 +0100 Subject: [PATCH 09/21] export otio modul wip --- openpype/hosts/flame/otio/flame_export.py | 192 ++++++++++-------- openpype/hosts/flame/otio/utils.py | 44 +++- .../plugins/publish/collect_test_selection.py | 5 +- 3 files changed, 147 insertions(+), 94 deletions(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index e55e72f55e..f01c600637 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -63,7 +63,6 @@ def create_otio_time_range(start_frame, frame_duration, fps): def _get_metadata(item): if hasattr(item, 'metadata'): - log.debug(item.metadata) if not item.metadata: return {} return {key: value for key, value in dict(item.metadata)} @@ -140,72 +139,6 @@ def create_time_effects(otio_clip, track_item): otio_clip.effects.append(otio_effect) -def create_otio_reference(clip_data): - metadata = _get_metadata(clip_data) - - # get file info for path and start frame - frame_start = 0 - path = clip_data["fpath"] - file_name = os.path.basename(path) - file_head, extension = os.path.splitext(file_name) - - # get padding and other file infos - is_sequence = padding = utils.get_padding_from_path(path) - if is_sequence: - padding_pattern = re.compile(r"[._](\d+)[.]") - number = re.findall(padding_pattern, path).pop() - file_head = file_name.split(number)[:-1] - - frame_duration = clip_data["source_duration"] - - if is_sequence: - metadata.update({ - "isSequence": True, - "padding": padding - }) - - - otio_ex_ref_item = None - - if is_sequence: - # 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 = os.path.dirname(path) - otio_ex_ref_item = otio.schema.ImageSequenceReference( - target_url_base=dirname + os.sep, - name_prefix=file_head, - name_suffix=extension, - start_frame=frame_start, - frame_zero_padding=padding, - rate=self.fps, - available_range=create_otio_time_range( - frame_start, - frame_duration, - self.fps - ) - ) - except AttributeError: - pass - - if not otio_ex_ref_item: - reformat_path = utils.get_reformated_path(path, padded=False) - # 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, - self.fps - ) - ) - - # add metadata to otio item - # add_otio_metadata(otio_ex_ref_item, media_source, **metadata) - - return otio_ex_ref_item - - def get_marker_color(tag): icon = tag.icon() pat = r'icons:Tag(?P\w+)\.\w+' @@ -267,11 +200,82 @@ def create_otio_markers(otio_item, item): otio_item.markers.append(marker) +def create_otio_reference(clip_data): + metadata = _get_metadata(clip_data) + + # get file info for path and start frame + frame_start = 0 + path = clip_data["fpath"] + file_name = os.path.basename(path) + file_head, extension = os.path.splitext(file_name) + + # get padding and other file infos + log.debug("_ path: {}".format(path)) + + is_sequence = padding = utils.get_frame_from_path(path) + if is_sequence: + number = utils.get_frame_from_path(path) + file_head = file_name.split(number)[:-1] + frame_start = int(number) + + frame_duration = clip_data["source_duration"] + + if is_sequence: + metadata.update({ + "isSequence": True, + "padding": padding + }) + + + otio_ex_ref_item = None + + if is_sequence: + # 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 = os.path.dirname(path) + otio_ex_ref_item = otio.schema.ImageSequenceReference( + target_url_base=dirname + os.sep, + name_prefix=file_head, + name_suffix=extension, + start_frame=frame_start, + frame_zero_padding=padding, + rate=self.fps, + available_range=create_otio_time_range( + frame_start, + frame_duration, + self.fps + ) + ) + except AttributeError: + pass + + if not otio_ex_ref_item: + reformat_path = utils.get_reformated_path(path, padded=False) + # 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, + self.fps + ) + ) + + # add metadata to otio item + # add_otio_metadata(otio_ex_ref_item, media_source, **metadata) + + return otio_ex_ref_item + + def create_otio_clip(clip_data): media_reference = create_otio_reference(clip_data) + # calculate source in + first_frame = utils.get_frame_from_path(clip_data["fpath"]) or 0 + source_in = int(clip_data["source_in"]) - int(first_frame) source_range = create_otio_time_range( - clip_data["source_in"], + source_in, clip_data["record_duration"], self.fps ) @@ -282,15 +286,15 @@ def create_otio_clip(clip_data): media_reference=media_reference ) - # Add tags as markers - if self.include_tags: - create_otio_markers(otio_clip, track_item) - create_otio_markers(otio_clip, track_item.source()) + # # Add tags as markers + # if self.include_tags: + # create_otio_markers(otio_clip, track_item) + # create_otio_markers(otio_clip, track_item.source()) - # only if video - if not clip.mediaSource().hasAudio(): - # Add effects to clips - create_time_effects(otio_clip, track_item) + # # only if video + # if not clip.mediaSource().hasAudio(): + # # Add effects to clips + # create_time_effects(otio_clip, track_item) return otio_clip @@ -329,7 +333,7 @@ def _create_otio_timeline(sequence): self.seq_frame_start, self.fps) return otio.schema.Timeline( - name=sequence.name, + name=str(sequence.name)[1:-1], global_start_time=rt_start_time, metadata=metadata ) @@ -369,8 +373,6 @@ def add_otio_metadata(otio_item, media_source, **kwargs): otio_item.metadata.update({key: value}) def get_segment_attributes(segment): - log.info(segment) - if str(segment.name)[1:-1] == "": return None @@ -401,14 +403,13 @@ def get_segment_attributes(segment): # exclude timeline start frame = utils.timecode_to_frames( _value, self.fps) - clip_data[attr] = frame - self.seq_frame_start + clip_data[attr] = (frame - self.seq_frame_start) + 1 else: clip_data[attr] = utils.timecode_to_frames( _value, self.fps) clip_data["segment_timecodes"] = segment_attrs_data - log.info(pformat(clip_data)) return clip_data def create_otio_timeline(sequence): @@ -419,8 +420,10 @@ def create_otio_timeline(sequence): self.fps = float(str(sequence.frame_rate)[:-4]) self.seq_frame_start = utils.timecode_to_frames( str(sequence.start_time), self.fps) - # # convert timeline to otio + + # convert timeline to otio otio_timeline = _create_otio_timeline(sequence) + log.debug("_ otio_timeline: {}".format(otio_timeline)) # create otio tracks and clips for ver in sequence.versions: @@ -432,13 +435,27 @@ def create_otio_timeline(sequence): otio_track = create_otio_track( "video", str(track.name)[1:-1]) + all_segments = [] + for segment in track.segments: + clip_data = get_segment_attributes(segment) + if not clip_data: + continue + all_segments.append(clip_data) + segments_ordered = { - itemindex: get_segment_attributes(segment) - for itemindex, segment in enumerate( - track.segments) + itemindex: clip_data + for itemindex, clip_data in enumerate( + all_segments) } + log.debug("_ segments_ordered: {}".format( + pformat(segments_ordered) + )) + if not segments_ordered: + continue for itemindex, segment_data in segments_ordered.items(): + log.debug("_ itemindex: {}".format(itemindex)) + # Add Gap if needed if itemindex == 0: # if it is first track item at track then add @@ -449,6 +466,9 @@ def create_otio_timeline(sequence): # get previouse item prev_item = segments_ordered[itemindex - 1] + log.debug("_ segment_data: {}".format(segment_data)) + log.debug("_ prev_item: {}".format(prev_item)) + # calculate clip frame range difference from each other clip_diff = segment_data["record_in"] - prev_item["record_out"] @@ -467,6 +487,8 @@ def create_otio_timeline(sequence): otio_clip = create_otio_clip(segment_data) otio_track.append(otio_clip) + log.debug("_ otio_clip: {}".format(otio_clip)) + # create otio marker # create otio metadata diff --git a/openpype/hosts/flame/otio/utils.py b/openpype/hosts/flame/otio/utils.py index 57c8e4fecd..640a4fabfb 100644 --- a/openpype/hosts/flame/otio/utils.py +++ b/openpype/hosts/flame/otio/utils.py @@ -1,6 +1,7 @@ import re import opentimelineio as otio - +import logging +log = logging.getLogger(__name__) def timecode_to_frames(timecode, framerate): rt = otio.opentime.from_timecode(timecode, framerate) @@ -31,20 +32,25 @@ def get_reformated_path(path, padded=True): get_reformated_path("plate.1001.exr") > plate.%04d.exr """ - num_pattern = re.compile(r"[._](\d+)[.]") padding = get_padding_from_path(path) + found = get_frame_from_path(path) + + if not found: + log.info("Path is not sequence: {}".format(path)) + return path if padded: - path = re.sub(num_pattern, "%0{}d".format(padding), path) + path = path.replace(found, "%0{}d".format(padding)) else: - path = re.sub(num_pattern, "%d", path) + path = path.replace(found, "%d") + return path def get_padding_from_path(path): """ - Return padding number from DaVinci Resolve sequence path style + Return padding number from Flame path style Args: path (str): path url or simple file name @@ -56,11 +62,33 @@ def get_padding_from_path(path): get_padding_from_path("plate.0001.exr") > 4 """ - padding_pattern = re.compile(r"[._](\d+)[.]") - - found = re.findall(padding_pattern, path).pop() + found = get_frame_from_path(path) if found: return len(found) + else: + return None + +def get_frame_from_path(path): + """ + Return sequence number from Flame path style + + Args: + path (str): path url or simple file name + + Returns: + int: sequence frame number + + Example: + def get_frame_from_path(path): + ("plate.0001.exr") > 0001 + + """ + frame_pattern = re.compile(r"[._](\d+)[.]") + + found = re.findall(frame_pattern, path) + + if found: + return found.pop() else: return None \ No newline at end of file diff --git a/openpype/hosts/flame/plugins/publish/collect_test_selection.py b/openpype/hosts/flame/plugins/publish/collect_test_selection.py index cefd9ee7cf..4bb99c107b 100644 --- a/openpype/hosts/flame/plugins/publish/collect_test_selection.py +++ b/openpype/hosts/flame/plugins/publish/collect_test_selection.py @@ -1,7 +1,9 @@ import pyblish.api import openpype.hosts.flame as opflame from openpype.hosts.flame.otio import flame_export as otio_export +from openpype.hosts.flame.api import lib from pprint import pformat +reload(lib) reload(otio_export) @pyblish.api.log @@ -16,7 +18,8 @@ class CollectTestSelection(pyblish.api.ContextPlugin): def process(self, context): self.log.info(opflame.selection) - sequence = opflame.get_current_sequence(opflame.selection) + sequence = lib.get_current_sequence(opflame.selection) + otio_timeline = otio_export.create_otio_timeline(sequence) self.log.info(pformat(otio_timeline)) \ No newline at end of file From 8de80ce7737ab53af516f3b0296f3e27d6e4cd42 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 17 Dec 2021 14:35:42 +0100 Subject: [PATCH 10/21] otio export modul wip --- openpype/hosts/flame/otio/flame_export.py | 157 ++++++++++++++++++---- 1 file changed, 130 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index f01c600637..7cadd390b5 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -23,7 +23,8 @@ self.track_types = { } self.fps = None self.seq_frame_start = None - +self.project = None +self.clips = None self.marker_color_map = { "magenta": otio.schema.MarkerColor.MAGENTA, "red": otio.schema.MarkerColor.RED, @@ -45,7 +46,8 @@ def flatten(_list): def get_current_flame_project(): - return flame.project.current_project + project = flame.project.current_project + return project def create_otio_rational_time(frame, fps): @@ -205,7 +207,19 @@ def create_otio_reference(clip_data): # get file info for path and start frame frame_start = 0 + fps = self.fps + path = clip_data["fpath"] + + reel_clip = None + match_reel_clip = [ + clip for clip in self.clips + if clip["fpath"] == path + ] + if match_reel_clip: + reel_clip = match_reel_clip.pop() + fps = reel_clip["fps"] + file_name = os.path.basename(path) file_head, extension = os.path.splitext(file_name) @@ -240,11 +254,11 @@ def create_otio_reference(clip_data): name_suffix=extension, start_frame=frame_start, frame_zero_padding=padding, - rate=self.fps, + rate=fps, available_range=create_otio_time_range( frame_start, frame_duration, - self.fps + fps ) ) except AttributeError: @@ -258,12 +272,12 @@ def create_otio_reference(clip_data): available_range=create_otio_time_range( frame_start, frame_duration, - self.fps + fps ) ) # add metadata to otio item - # add_otio_metadata(otio_ex_ref_item, media_source, **metadata) + add_otio_metadata(otio_ex_ref_item, clip_data, **metadata) return otio_ex_ref_item @@ -281,7 +295,7 @@ def create_otio_clip(clip_data): ) otio_clip = otio.schema.Clip( - name=clip_data["name"], + name=clip_data["segment_name"], source_range=source_range, media_reference=media_reference ) @@ -308,9 +322,39 @@ def create_otio_gap(gap_start, clip_start, tl_start_frame, fps): ) ) +def get_clips_in_reels(project): + output_clips = [] + project_desktop = project.current_workspace.desktop + + for reel_group in project_desktop.reel_groups: + for reel in reel_group.reels: + for clip in reel.clips: + clip_data = { + "PyClip": clip, + "fps": float(str(clip.frame_rate)[:-4]) + } + + attrs = [ + "name", "width", "height", + "ratio", "sample_rate", "bit_depth" + ] + + for attr in attrs: + val = getattr(clip, attr) + clip_data[attr] = val + + version = clip.versions[-1] + track = version.tracks[-1] + for segment in track.segments: + segment_data = get_segment_attributes(segment) + clip_data.update(segment_data) + + output_clips.append(clip_data) + + return output_clips def _create_otio_timeline(sequence): - project = get_current_flame_project() + metadata = _get_metadata(sequence) metadata.update({ @@ -361,8 +405,8 @@ def add_otio_gap(clip_data, otio_track, prev_out): otio_track.append(otio_gap) -def add_otio_metadata(otio_item, media_source, **kwargs): - metadata = _get_metadata(media_source) +def add_otio_metadata(otio_item, item, **kwargs): + metadata = _get_metadata(item) # add additional metadata from kwargs if kwargs: @@ -372,20 +416,78 @@ def add_otio_metadata(otio_item, media_source, **kwargs): for key, value in metadata.items(): otio_item.metadata.update({key: value}) +def get_markers(item, segment=True): + output_markers = [] + + if segment: + segment_tl_in = item.record_in.relative_frame + + for marker in item.markers: + log.debug(marker) + start_frame = marker.location.get_value().relative_frame + + if segment: + start_frame = (start_frame - segment_tl_in) + 1 + + marker_data = { + "name": marker.name.get_value(), + "duration": marker.duration.get_value().relative_frame, + "comment": marker.comment.get_value(), + "start_frame": start_frame, + "colour": marker.colour.get_value() + } + + output_markers.append(marker_data) + + return output_markers + +def get_shot_tokens_values(clip, tokens): + old_value = None + output = {} + + shot_name = getattr(clip, "shot_name") + + if not shot_name: + return output + + old_value = shot_name.get_value() + + for token in tokens: + shot_name.set_value(token) + _key = re.sub("[ <>]", "", token) + try: + output[_key] = int(shot_name.get_value()) + except: + output[_key] = shot_name.get_value() + + shot_name.set_value(old_value) + + return output + def get_segment_attributes(segment): + # log.debug(dir(segment)) + + markers = get_markers(segment) + if str(segment.name)[1:-1] == "": return None # Add timeline segment to tree clip_data = { - "name": str(segment.name)[1:-1], - "comment": str(segment.comment)[1:-1], - "tape_name": str(segment.tape_name), - "source_name": str(segment.source_name), - "fpath": str(segment.file_path), - "segment": segment + "segment_name": segment.name.get_value(), + "segment_comment": segment.comment.get_value(), + "tape_name": segment.tape_name, + "source_name": segment.source_name, + "fpath": segment.file_path, + "PySegment": segment } + # add all available shot tokens + shot_tokens = get_shot_tokens_values(segment, [ + "", "", "", "", + ]) + clip_data.update(shot_tokens) + # populate shot source metadata segment_attrs = [ "record_duration", "record_in", "record_out", @@ -396,17 +498,12 @@ def get_segment_attributes(segment): if not hasattr(segment, attr): continue _value = getattr(segment, attr) - segment_attrs_data[attr] = _value - _value = str(_value)[1:-1] + segment_attrs_data[attr] = str(_value).replace("+", ":") if attr in ["record_in", "record_out"]: - # exclude timeline start - frame = utils.timecode_to_frames( - _value, self.fps) - clip_data[attr] = (frame - self.seq_frame_start) + 1 + clip_data[attr] = _value.relative_frame else: - clip_data[attr] = utils.timecode_to_frames( - _value, self.fps) + clip_data[attr] = _value.frame clip_data["segment_timecodes"] = segment_attrs_data @@ -416,14 +513,21 @@ def create_otio_timeline(sequence): log.info(dir(sequence)) log.info(sequence.attributes) + self.project = get_current_flame_project() + self.clips = get_clips_in_reels(self.project) + + log.debug(pformat( + self.clips + )) + # get current timeline self.fps = float(str(sequence.frame_rate)[:-4]) self.seq_frame_start = utils.timecode_to_frames( - str(sequence.start_time), self.fps) + str(sequence.start_time).replace("+", ":"), + self.fps) # convert timeline to otio otio_timeline = _create_otio_timeline(sequence) - log.debug("_ otio_timeline: {}".format(otio_timeline)) # create otio tracks and clips for ver in sequence.versions: @@ -467,7 +571,6 @@ def create_otio_timeline(sequence): prev_item = segments_ordered[itemindex - 1] log.debug("_ segment_data: {}".format(segment_data)) - log.debug("_ prev_item: {}".format(prev_item)) # calculate clip frame range difference from each other clip_diff = segment_data["record_in"] - prev_item["record_out"] From 4653de69428b4c658d6cbab99c2c676e4f1b1c22 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 17 Dec 2021 18:19:01 +0100 Subject: [PATCH 11/21] wiretap handle for metadata --- openpype/hosts/flame/api/lib.py | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index fba2d8f5c8..b7b815b373 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -290,3 +290,48 @@ def rescan_hooks(): flame.execute_shortcut('Rescan Python Hooks') except Exception: pass + + + +def get_metadata(project_name, _log=None): + import flame + + from adsk.libwiretapPythonClientAPI import ( + WireTapClient, + WireTapServerHandle, + WireTapNodeHandle, + WireTapStr, + WireTapInt + ) + + class GetProjectColorPolicy(object): + def __init__(self, host_name=None, _log=None): + # Create a connection to the Backburner manager using the Wiretap + # python API. + # + self.log = _log or log + self.host_name = host_name or "localhost" + self._wiretap_client = WireTapClient() + if not self._wiretap_client.init(): + raise Exception("Could not initialize Wiretap Client") + self._server = WireTapServerHandle( + "{}:IFFFS".format(self.host_name)) + + def process(self, project_name): + policy_node_handle = WireTapNodeHandle( + self._server, "/projects/{}/syncolor/policy".format(project_name)) + self.log.info(policy_node_handle) + + policy = WireTapStr() + if not policy_node_handle.getNodeTypeStr(policy): + self.log.warning( + "Could not retrieve policy of '%s': %s" % ( + policy_node_handle.getNodeId().id(), + policy_node_handle.lastError() + ) + ) + + return policy.c_str() + + policy_wiretap = GetProjectColorPolicy(_log=_log) + return policy_wiretap.process(project_name) \ No newline at end of file From f73b9b3822dc3769e4f20c4685b39ea8d07044ac Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 17 Dec 2021 18:25:09 +0100 Subject: [PATCH 12/21] getting projects color policy to otio timeline metadata --- openpype/hosts/flame/otio/flame_export.py | 36 +++++++++++++------ .../plugins/publish/collect_test_selection.py | 3 +- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index 7cadd390b5..e6ceafb3a6 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -353,24 +353,38 @@ def get_clips_in_reels(project): return output_clips +def _get_colourspace_policy(): + + output = {} + # get policies project path + policy_dir = "/opt/Autodesk/project/{}/synColor/policy".format( + self.project.name + ) + log.debug(policy_dir) + policy_fp = os.path.join(policy_dir, "policy.cfg") + + if not os.path.exists(policy_fp): + return output + + with open(policy_fp) as file: + dict_conf = dict(line.strip().split(' = ', 1) for line in file) + output.update( + {"openpype.flame.{}".format(k): v for k, v in dict_conf.items()} + ) + return output + def _create_otio_timeline(sequence): metadata = _get_metadata(sequence) + # find colour policy files and add them to metadata + colorspace_policy = _get_colourspace_policy() + metadata.update(colorspace_policy) + metadata.update({ "openpype.timeline.width": int(sequence.width), "openpype.timeline.height": int(sequence.height), - "openpype.timeline.pixelAspect": 1, # noqa - # "openpype.project.useOCIOEnvironmentOverride": project.useOCIOEnvironmentOverride(), # noqa - # "openpype.project.lutSetting16Bit": project.lutSetting16Bit(), - # "openpype.project.lutSetting8Bit": project.lutSetting8Bit(), - # "openpype.project.lutSettingFloat": project.lutSettingFloat(), - # "openpype.project.lutSettingLog": project.lutSettingLog(), - # "openpype.project.lutSettingViewer": project.lutSettingViewer(), - # "openpype.project.lutSettingWorkingSpace": project.lutSettingWorkingSpace(), # noqa - # "openpype.project.lutUseOCIOForExport": project.lutUseOCIOForExport(), - # "openpype.project.ocioConfigName": project.ocioConfigName(), - # "openpype.project.ocioConfigPath": project.ocioConfigPath() + "openpype.timeline.pixelAspect": 1 }) rt_start_time = create_otio_rational_time( diff --git a/openpype/hosts/flame/plugins/publish/collect_test_selection.py b/openpype/hosts/flame/plugins/publish/collect_test_selection.py index 4bb99c107b..41847ad9b6 100644 --- a/openpype/hosts/flame/plugins/publish/collect_test_selection.py +++ b/openpype/hosts/flame/plugins/publish/collect_test_selection.py @@ -6,6 +6,7 @@ from pprint import pformat reload(lib) reload(otio_export) + @pyblish.api.log class CollectTestSelection(pyblish.api.ContextPlugin): """testing selection sharing @@ -22,4 +23,4 @@ class CollectTestSelection(pyblish.api.ContextPlugin): otio_timeline = otio_export.create_otio_timeline(sequence) - self.log.info(pformat(otio_timeline)) \ No newline at end of file + self.log.info(pformat(otio_timeline)) From e7760a6508c21074420ca08d39204afd57ec62b3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 20 Dec 2021 15:08:29 +0100 Subject: [PATCH 13/21] flame otio export module update --- openpype/hosts/flame/otio/flame_export.py | 296 +++++++++++----------- 1 file changed, 149 insertions(+), 147 deletions(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index e6ceafb3a6..7a2e5744e8 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -4,7 +4,7 @@ import os import re import sys -import ast +import json import logging import opentimelineio as otio from . import utils @@ -12,7 +12,7 @@ from . import utils import flame from pprint import pformat -reload(utils) +reload(utils) # noqa log = logging.getLogger(__name__) @@ -31,9 +31,14 @@ self.marker_color_map = { "yellow": otio.schema.MarkerColor.YELLOW, "green": otio.schema.MarkerColor.GREEN, "cyan": otio.schema.MarkerColor.CYAN, + "white": otio.schema.MarkerColor.WHITE, + "orange": otio.schema.MarkerColor.ORANGE, "blue": otio.schema.MarkerColor.BLUE, + "purple": otio.schema.MarkerColor.PURPLE, + "pink": otio.schema.MarkerColor.PINK, + "black": otio.schema.MarkerColor.BLACK, } -self.include_tags = True +self.include_markers = True def flatten(_list): @@ -63,6 +68,7 @@ def create_otio_time_range(start_frame, frame_duration, fps): duration=create_otio_rational_time(frame_duration, fps) ) + def _get_metadata(item): if hasattr(item, 'metadata'): if not item.metadata: @@ -71,130 +77,150 @@ def _get_metadata(item): return {} -def create_time_effects(otio_clip, track_item): +def create_time_effects(otio_clip, item): + # todo #2426: add retiming effects to export + pass # get all subtrack items - subTrackItems = flatten(track_item.parent().subTrackItems()) - speed = track_item.playbackSpeed() + # subTrackItems = flatten(track_item.parent().subTrackItems()) + # speed = track_item.playbackSpeed() - otio_effect = None - # retime on track item - if speed != 1.: - # make effect - otio_effect = otio.schema.LinearTimeWarp() - otio_effect.name = "Speed" - otio_effect.time_scalar = speed - otio_effect.metadata = {} + # otio_effect = None + # # retime on track item + # if speed != 1.: + # # make effect + # otio_effect = otio.schema.LinearTimeWarp() + # otio_effect.name = "Speed" + # otio_effect.time_scalar = speed + # otio_effect.metadata = {} - # freeze frame effect - if speed == 0.: - otio_effect = otio.schema.FreezeFrame() - otio_effect.name = "FreezeFrame" - otio_effect.metadata = {} + # # freeze frame effect + # if speed == 0.: + # otio_effect = otio.schema.FreezeFrame() + # otio_effect.name = "FreezeFrame" + # otio_effect.metadata = {} - if otio_effect: - # add otio effect to clip effects - otio_clip.effects.append(otio_effect) + # if otio_effect: + # # add otio effect to clip effects + # otio_clip.effects.append(otio_effect) - # loop trought and get all Timewarps - for effect in subTrackItems: - if ((track_item not in effect.linkedItems()) - and (len(effect.linkedItems()) > 0)): - continue - # avoid all effect which are not TimeWarp and disabled - if "TimeWarp" not in effect.name(): - continue + # # loop trought and get all Timewarps + # for effect in subTrackItems: + # if ((track_item not in effect.linkedItems()) + # and (len(effect.linkedItems()) > 0)): + # continue + # # avoid all effect which are not TimeWarp and disabled + # if "TimeWarp" not in effect.name(): + # continue - if not effect.isEnabled(): - continue + # if not effect.isEnabled(): + # continue - node = effect.node() - name = node["name"].value() + # node = effect.node() + # name = node["name"].value() - # solve effect class as effect name - _name = effect.name() - if "_" in _name: - effect_name = re.sub(r"(?:_)[_0-9]+", "", _name) # more numbers - else: - effect_name = re.sub(r"\d+", "", _name) # one number + # # solve effect class as effect name + # _name = effect.name() + # if "_" in _name: + # effect_name = re.sub(r"(?:_)[_0-9]+", "", _name) # more numbers + # else: + # effect_name = re.sub(r"\d+", "", _name) # one number - metadata = {} - # add knob to metadata - for knob in ["lookup", "length"]: - value = node[knob].value() - animated = node[knob].isAnimated() - if animated: - value = [ - ((node[knob].getValueAt(i)) - i) - for i in range( - track_item.timelineIn(), track_item.timelineOut() + 1) - ] + # metadata = {} + # # add knob to metadata + # for knob in ["lookup", "length"]: + # value = node[knob].value() + # animated = node[knob].isAnimated() + # if animated: + # value = [ + # ((node[knob].getValueAt(i)) - i) + # for i in range( + # track_item.timelineIn(), track_item.timelineOut() + 1) + # ] - metadata[knob] = value + # metadata[knob] = value - # make effect - otio_effect = otio.schema.TimeEffect() - otio_effect.name = name - otio_effect.effect_name = effect_name - otio_effect.metadata = metadata + # # make effect + # otio_effect = otio.schema.TimeEffect() + # otio_effect.name = name + # otio_effect.effect_name = effect_name + # otio_effect.metadata = metadata - # add otio effect to clip effects - otio_clip.effects.append(otio_effect) + # # add otio effect to clip effects + # otio_clip.effects.append(otio_effect) -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()] +def _get_marker_color(flame_colour): + if flame_colour in self.marker_color_map: + return self.marker_color_map[flame_colour] return otio.schema.MarkerColor.RED +def _get_flame_markers(item): + output_markers = [] + + time_in = item.record_in.relative_frame + + for marker in item.markers: + log.debug(marker) + start_frame = marker.location.get_value().relative_frame + + start_frame = (start_frame - time_in) + 1 + + marker_data = { + "name": marker.name.get_value(), + "duration": marker.duration.get_value().relative_frame, + "comment": marker.comment.get_value(), + "start_frame": start_frame, + "colour": marker.colour.get_value() + } + + output_markers.append(marker_data) + + return output_markers + + def create_otio_markers(otio_item, item): - for tag in item.tags(): - if not tag.visible(): - continue - - if tag.name() == 'Copy': - # Hiero adds this tag to a lot of clips - continue - - frame_rate = utils.get_rate(item) or self.fps + markers = _get_flame_markers(item) + for marker in markers: + frame_rate = self.fps marked_range = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( - tag.inTime(), + marker["start_frame"], frame_rate ), duration=otio.opentime.RationalTime( - int(tag.metadata().dict().get('tag.length', '0')), + marker["duration"], frame_rate ) ) - # add tag metadata but remove "tag." string + + # testing the comment if it is not containing json string + check_if_json = re.findall( + re.compile(r"[{:}]"), + marker["comment"] + ) + + # to identify this as json, at least 3 items in the list should + # be present ["{", ":", "}"] metadata = {} - - for key, value in tag.metadata().dict().items(): - _key = key.replace("tag.", "") - + if len(check_if_json) >= 3: + # this is json string try: # capture exceptions which are related to strings only - _value = ast.literal_eval(value) - except (ValueError, SyntaxError): - _value = value - - metadata.update({_key: _value}) - - # Store the source item for future import assignment - metadata['hiero_source_type'] = item.__class__.__name__ + metadata.update( + json.loads(marker["comment"]) + ) + except ValueError as msg: + log.error("Marker json conversion: {}".format(msg)) + else: + metadata["comment"] = marker["comment"] marker = otio.schema.Marker( - name=tag.name(), - color=get_marker_color(tag), + name=marker["name"], + color=_get_marker_color( + marker["colour"]), marked_range=marked_range, metadata=metadata ) @@ -240,7 +266,6 @@ def create_otio_reference(clip_data): "padding": padding }) - otio_ex_ref_item = None if is_sequence: @@ -283,11 +308,16 @@ def create_otio_reference(clip_data): def create_otio_clip(clip_data): + segment = clip_data["PySegment"] + # create media reference media_reference = create_otio_reference(clip_data) + # calculate source in first_frame = utils.get_frame_from_path(clip_data["fpath"]) or 0 source_in = int(clip_data["source_in"]) - int(first_frame) + + # creatae source range source_range = create_otio_time_range( source_in, clip_data["record_duration"], @@ -300,15 +330,9 @@ def create_otio_clip(clip_data): media_reference=media_reference ) - # # Add tags as markers - # if self.include_tags: - # create_otio_markers(otio_clip, track_item) - # create_otio_markers(otio_clip, track_item.source()) - - # # only if video - # if not clip.mediaSource().hasAudio(): - # # Add effects to clips - # create_time_effects(otio_clip, track_item) + # Add markers + if self.include_markers: + create_otio_markers(otio_clip, segment) return otio_clip @@ -322,6 +346,7 @@ def create_otio_gap(gap_start, clip_start, tl_start_frame, fps): ) ) + def get_clips_in_reels(project): output_clips = [] project_desktop = project.current_workspace.desktop @@ -346,13 +371,14 @@ def get_clips_in_reels(project): version = clip.versions[-1] track = version.tracks[-1] for segment in track.segments: - segment_data = get_segment_attributes(segment) + segment_data = _get_segment_attributes(segment) clip_data.update(segment_data) output_clips.append(clip_data) return output_clips + def _get_colourspace_policy(): output = {} @@ -373,6 +399,7 @@ def _get_colourspace_policy(): ) return output + def _create_otio_timeline(sequence): metadata = _get_metadata(sequence) @@ -430,58 +457,32 @@ def add_otio_metadata(otio_item, item, **kwargs): for key, value in metadata.items(): otio_item.metadata.update({key: value}) -def get_markers(item, segment=True): - output_markers = [] - if segment: - segment_tl_in = item.record_in.relative_frame - - for marker in item.markers: - log.debug(marker) - start_frame = marker.location.get_value().relative_frame - - if segment: - start_frame = (start_frame - segment_tl_in) + 1 - - marker_data = { - "name": marker.name.get_value(), - "duration": marker.duration.get_value().relative_frame, - "comment": marker.comment.get_value(), - "start_frame": start_frame, - "colour": marker.colour.get_value() - } - - output_markers.append(marker_data) - - return output_markers - -def get_shot_tokens_values(clip, tokens): +def _get_shot_tokens_values(clip, tokens): old_value = None output = {} - shot_name = getattr(clip, "shot_name") - - if not shot_name: + if not clip.shot_name: return output - old_value = shot_name.get_value() + old_value = clip.shot_name.get_value() for token in tokens: - shot_name.set_value(token) + clip.shot_name.set_value(token) _key = re.sub("[ <>]", "", token) - try: - output[_key] = int(shot_name.get_value()) - except: - output[_key] = shot_name.get_value() - shot_name.set_value(old_value) + try: + output[_key] = int(clip.shot_name.get_value()) + except TypeError: + output[_key] = clip.shot_name.get_value() + + clip.shot_name.set_value(old_value) return output -def get_segment_attributes(segment): - # log.debug(dir(segment)) - markers = get_markers(segment) +def _get_segment_attributes(segment): + # log.debug(dir(segment)) if str(segment.name)[1:-1] == "": return None @@ -497,7 +498,7 @@ def get_segment_attributes(segment): } # add all available shot tokens - shot_tokens = get_shot_tokens_values(segment, [ + shot_tokens = _get_shot_tokens_values(segment, [ "", "", "", "", ]) clip_data.update(shot_tokens) @@ -523,6 +524,7 @@ def get_segment_attributes(segment): return clip_data + def create_otio_timeline(sequence): log.info(dir(sequence)) log.info(sequence.attributes) @@ -537,8 +539,8 @@ def create_otio_timeline(sequence): # get current timeline self.fps = float(str(sequence.frame_rate)[:-4]) self.seq_frame_start = utils.timecode_to_frames( - str(sequence.start_time).replace("+", ":"), - self.fps) + str(sequence.start_time).replace("+", ":"), + self.fps) # convert timeline to otio otio_timeline = _create_otio_timeline(sequence) @@ -555,7 +557,7 @@ def create_otio_timeline(sequence): all_segments = [] for segment in track.segments: - clip_data = get_segment_attributes(segment) + clip_data = _get_segment_attributes(segment) if not clip_data: continue all_segments.append(clip_data) From 54b5dd8d61c18e88391e07d30b0cfa41d7a56214 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 20 Dec 2021 15:35:08 +0100 Subject: [PATCH 14/21] pep8 comply --- openpype/hosts/flame/api/lib.py | 11 +++++------ openpype/hosts/flame/api/menu.py | 2 ++ openpype/hosts/flame/otio/flame_export.py | 3 ++- openpype/hosts/flame/otio/utils.py | 5 +++-- .../flame/plugins/publish/collect_test_selection.py | 4 ++-- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index b7b815b373..80185579d6 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -258,6 +258,7 @@ def get_current_project(): def get_current_sequence(selection): import flame + def segment_to_sequence(_segment): track = _segment.parent version = track.parent @@ -291,11 +292,7 @@ def rescan_hooks(): except Exception: pass - - def get_metadata(project_name, _log=None): - import flame - from adsk.libwiretapPythonClientAPI import ( WireTapClient, WireTapServerHandle, @@ -319,7 +316,9 @@ def get_metadata(project_name, _log=None): def process(self, project_name): policy_node_handle = WireTapNodeHandle( - self._server, "/projects/{}/syncolor/policy".format(project_name)) + self._server, + "/projects/{}/syncolor/policy".format(project_name) + ) self.log.info(policy_node_handle) policy = WireTapStr() @@ -334,4 +333,4 @@ def get_metadata(project_name, _log=None): return policy.c_str() policy_wiretap = GetProjectColorPolicy(_log=_log) - return policy_wiretap.process(project_name) \ No newline at end of file + return policy_wiretap.process(project_name) diff --git a/openpype/hosts/flame/api/menu.py b/openpype/hosts/flame/api/menu.py index f216428e18..893c7a21d0 100644 --- a/openpype/hosts/flame/api/menu.py +++ b/openpype/hosts/flame/api/menu.py @@ -24,12 +24,14 @@ default_flame_export_presets = { } } + def send_selection(selection): import openpype.hosts.flame as opflame opflame.selection = selection print(opflame.selection) return True + class _FlameMenuApp(object): def __init__(self, framework): self.name = self.__class__.__name__ diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index 7a2e5744e8..a9be495bb5 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -134,7 +134,8 @@ def create_time_effects(otio_clip, item): # value = [ # ((node[knob].getValueAt(i)) - i) # for i in range( - # track_item.timelineIn(), track_item.timelineOut() + 1) + # track_item.timelineIn(), + # track_item.timelineOut() + 1) # ] # metadata[knob] = value diff --git a/openpype/hosts/flame/otio/utils.py b/openpype/hosts/flame/otio/utils.py index 640a4fabfb..74963bdd73 100644 --- a/openpype/hosts/flame/otio/utils.py +++ b/openpype/hosts/flame/otio/utils.py @@ -3,6 +3,7 @@ import opentimelineio as otio import logging log = logging.getLogger(__name__) + def timecode_to_frames(timecode, framerate): rt = otio.opentime.from_timecode(timecode, framerate) return int(otio.opentime.to_frames(rt)) @@ -44,7 +45,6 @@ def get_reformated_path(path, padded=True): else: path = path.replace(found, "%d") - return path @@ -69,6 +69,7 @@ def get_padding_from_path(path): else: return None + def get_frame_from_path(path): """ Return sequence number from Flame path style @@ -91,4 +92,4 @@ def get_frame_from_path(path): if found: return found.pop() else: - return None \ No newline at end of file + return None diff --git a/openpype/hosts/flame/plugins/publish/collect_test_selection.py b/openpype/hosts/flame/plugins/publish/collect_test_selection.py index 41847ad9b6..9a80a92414 100644 --- a/openpype/hosts/flame/plugins/publish/collect_test_selection.py +++ b/openpype/hosts/flame/plugins/publish/collect_test_selection.py @@ -3,8 +3,8 @@ import openpype.hosts.flame as opflame from openpype.hosts.flame.otio import flame_export as otio_export from openpype.hosts.flame.api import lib from pprint import pformat -reload(lib) -reload(otio_export) +reload(lib) # noqa +reload(otio_export) # noqa @pyblish.api.log From ab4bc22616aa6a552086b5ef597e1a398bbf24b1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 20 Dec 2021 15:38:15 +0100 Subject: [PATCH 15/21] pep8 comply --- openpype/hosts/flame/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 80185579d6..96bffab774 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -292,13 +292,13 @@ def rescan_hooks(): except Exception: pass + def get_metadata(project_name, _log=None): from adsk.libwiretapPythonClientAPI import ( WireTapClient, WireTapServerHandle, WireTapNodeHandle, - WireTapStr, - WireTapInt + WireTapStr ) class GetProjectColorPolicy(object): From 5fed85f647c430161ba99f93684a8039ee4ee5a2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 20 Dec 2021 16:04:49 +0100 Subject: [PATCH 16/21] typo --- openpype/hosts/flame/otio/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/otio/utils.py b/openpype/hosts/flame/otio/utils.py index 74963bdd73..229946343b 100644 --- a/openpype/hosts/flame/otio/utils.py +++ b/openpype/hosts/flame/otio/utils.py @@ -14,7 +14,7 @@ def frames_to_timecode(frames, framerate): return otio.opentime.to_timecode(rt) -def frames_to_secons(frames, framerate): +def frames_to_seconds(frames, framerate): rt = otio.opentime.from_frames(frames, framerate) return otio.opentime.to_seconds(rt) From 9802d01b0b2798e95e3ec947128edb85682e37af Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 20 Dec 2021 17:30:44 +0100 Subject: [PATCH 17/21] removing self from module and replacing with context class --- openpype/hosts/flame/otio/flame_export.py | 80 ++++++++++++++++------- 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index a9be495bb5..c149d12d70 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -16,16 +16,12 @@ reload(utils) # noqa log = logging.getLogger(__name__) -self = sys.modules[__name__] -self.track_types = { + +TRACK_TYPES = { "video": otio.schema.TrackKind.Video, "audio": otio.schema.TrackKind.Audio } -self.fps = None -self.seq_frame_start = None -self.project = None -self.clips = None -self.marker_color_map = { +MARKERS_COLOR_MAP = { "magenta": otio.schema.MarkerColor.MAGENTA, "red": otio.schema.MarkerColor.RED, "yellow": otio.schema.MarkerColor.YELLOW, @@ -38,7 +34,37 @@ self.marker_color_map = { "pink": otio.schema.MarkerColor.PINK, "black": otio.schema.MarkerColor.BLACK, } -self.include_markers = True +MARKERS_INCLUDE = True + + +class CTX: + _fps = None + _tl_start_frame = None + project = None + clips = None + + @classmethod + def set_fps(cls, new_fps): + if not isinstance(new_fps, float): + raise TypeError("Invalid fps type {}".format(type(new_fps))) + if cls._fps != new_fps: + cls._fps = new_fps + + @classmethod + def get_fps(cls): + return cls._fps + + @classmethod + def set_tl_start_frame(cls, number): + if not isinstance(number, int): + raise TypeError("Invalid timeline start frame type {}".format( + type(number))) + if cls._tl_start_frame != number: + cls._tl_start_frame = number + + @classmethod + def get_tl_start_frame(cls): + return cls._tl_start_frame def flatten(_list): @@ -151,8 +177,8 @@ def create_time_effects(otio_clip, item): def _get_marker_color(flame_colour): - if flame_colour in self.marker_color_map: - return self.marker_color_map[flame_colour] + if flame_colour in MARKERS_COLOR_MAP: + return MARKERS_COLOR_MAP[flame_colour] return otio.schema.MarkerColor.RED @@ -184,7 +210,7 @@ def _get_flame_markers(item): def create_otio_markers(otio_item, item): markers = _get_flame_markers(item) for marker in markers: - frame_rate = self.fps + frame_rate = CTX.get_fps() marked_range = otio.opentime.TimeRange( start_time=otio.opentime.RationalTime( @@ -234,13 +260,13 @@ def create_otio_reference(clip_data): # get file info for path and start frame frame_start = 0 - fps = self.fps + fps = CTX.get_fps() path = clip_data["fpath"] reel_clip = None match_reel_clip = [ - clip for clip in self.clips + clip for clip in CTX.clips if clip["fpath"] == path ] if match_reel_clip: @@ -322,7 +348,7 @@ def create_otio_clip(clip_data): source_range = create_otio_time_range( source_in, clip_data["record_duration"], - self.fps + CTX.get_fps() ) otio_clip = otio.schema.Clip( @@ -332,7 +358,7 @@ def create_otio_clip(clip_data): ) # Add markers - if self.include_markers: + if MARKERS_INCLUDE: create_otio_markers(otio_clip, segment) return otio_clip @@ -385,7 +411,7 @@ def _get_colourspace_policy(): output = {} # get policies project path policy_dir = "/opt/Autodesk/project/{}/synColor/policy".format( - self.project.name + CTX.project.name ) log.debug(policy_dir) policy_fp = os.path.join(policy_dir, "policy.cfg") @@ -416,7 +442,7 @@ def _create_otio_timeline(sequence): }) rt_start_time = create_otio_rational_time( - self.seq_frame_start, self.fps) + CTX.get_tl_start_frame(), CTX.get_fps()) return otio.schema.Timeline( name=str(sequence.name)[1:-1], @@ -428,7 +454,7 @@ def _create_otio_timeline(sequence): def create_otio_track(track_type, track_name): return otio.schema.Track( name=track_name, - kind=self.track_types[track_type] + kind=TRACK_TYPES[track_type] ) @@ -440,7 +466,7 @@ def add_otio_gap(clip_data, otio_track, prev_out): gap = otio.opentime.TimeRange( duration=otio.opentime.RationalTime( gap_length, - self.fps + CTX.get_fps() ) ) otio_gap = otio.schema.Gap(source_range=gap) @@ -530,18 +556,22 @@ def create_otio_timeline(sequence): log.info(dir(sequence)) log.info(sequence.attributes) - self.project = get_current_flame_project() - self.clips = get_clips_in_reels(self.project) + CTX.project = get_current_flame_project() + CTX.clips = get_clips_in_reels(CTX.project) log.debug(pformat( - self.clips + CTX.clips )) # get current timeline - self.fps = float(str(sequence.frame_rate)[:-4]) - self.seq_frame_start = utils.timecode_to_frames( + CTX.set_fps( + float(str(sequence.frame_rate)[:-4])) + + tl_start_frame = utils.timecode_to_frames( str(sequence.start_time).replace("+", ":"), - self.fps) + CTX.get_fps() + ) + CTX.set_tl_start_frame(tl_start_frame) # convert timeline to otio otio_timeline = _create_otio_timeline(sequence) From 25c6ce08e4965e60da35b7359d73fce4bf011477 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 20 Dec 2021 18:06:42 +0100 Subject: [PATCH 18/21] fix markers creation and token conversion --- openpype/hosts/flame/otio/flame_export.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index c149d12d70..6897c4d818 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -244,7 +244,7 @@ def create_otio_markers(otio_item, item): else: metadata["comment"] = marker["comment"] - marker = otio.schema.Marker( + otio_marker = otio.schema.Marker( name=marker["name"], color=_get_marker_color( marker["colour"]), @@ -252,7 +252,7 @@ def create_otio_markers(otio_item, item): metadata=metadata ) - otio_item.markers.append(marker) + otio_item.markers.append(otio_marker) def create_otio_reference(clip_data): @@ -500,7 +500,7 @@ def _get_shot_tokens_values(clip, tokens): try: output[_key] = int(clip.shot_name.get_value()) - except TypeError: + except ValueError: output[_key] = clip.shot_name.get_value() clip.shot_name.set_value(old_value) From 897cc0946fbd5719f77d98c46eac7c2eb77622f6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Dec 2021 10:41:52 +0100 Subject: [PATCH 19/21] color schemas --- openpype/hosts/flame/otio/flame_export.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index 6897c4d818..356cf1b49e 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -22,17 +22,17 @@ TRACK_TYPES = { "audio": otio.schema.TrackKind.Audio } MARKERS_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, - "white": otio.schema.MarkerColor.WHITE, - "orange": otio.schema.MarkerColor.ORANGE, - "blue": otio.schema.MarkerColor.BLUE, - "purple": otio.schema.MarkerColor.PURPLE, - "pink": otio.schema.MarkerColor.PINK, - "black": otio.schema.MarkerColor.BLACK, + (1.0, 0.0, 0.0): otio.schema.MarkerColor.RED, + (1.0, 0.5, 0.0): otio.schema.MarkerColor.ORANGE, + (1.0, 1.0, 0.0): otio.schema.MarkerColor.YELLOW, + (1.0, 0.5, 1.0): otio.schema.MarkerColor.PINK, + (1.0, 1.0, 1.0): otio.schema.MarkerColor.WHITE, + (0.0, 1.0, 0.0): otio.schema.MarkerColor.GREEN, + (0.0, 1.0, 1.0): otio.schema.MarkerColor.CYAN, + (0.0, 0.0, 1.0): otio.schema.MarkerColor.BLUE, + (0.5, 0.0, 0.5): otio.schema.MarkerColor.PURPLE, + (0.5, 0.0, 1.0): otio.schema.MarkerColor.MAGENTA, + (0.0, 0.0, 0.0): otio.schema.MarkerColor.BLACK } MARKERS_INCLUDE = True From 5d611395dfc34dd40192f6c3faf97f026c3bd042 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 4 Jan 2022 12:21:34 +0100 Subject: [PATCH 20/21] improve handling with selection --- openpype/hosts/flame/api/menu.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/flame/api/menu.py b/openpype/hosts/flame/api/menu.py index 893c7a21d0..fef6dbfa35 100644 --- a/openpype/hosts/flame/api/menu.py +++ b/openpype/hosts/flame/api/menu.py @@ -25,11 +25,11 @@ default_flame_export_presets = { } -def send_selection(selection): +def callback_selection(selection, function): import openpype.hosts.flame as opflame opflame.selection = selection print(opflame.selection) - return True + function() class _FlameMenuApp(object): @@ -103,9 +103,6 @@ class FlameMenuProjectConnect(_FlameMenuApp): if not self.flame: return [] - flame_project_name = self.flame_project_name - self.log.info("______ {} ______".format(flame_project_name)) - menu = deepcopy(self.menu) menu['actions'].append({ @@ -114,13 +111,13 @@ class FlameMenuProjectConnect(_FlameMenuApp): }) menu['actions'].append({ "name": "Create ...", - "isVisible": send_selection, - "execute": lambda x: self.tools_helper.show_creator() + "execute": lambda x: callback_selection( + x, self.tools_helper.show_creator) }) menu['actions'].append({ "name": "Publish ...", - "isVisible": send_selection, - "execute": lambda x: self.tools_helper.show_publish() + "execute": lambda x: callback_selection( + x, self.tools_helper.show_publish) }) menu['actions'].append({ "name": "Load ...", @@ -170,20 +167,17 @@ class FlameMenuTimeline(_FlameMenuApp): if not self.flame: return [] - flame_project_name = self.flame_project_name - self.log.info("______ {} ______".format(flame_project_name)) - menu = deepcopy(self.menu) menu['actions'].append({ "name": "Create ...", - "isVisible": send_selection, - "execute": lambda x: self.tools_helper.show_creator() + "execute": lambda x: callback_selection( + x, self.tools_helper.show_creator) }) menu['actions'].append({ "name": "Publish ...", - "isVisible": send_selection, - "execute": lambda x: self.tools_helper.show_publish() + "execute": lambda x: callback_selection( + x, self.tools_helper.show_publish) }) menu['actions'].append({ "name": "Load ...", From 31ab8e51f88e559fadade204c247682a0a7fbf3a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 4 Jan 2022 15:31:28 +0100 Subject: [PATCH 21/21] marker colors picking correctly --- openpype/hosts/flame/otio/flame_export.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index 356cf1b49e..aea1f387e8 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -3,7 +3,6 @@ import os import re -import sys import json import logging import opentimelineio as otio @@ -52,7 +51,7 @@ class CTX: @classmethod def get_fps(cls): - return cls._fps + return cls._fps @classmethod def set_tl_start_frame(cls, number): @@ -64,7 +63,7 @@ class CTX: @classmethod def get_tl_start_frame(cls): - return cls._tl_start_frame + return cls._tl_start_frame def flatten(_list): @@ -105,7 +104,6 @@ def _get_metadata(item): def create_time_effects(otio_clip, item): # todo #2426: add retiming effects to export - pass # get all subtrack items # subTrackItems = flatten(track_item.parent().subTrackItems()) # speed = track_item.playbackSpeed() @@ -174,11 +172,18 @@ def create_time_effects(otio_clip, item): # # add otio effect to clip effects # otio_clip.effects.append(otio_effect) + pass def _get_marker_color(flame_colour): - if flame_colour in MARKERS_COLOR_MAP: - return MARKERS_COLOR_MAP[flame_colour] + # clamp colors to closes half numbers + _flame_colour = [ + (lambda x: round(x * 2) / 2)(c) + for c in flame_colour] + + for color, otio_color_type in MARKERS_COLOR_MAP.items(): + if _flame_colour == list(color): + return otio_color_type return otio.schema.MarkerColor.RED