Hiero: final changes to otio import export modules

This commit is contained in:
Jakub Jezek 2021-04-08 20:57:47 +02:00
parent d5e4c1ee0c
commit 2db6bb534b
No known key found for this signature in database
GPG key ID: C4B96E101D2A47F3
7 changed files with 58 additions and 448 deletions

View file

@ -4,10 +4,12 @@
import os
import re
import sys
import ast
import opentimelineio as otio
from . import utils
import hiero.core
import hiero.ui
reload(utils)
self = sys.modules[__name__]
self.track_types = {
@ -43,7 +45,7 @@ def create_otio_time_range(start_frame, frame_duration, fps):
def _get_metadata(item):
if hasattr(item, 'metadata'):
return {key: value for key, value in item.metadata().items()}
return {key: value for key, value in dict(item.metadata()).items()}
return {}
@ -61,7 +63,7 @@ def create_otio_reference(clip):
file_head = media_source.filenameHead()
is_sequence = not media_source.singleFile()
frame_duration = media_source.duration()
fps = utils.get_rate(clip)
fps = utils.get_rate(clip) or self.project_fps
extension = os.path.splitext(path)[-1]
if is_sequence:
@ -70,6 +72,13 @@ def create_otio_reference(clip):
"padding": padding
})
# add resolution metadata
metadata.update({
"width": int(media_source.width()),
"height": int(media_source.height()),
"pixelAspect": float(media_source.pixelAspect())
})
otio_ex_ref_item = None
if is_sequence:
@ -133,7 +142,7 @@ def create_otio_markers(otio_item, item):
# Hiero adds this tag to a lot of clips
continue
frame_rate = utils.get_rate(item)
frame_rate = utils.get_rate(item) or self.project_fps
marked_range = otio.opentime.TimeRange(
start_time=otio.opentime.RationalTime(
@ -145,12 +154,22 @@ def create_otio_markers(otio_item, item):
frame_rate
)
)
# add tag metadata but remove "tag." string
metadata = {}
for key, value in tag.metadata().dict().items():
_key = key.replace("tag.", "")
try:
# capture exceptions which are related to strings only
_value = ast.literal_eval(value)
except (ValueError, SyntaxError):
_value = value
metadata.update({_key: _value})
metadata = dict(
Hiero=tag.metadata().dict()
)
# Store the source item for future import assignment
metadata['Hiero']['source_type'] = item.__class__.__name__
metadata['hiero_source_type'] = item.__class__.__name__
marker = otio.schema.Marker(
name=tag.name(),
@ -166,7 +185,7 @@ def create_otio_clip(track_item):
clip = track_item.source()
source_in = track_item.sourceIn()
duration = track_item.sourceDuration()
fps = utils.get_rate(track_item)
fps = utils.get_rate(track_item) or self.project_fps
name = track_item.name()
media_reference = create_otio_reference(clip)
@ -212,29 +231,6 @@ def _create_otio_timeline():
)
def _get_metadata_media_pool_item(media_pool_item):
data = dict()
data.update({k: v for k, v in media_pool_item.GetMetadata().items()})
property = media_pool_item.GetClipProperty() or {}
for name, value in property.items():
if "Resolution" in name and "" != value:
width, height = value.split("x")
data.update({
"width": int(width),
"height": int(height)
})
if "PAR" in name and "" != value:
try:
data.update({"pixelAspect": float(value)})
except ValueError:
if "Square" in value:
data.update({"pixelAspect": float(1)})
else:
data.update({"pixelAspect": float(1)})
return data
def create_otio_track(track_type, track_name):
return otio.schema.Track(
name=track_name,
@ -271,6 +267,7 @@ def add_otio_metadata(otio_item, media_source, **kwargs):
def create_otio_timeline():
print(">>>>>> self.include_tags: {}".format(self.include_tags))
# get current timeline
self.timeline = hiero.ui.activeSequence()
self.project_fps = self.timeline.framerate().toFloat()
@ -278,12 +275,8 @@ def create_otio_timeline():
# convert timeline to otio
otio_timeline = _create_otio_timeline()
# Add tags as markers
if self.include_tags:
create_otio_markers(otio_timeline, self.timeline)
# loop all defined track types
for track in self.hiero_sequence.items():
for track in self.timeline.items():
# skip if track is disabled
if not track.isEnabled():
continue

View file

@ -1,386 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = "Daniel Flehner Heen"
__credits__ = ["Jakub Jezek", "Daniel Flehner Heen"]
import os
import sys
import re
import hiero.core
import hiero.ui
import opentimelineio as otio
# build modul class
self = sys.modules[__name__]
self.marker_color_map = {
"magenta": otio.schema.MarkerColor.MAGENTA,
"red": otio.schema.MarkerColor.RED,
"yellow": otio.schema.MarkerColor.YELLOW,
"green": otio.schema.MarkerColor.GREEN,
"cyan": otio.schema.MarkerColor.CYAN,
"blue": otio.schema.MarkerColor.BLUE,
}
self.hiero_sequence = None
self.include_tags = None
def get_rate(item):
if not hasattr(item, 'framerate'):
item = item.sequence()
num, den = item.framerate().toRational()
rate = float(num) / float(den)
if rate.is_integer():
return rate
return round(rate, 2)
def get_clip_ranges(trackitem):
# Get rate from source or sequence
if trackitem.source().mediaSource().hasVideo():
rate_item = trackitem.source()
else:
rate_item = trackitem.sequence()
source_rate = get_rate(rate_item)
# Reversed video/audio
if trackitem.playbackSpeed() < 0:
start = trackitem.sourceOut()
else:
start = trackitem.sourceIn()
source_start_time = otio.opentime.RationalTime(
start,
source_rate
)
source_duration = otio.opentime.RationalTime(
trackitem.duration(),
source_rate
)
source_range = otio.opentime.TimeRange(
start_time=source_start_time,
duration=source_duration
)
hiero_clip = trackitem.source()
available_range = None
if hiero_clip.mediaSource().isMediaPresent():
start_time = otio.opentime.RationalTime(
hiero_clip.mediaSource().startTime(),
source_rate
)
duration = otio.opentime.RationalTime(
hiero_clip.mediaSource().duration(),
source_rate
)
available_range = otio.opentime.TimeRange(
start_time=start_time,
duration=duration
)
return source_range, available_range
def add_gap(trackitem, otio_track, prev_out):
gap_length = trackitem.timelineIn() - prev_out
if prev_out != 0:
gap_length -= 1
rate = get_rate(trackitem.sequence())
gap = otio.opentime.TimeRange(
duration=otio.opentime.RationalTime(
gap_length,
rate
)
)
otio_gap = otio.schema.Gap(source_range=gap)
otio_track.append(otio_gap)
def get_marker_color(tag):
icon = tag.icon()
pat = r'icons:Tag(?P<color>\w+)\.\w+'
res = re.search(pat, icon)
if res:
color = res.groupdict().get('color')
if color.lower() in self.marker_color_map:
return self.marker_color_map[color.lower()]
return otio.schema.MarkerColor.RED
def add_markers(hiero_item, otio_item):
for tag in hiero_item.tags():
if not tag.visible():
continue
if tag.name() == 'Copy':
# Hiero adds this tag to a lot of clips
continue
frame_rate = get_rate(hiero_item)
marked_range = otio.opentime.TimeRange(
start_time=otio.opentime.RationalTime(
tag.inTime(),
frame_rate
),
duration=otio.opentime.RationalTime(
int(tag.metadata().dict().get('tag.length', '0')),
frame_rate
)
)
metadata = dict(
Hiero=tag.metadata().dict()
)
# Store the source item for future import assignment
metadata['Hiero']['source_type'] = hiero_item.__class__.__name__
marker = otio.schema.Marker(
name=tag.name(),
color=get_marker_color(tag),
marked_range=marked_range,
metadata=metadata
)
otio_item.markers.append(marker)
def add_clip(trackitem, otio_track, itemindex):
hiero_clip = trackitem.source()
# Add Gap if needed
if itemindex == 0:
prev_item = trackitem
else:
prev_item = trackitem.parent().items()[itemindex - 1]
clip_diff = trackitem.timelineIn() - prev_item.timelineOut()
if itemindex == 0 and trackitem.timelineIn() > 0:
add_gap(trackitem, otio_track, 0)
elif itemindex and clip_diff != 1:
add_gap(trackitem, otio_track, prev_item.timelineOut())
# Create Clip
source_range, available_range = get_clip_ranges(trackitem)
otio_clip = otio.schema.Clip(
name=trackitem.name(),
source_range=source_range
)
media_reference = create_otio_reference(hiero_clip)
otio_clip.media_reference = media_reference
# Add Time Effects
playbackspeed = trackitem.playbackSpeed()
if playbackspeed != 1:
if playbackspeed == 0:
time_effect = otio.schema.FreezeFrame()
else:
time_effect = otio.schema.LinearTimeWarp(
time_scalar=playbackspeed
)
otio_clip.effects.append(time_effect)
# Add tags as markers
if self.include_tags:
add_markers(trackitem, otio_clip)
add_markers(trackitem.source(), otio_clip)
otio_track.append(otio_clip)
# Add Transition if needed
if trackitem.inTransition() or trackitem.outTransition():
add_transition(trackitem, otio_track)
def _get_metadata(hiero_object):
metadata = hiero_object.metadata()
return {key: value for key, value in metadata.items()}
def create_otio_reference(hiero_clip):
metadata = _get_metadata(hiero_clip)
mp_clip_property = media_pool_item.GetClipProperty()
path = mp_clip_property["File Path"]
reformat_path = utils.get_reformated_path(path, padded=True)
padding = utils.get_padding_from_path(path)
if padding:
metadata.update({
"isSequence": True,
"padding": padding
})
# get clip property regarding to type
mp_clip_property = media_pool_item.GetClipProperty()
fps = float(mp_clip_property["FPS"])
if mp_clip_property["Type"] == "Video":
frame_start = int(mp_clip_property["Start"])
frame_duration = int(mp_clip_property["Frames"])
else:
audio_duration = str(mp_clip_property["Duration"])
frame_start = 0
frame_duration = int(utils.timecode_to_frames(
audio_duration, float(fps)))
otio_ex_ref_item = None
if padding:
# if it is file sequence try to create `ImageSequenceReference`
# the OTIO might not be compatible so return nothing and do it old way
try:
dirname, filename = os.path.split(path)
collection = clique.parse(filename, '{head}[{ranges}]{tail}')
padding_num = len(re.findall("(\\d+)(?=-)", filename).pop())
otio_ex_ref_item = otio.schema.ImageSequenceReference(
target_url_base=dirname + os.sep,
name_prefix=collection.format("{head}"),
name_suffix=collection.format("{tail}"),
start_frame=frame_start,
frame_zero_padding=padding_num,
rate=fps,
available_range=create_otio_time_range(
frame_start,
frame_duration,
fps
)
)
except AttributeError:
pass
if not otio_ex_ref_item:
# in case old OTIO or video file create `ExternalReference`
otio_ex_ref_item = otio.schema.ExternalReference(
target_url=reformat_path,
available_range=create_otio_time_range(
frame_start,
frame_duration,
fps
)
)
# add metadata to otio item
add_otio_metadata(otio_ex_ref_item, hiero_clip, **metadata)
return otio_ex_ref_item
def add_otio_metadata(otio_item, hiero_clip, **kwargs):
mp_metadata = hiero_clip.GetMetadata()
# add additional metadata from kwargs
if kwargs:
mp_metadata.update(kwargs)
# add metadata to otio item metadata
for key, value in mp_metadata.items():
otio_item.metadata.update({key: value})
def add_transition(trackitem, otio_track):
transitions = []
if trackitem.inTransition():
if trackitem.inTransition().alignment().name == 'kFadeIn':
transitions.append(trackitem.inTransition())
if trackitem.outTransition():
transitions.append(trackitem.outTransition())
for transition in transitions:
alignment = transition.alignment().name
if alignment == 'kFadeIn':
in_offset_frames = 0
out_offset_frames = (
transition.timelineOut() - transition.timelineIn()
) + 1
elif alignment == 'kFadeOut':
in_offset_frames = (
trackitem.timelineOut() - transition.timelineIn()
) + 1
out_offset_frames = 0
elif alignment == 'kDissolve':
in_offset_frames = (
transition.inTrackItem().timelineOut() -
transition.timelineIn()
)
out_offset_frames = (
transition.timelineOut() -
transition.outTrackItem().timelineIn()
)
else:
# kUnknown transition is ignored
continue
rate = trackitem.source().framerate().toFloat()
in_time = otio.opentime.RationalTime(in_offset_frames, rate)
out_time = otio.opentime.RationalTime(out_offset_frames, rate)
otio_transition = otio.schema.Transition(
name=alignment, # Consider placing Hiero name in metadata
transition_type=otio.schema.TransitionTypes.SMPTE_Dissolve,
in_offset=in_time,
out_offset=out_time
)
if alignment == 'kFadeIn':
otio_track.insert(-1, otio_transition)
else:
otio_track.append(otio_transition)
def add_tracks():
for track in self.hiero_sequence.items():
if isinstance(track, hiero.core.AudioTrack):
kind = otio.schema.TrackKind.Audio
else:
kind = otio.schema.TrackKind.Video
otio_track = otio.schema.Track(name=track.name(), kind=kind)
for itemindex, trackitem in enumerate(track):
if isinstance(trackitem.source(), hiero.core.Clip):
add_clip(trackitem, otio_track, itemindex)
self.otio_timeline.tracks.append(otio_track)
# Add tags as markers
if self.include_tags:
add_markers(self.hiero_sequence, self.otio_timeline.tracks)
def create_OTIO(sequence=None):
self.hiero_sequence = sequence or hiero.ui.activeSequence()
self.otio_timeline = otio.schema.Timeline()
# Set global start time based on sequence
self.otio_timeline.global_start_time = otio.opentime.RationalTime(
self.hiero_sequence.timecodeStart(),
self.hiero_sequence.framerate().toFloat()
)
self.otio_timeline.name = self.hiero_sequence.name()
add_tracks()
return self.otio_timeline

View file

@ -19,6 +19,7 @@ except ImportError:
import opentimelineio as otio
_otio_old = False
def inform(messages):
if isinstance(messages, type('')):
@ -180,14 +181,23 @@ def prep_url(url_in):
def create_offline_mediasource(otio_clip, path=None):
global _otio_old
hiero_rate = hiero.core.TimeBase(
otio_clip.source_range.start_time.rate
)
legal_media_refs = (
otio.schema.ExternalReference,
otio.schema.ImageSequenceReference
)
try:
legal_media_refs = (
otio.schema.ExternalReference,
otio.schema.ImageSequenceReference
)
except AttributeError:
_otio_old = True
legal_media_refs = (
otio.schema.ExternalReference
)
if isinstance(otio_clip.media_reference, legal_media_refs):
source_range = otio_clip.available_range()
@ -331,9 +341,10 @@ def create_clip(otio_clip, tagsbin, sequencebin):
url = prep_url(otio_media.target_url)
media = hiero.core.MediaSource(url)
elif isinstance(otio_media, otio.schema.ImageSequenceReference):
url = prep_url(otio_media.abstract_target_url('#'))
media = hiero.core.MediaSource(url)
elif not _otio_old:
if isinstance(otio_media, otio.schema.ImageSequenceReference):
url = prep_url(otio_media.abstract_target_url('#'))
media = hiero.core.MediaSource(url)
if media is None or media.isOffline():
media = create_offline_mediasource(otio_clip, url)

View file

@ -17,7 +17,7 @@ def frames_to_secons(frames, framerate):
return otio.opentime.to_seconds(rt)
def get_reformated_path(path, padded=True, first=False):
def get_reformated_path(path, padded=True):
"""
Return fixed python expression path
@ -31,19 +31,12 @@ def get_reformated_path(path, padded=True, first=False):
get_reformated_path("plate.[0001-1008].exr") > plate.%04d.exr
"""
num_pattern = r"(\[\d+\-\d+\])"
padding_pattern = r"(\d+)(?=-)"
first_frame_pattern = re.compile(r"\[(\d+)\-\d+\]")
if "[" in path:
padding = len(re.findall(padding_pattern, path).pop())
if "%" in path:
padding_pattern = r"(\d+)"
padding = int(re.findall(padding_pattern, path).pop())
num_pattern = r"(%\d+d)"
if padded:
path = re.sub(num_pattern, f"%0{padding}d", path)
elif first:
first_frame = re.findall(first_frame_pattern, path, flags=0)
if len(first_frame) >= 1:
first_frame = first_frame[0]
path = re.sub(num_pattern, first_frame, path)
path = re.sub(num_pattern, "%0{}d".format(padding), path)
else:
path = re.sub(num_pattern, "%d", path)
return path
@ -72,7 +65,7 @@ def get_padding_from_path(path):
def get_rate(item):
if not hasattr(item, 'framerate'):
item = item.sequence()
return None
num, den = item.framerate().toRational()
rate = float(num) / float(den)

View file

@ -60,7 +60,7 @@ class OTIOExportPreset(hiero.core.TaskPresetBase):
"""Initialise presets to default values"""
hiero.core.TaskPresetBase.__init__(self, OTIOExportTask, name)
self.properties()["includeTags"] = True
self.properties()["includeTags"] = hiero_export.include_tags = True
self.properties().update(properties)
def supportedItems(self):

View file

@ -20,8 +20,7 @@ except ImportError:
FormLayout = QFormLayout # lint:ok
from pype.hosts.hiero.otio import hiero_export
from openpype.hosts.hiero.otio import hiero_export
class OTIOExportUI(hiero.ui.TaskUIBase):
def __init__(self, preset):
@ -35,7 +34,7 @@ class OTIOExportUI(hiero.ui.TaskUIBase):
def includeMarkersCheckboxChanged(self, state):
# Slot to handle change of checkbox state
hiero_export.hiero_sequence = state == QtCore.Qt.Checked
hiero_export.include_tags = state == QtCore.Qt.Checked
def populateUI(self, widget, exportTemplate):
layout = widget.layout()

View file

@ -9,7 +9,7 @@ import hiero.core
import PySide2.QtWidgets as qw
from pype.hosts.hiero.otio.hiero_import import load_otio
from openpype.hosts.hiero.otio.hiero_import import load_otio
class OTIOProjectSelect(qw.QDialog):