mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 12:54:40 +01:00
Hiero: otio-workflow wip
This commit is contained in:
parent
1ab3fb3953
commit
6c27152eeb
8 changed files with 1045 additions and 367 deletions
0
openpype/hosts/hiero/otio/__init__.py
Normal file
0
openpype/hosts/hiero/otio/__init__.py
Normal file
386
openpype/hosts/hiero/otio/hiero_export.py
Normal file
386
openpype/hosts/hiero/otio/hiero_export.py
Normal file
|
|
@ -0,0 +1,386 @@
|
||||||
|
#!/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
|
||||||
529
openpype/hosts/hiero/otio/hiero_import.py
Normal file
529
openpype/hosts/hiero/otio/hiero_import.py
Normal file
|
|
@ -0,0 +1,529 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
__author__ = "Daniel Flehner Heen"
|
||||||
|
__credits__ = ["Jakub Jezek", "Daniel Flehner Heen"]
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import hiero.core
|
||||||
|
import hiero.ui
|
||||||
|
|
||||||
|
import PySide2.QtWidgets as qw
|
||||||
|
|
||||||
|
try:
|
||||||
|
from urllib import unquote
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
from urllib.parse import unquote # lint:ok
|
||||||
|
|
||||||
|
import opentimelineio as otio
|
||||||
|
|
||||||
|
|
||||||
|
def inform(messages):
|
||||||
|
if isinstance(messages, type('')):
|
||||||
|
messages = [messages]
|
||||||
|
|
||||||
|
qw.QMessageBox.information(
|
||||||
|
hiero.ui.mainWindow(),
|
||||||
|
'OTIO Import',
|
||||||
|
'\n'.join(messages),
|
||||||
|
qw.QMessageBox.StandardButton.Ok
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_transition_type(otio_item, otio_track):
|
||||||
|
_in, _out = otio_track.neighbors_of(otio_item)
|
||||||
|
|
||||||
|
if isinstance(_in, otio.schema.Gap):
|
||||||
|
_in = None
|
||||||
|
|
||||||
|
if isinstance(_out, otio.schema.Gap):
|
||||||
|
_out = None
|
||||||
|
|
||||||
|
if _in and _out:
|
||||||
|
return 'dissolve'
|
||||||
|
|
||||||
|
elif _in and not _out:
|
||||||
|
return 'fade_out'
|
||||||
|
|
||||||
|
elif not _in and _out:
|
||||||
|
return 'fade_in'
|
||||||
|
|
||||||
|
else:
|
||||||
|
return 'unknown'
|
||||||
|
|
||||||
|
|
||||||
|
def find_trackitem(otio_clip, hiero_track):
|
||||||
|
for item in hiero_track.items():
|
||||||
|
if item.timelineIn() == otio_clip.range_in_parent().start_time.value:
|
||||||
|
if item.name() == otio_clip.name:
|
||||||
|
return item
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_neighboring_trackitems(otio_item, otio_track, hiero_track):
|
||||||
|
_in, _out = otio_track.neighbors_of(otio_item)
|
||||||
|
trackitem_in = None
|
||||||
|
trackitem_out = None
|
||||||
|
|
||||||
|
if _in:
|
||||||
|
trackitem_in = find_trackitem(_in, hiero_track)
|
||||||
|
|
||||||
|
if _out:
|
||||||
|
trackitem_out = find_trackitem(_out, hiero_track)
|
||||||
|
|
||||||
|
return trackitem_in, trackitem_out
|
||||||
|
|
||||||
|
|
||||||
|
def apply_transition(otio_track, otio_item, track):
|
||||||
|
warning = None
|
||||||
|
|
||||||
|
# Figure out type of transition
|
||||||
|
transition_type = get_transition_type(otio_item, otio_track)
|
||||||
|
|
||||||
|
# Figure out track kind for getattr below
|
||||||
|
kind = ''
|
||||||
|
if isinstance(track, hiero.core.AudioTrack):
|
||||||
|
kind = 'Audio'
|
||||||
|
|
||||||
|
# Gather TrackItems involved in trasition
|
||||||
|
item_in, item_out = get_neighboring_trackitems(
|
||||||
|
otio_item,
|
||||||
|
otio_track,
|
||||||
|
track
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create transition object
|
||||||
|
if transition_type == 'dissolve':
|
||||||
|
transition_func = getattr(
|
||||||
|
hiero.core.Transition,
|
||||||
|
'create{kind}DissolveTransition'.format(kind=kind)
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
transition = transition_func(
|
||||||
|
item_in,
|
||||||
|
item_out,
|
||||||
|
otio_item.in_offset.value,
|
||||||
|
otio_item.out_offset.value
|
||||||
|
)
|
||||||
|
|
||||||
|
# Catch error raised if transition is bigger than TrackItem source
|
||||||
|
except RuntimeError as e:
|
||||||
|
transition = None
|
||||||
|
warning = \
|
||||||
|
'Unable to apply transition "{t.name}": {e} ' \
|
||||||
|
'Ignoring the transition.' \
|
||||||
|
.format(t=otio_item, e=e.message)
|
||||||
|
|
||||||
|
elif transition_type == 'fade_in':
|
||||||
|
transition_func = getattr(
|
||||||
|
hiero.core.Transition,
|
||||||
|
'create{kind}FadeInTransition'.format(kind=kind)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Warn user if part of fade is outside of clip
|
||||||
|
if otio_item.in_offset.value:
|
||||||
|
warning = \
|
||||||
|
'Fist half of transition "{t.name}" is outside of clip and ' \
|
||||||
|
'not valid in Hiero. Only applied second half.' \
|
||||||
|
.format(t=otio_item)
|
||||||
|
|
||||||
|
transition = transition_func(
|
||||||
|
item_out,
|
||||||
|
otio_item.out_offset.value
|
||||||
|
)
|
||||||
|
|
||||||
|
elif transition_type == 'fade_out':
|
||||||
|
transition_func = getattr(
|
||||||
|
hiero.core.Transition,
|
||||||
|
'create{kind}FadeOutTransition'.format(kind=kind)
|
||||||
|
)
|
||||||
|
transition = transition_func(
|
||||||
|
item_in,
|
||||||
|
otio_item.in_offset.value
|
||||||
|
)
|
||||||
|
|
||||||
|
# Warn user if part of fade is outside of clip
|
||||||
|
if otio_item.out_offset.value:
|
||||||
|
warning = \
|
||||||
|
'Second half of transition "{t.name}" is outside of clip ' \
|
||||||
|
'and not valid in Hiero. Only applied first half.' \
|
||||||
|
.format(t=otio_item)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Unknown transition
|
||||||
|
return
|
||||||
|
|
||||||
|
# Apply transition to track
|
||||||
|
if transition:
|
||||||
|
track.addTransition(transition)
|
||||||
|
|
||||||
|
# Inform user about missing or adjusted transitions
|
||||||
|
return warning
|
||||||
|
|
||||||
|
|
||||||
|
def prep_url(url_in):
|
||||||
|
url = unquote(url_in)
|
||||||
|
|
||||||
|
if url.startswith('file://localhost/'):
|
||||||
|
return url
|
||||||
|
|
||||||
|
url = 'file://localhost{sep}{url}'.format(
|
||||||
|
sep=url.startswith(os.sep) and '' or os.sep,
|
||||||
|
url=url.startswith(os.sep) and url[1:] or url
|
||||||
|
)
|
||||||
|
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
def create_offline_mediasource(otio_clip, path=None):
|
||||||
|
hiero_rate = hiero.core.TimeBase(
|
||||||
|
otio_clip.source_range.start_time.rate
|
||||||
|
)
|
||||||
|
|
||||||
|
legal_media_refs = (
|
||||||
|
otio.schema.ExternalReference,
|
||||||
|
otio.schema.ImageSequenceReference
|
||||||
|
)
|
||||||
|
if isinstance(otio_clip.media_reference, legal_media_refs):
|
||||||
|
source_range = otio_clip.available_range()
|
||||||
|
|
||||||
|
else:
|
||||||
|
source_range = otio_clip.source_range
|
||||||
|
|
||||||
|
if path is None:
|
||||||
|
path = otio_clip.name
|
||||||
|
|
||||||
|
media = hiero.core.MediaSource.createOfflineVideoMediaSource(
|
||||||
|
prep_url(path),
|
||||||
|
source_range.start_time.value,
|
||||||
|
source_range.duration.value,
|
||||||
|
hiero_rate,
|
||||||
|
source_range.start_time.value
|
||||||
|
)
|
||||||
|
|
||||||
|
return media
|
||||||
|
|
||||||
|
|
||||||
|
def load_otio(otio_file, project=None, sequence=None):
|
||||||
|
otio_timeline = otio.adapters.read_from_file(otio_file)
|
||||||
|
build_sequence(otio_timeline, project=project, sequence=sequence)
|
||||||
|
|
||||||
|
|
||||||
|
marker_color_map = {
|
||||||
|
"PINK": "Magenta",
|
||||||
|
"RED": "Red",
|
||||||
|
"ORANGE": "Yellow",
|
||||||
|
"YELLOW": "Yellow",
|
||||||
|
"GREEN": "Green",
|
||||||
|
"CYAN": "Cyan",
|
||||||
|
"BLUE": "Blue",
|
||||||
|
"PURPLE": "Magenta",
|
||||||
|
"MAGENTA": "Magenta",
|
||||||
|
"BLACK": "Blue",
|
||||||
|
"WHITE": "Green"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_tag(tagname, tagsbin):
|
||||||
|
for tag in tagsbin.items():
|
||||||
|
if tag.name() == tagname:
|
||||||
|
return tag
|
||||||
|
|
||||||
|
if isinstance(tag, hiero.core.Bin):
|
||||||
|
tag = get_tag(tagname, tag)
|
||||||
|
|
||||||
|
if tag is not None:
|
||||||
|
return tag
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def add_metadata(metadata, hiero_item):
|
||||||
|
for key, value in metadata.get('Hiero', dict()).items():
|
||||||
|
if key == 'source_type':
|
||||||
|
# Only used internally to reassign tag to correct Hiero item
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(value, dict):
|
||||||
|
add_metadata(value, hiero_item)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if value is not None:
|
||||||
|
if not key.startswith('tag.'):
|
||||||
|
key = 'tag.' + key
|
||||||
|
|
||||||
|
hiero_item.metadata().setValue(key, str(value))
|
||||||
|
|
||||||
|
|
||||||
|
def add_markers(otio_item, hiero_item, tagsbin):
|
||||||
|
if isinstance(otio_item, (otio.schema.Stack, otio.schema.Clip)):
|
||||||
|
markers = otio_item.markers
|
||||||
|
|
||||||
|
elif isinstance(otio_item, otio.schema.Timeline):
|
||||||
|
markers = otio_item.tracks.markers
|
||||||
|
|
||||||
|
else:
|
||||||
|
markers = []
|
||||||
|
|
||||||
|
for marker in markers:
|
||||||
|
meta = marker.metadata.get('Hiero', dict())
|
||||||
|
if 'source_type' in meta:
|
||||||
|
if hiero_item.__class__.__name__ != meta.get('source_type'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
marker_color = marker.color
|
||||||
|
|
||||||
|
_tag = get_tag(marker.name, tagsbin)
|
||||||
|
if _tag is None:
|
||||||
|
_tag = get_tag(marker_color_map[marker_color], tagsbin)
|
||||||
|
|
||||||
|
if _tag is None:
|
||||||
|
_tag = hiero.core.Tag(marker_color_map[marker.color])
|
||||||
|
|
||||||
|
start = marker.marked_range.start_time.value
|
||||||
|
end = (
|
||||||
|
marker.marked_range.start_time.value +
|
||||||
|
marker.marked_range.duration.value
|
||||||
|
)
|
||||||
|
|
||||||
|
if hasattr(hiero_item, 'addTagToRange'):
|
||||||
|
tag = hiero_item.addTagToRange(_tag, start, end)
|
||||||
|
|
||||||
|
else:
|
||||||
|
tag = hiero_item.addTag(_tag)
|
||||||
|
|
||||||
|
tag.setName(marker.name or marker_color_map[marker_color])
|
||||||
|
# tag.setNote(meta.get('tag.note', ''))
|
||||||
|
|
||||||
|
# Add metadata
|
||||||
|
add_metadata(marker.metadata, tag)
|
||||||
|
|
||||||
|
|
||||||
|
def create_track(otio_track, tracknum, track_kind):
|
||||||
|
if track_kind is None and hasattr(otio_track, 'kind'):
|
||||||
|
track_kind = otio_track.kind
|
||||||
|
|
||||||
|
# Create a Track
|
||||||
|
if track_kind == otio.schema.TrackKind.Video:
|
||||||
|
track = hiero.core.VideoTrack(
|
||||||
|
otio_track.name or 'Video{n}'.format(n=tracknum)
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
track = hiero.core.AudioTrack(
|
||||||
|
otio_track.name or 'Audio{n}'.format(n=tracknum)
|
||||||
|
)
|
||||||
|
|
||||||
|
return track
|
||||||
|
|
||||||
|
|
||||||
|
def create_clip(otio_clip, tagsbin, sequencebin):
|
||||||
|
# Create MediaSource
|
||||||
|
url = None
|
||||||
|
media = None
|
||||||
|
otio_media = otio_clip.media_reference
|
||||||
|
|
||||||
|
if isinstance(otio_media, otio.schema.ExternalReference):
|
||||||
|
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)
|
||||||
|
|
||||||
|
if media is None or media.isOffline():
|
||||||
|
media = create_offline_mediasource(otio_clip, url)
|
||||||
|
|
||||||
|
# Reuse previous clip if possible
|
||||||
|
clip = None
|
||||||
|
for item in sequencebin.clips():
|
||||||
|
if item.activeItem().mediaSource() == media:
|
||||||
|
clip = item.activeItem()
|
||||||
|
break
|
||||||
|
|
||||||
|
if not clip:
|
||||||
|
# Create new Clip
|
||||||
|
clip = hiero.core.Clip(media)
|
||||||
|
|
||||||
|
# Add Clip to a Bin
|
||||||
|
sequencebin.addItem(hiero.core.BinItem(clip))
|
||||||
|
|
||||||
|
# Add markers
|
||||||
|
add_markers(otio_clip, clip, tagsbin)
|
||||||
|
|
||||||
|
return clip
|
||||||
|
|
||||||
|
|
||||||
|
def create_trackitem(playhead, track, otio_clip, clip):
|
||||||
|
source_range = otio_clip.source_range
|
||||||
|
|
||||||
|
trackitem = track.createTrackItem(otio_clip.name)
|
||||||
|
trackitem.setPlaybackSpeed(source_range.start_time.rate)
|
||||||
|
trackitem.setSource(clip)
|
||||||
|
|
||||||
|
time_scalar = 1.
|
||||||
|
|
||||||
|
# Check for speed effects and adjust playback speed accordingly
|
||||||
|
for effect in otio_clip.effects:
|
||||||
|
if isinstance(effect, otio.schema.LinearTimeWarp):
|
||||||
|
time_scalar = effect.time_scalar
|
||||||
|
# Only reverse effect can be applied here
|
||||||
|
if abs(time_scalar) == 1.:
|
||||||
|
trackitem.setPlaybackSpeed(trackitem.playbackSpeed() * time_scalar)
|
||||||
|
|
||||||
|
elif isinstance(effect, otio.schema.FreezeFrame):
|
||||||
|
# For freeze frame, playback speed must be set after range
|
||||||
|
time_scalar = 0.
|
||||||
|
|
||||||
|
# If reverse playback speed swap source in and out
|
||||||
|
if trackitem.playbackSpeed() < 0:
|
||||||
|
source_out = source_range.start_time.value
|
||||||
|
source_in = source_range.end_time_inclusive().value
|
||||||
|
|
||||||
|
timeline_in = playhead + source_out
|
||||||
|
timeline_out = (
|
||||||
|
timeline_in +
|
||||||
|
source_range.duration.value
|
||||||
|
) - 1
|
||||||
|
else:
|
||||||
|
# Normal playback speed
|
||||||
|
source_in = source_range.start_time.value
|
||||||
|
source_out = source_range.end_time_inclusive().value
|
||||||
|
|
||||||
|
timeline_in = playhead
|
||||||
|
timeline_out = (
|
||||||
|
timeline_in +
|
||||||
|
source_range.duration.value
|
||||||
|
) - 1
|
||||||
|
|
||||||
|
# Set source and timeline in/out points
|
||||||
|
trackitem.setTimes(
|
||||||
|
timeline_in,
|
||||||
|
timeline_out,
|
||||||
|
source_in,
|
||||||
|
source_out
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply playback speed for freeze frames
|
||||||
|
if abs(time_scalar) != 1.:
|
||||||
|
trackitem.setPlaybackSpeed(trackitem.playbackSpeed() * time_scalar)
|
||||||
|
|
||||||
|
# Link audio to video when possible
|
||||||
|
if isinstance(track, hiero.core.AudioTrack):
|
||||||
|
for other in track.parent().trackItemsAt(playhead):
|
||||||
|
if other.source() == clip:
|
||||||
|
trackitem.link(other)
|
||||||
|
|
||||||
|
return trackitem
|
||||||
|
|
||||||
|
|
||||||
|
def build_sequence(otio_timeline, project=None, sequence=None, track_kind=None):
|
||||||
|
if project is None:
|
||||||
|
if sequence:
|
||||||
|
project = sequence.project()
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Per version 12.1v2 there is no way of getting active project
|
||||||
|
project = hiero.core.projects(hiero.core.Project.kUserProjects)[-1]
|
||||||
|
|
||||||
|
projectbin = project.clipsBin()
|
||||||
|
|
||||||
|
if not sequence:
|
||||||
|
# Create a Sequence
|
||||||
|
sequence = hiero.core.Sequence(otio_timeline.name or 'OTIOSequence')
|
||||||
|
|
||||||
|
# Set sequence settings from otio timeline if available
|
||||||
|
if hasattr(otio_timeline, 'global_start_time'):
|
||||||
|
if otio_timeline.global_start_time:
|
||||||
|
start_time = otio_timeline.global_start_time
|
||||||
|
sequence.setFramerate(start_time.rate)
|
||||||
|
sequence.setTimecodeStart(start_time.value)
|
||||||
|
|
||||||
|
# Create a Bin to hold clips
|
||||||
|
projectbin.addItem(hiero.core.BinItem(sequence))
|
||||||
|
|
||||||
|
sequencebin = hiero.core.Bin(sequence.name())
|
||||||
|
projectbin.addItem(sequencebin)
|
||||||
|
|
||||||
|
else:
|
||||||
|
sequencebin = projectbin
|
||||||
|
|
||||||
|
# Get tagsBin
|
||||||
|
tagsbin = hiero.core.project("Tag Presets").tagsBin()
|
||||||
|
|
||||||
|
# Add timeline markers
|
||||||
|
add_markers(otio_timeline, sequence, tagsbin)
|
||||||
|
|
||||||
|
if isinstance(otio_timeline, otio.schema.Timeline):
|
||||||
|
tracks = otio_timeline.tracks
|
||||||
|
|
||||||
|
else:
|
||||||
|
tracks = [otio_timeline]
|
||||||
|
|
||||||
|
for tracknum, otio_track in enumerate(tracks):
|
||||||
|
playhead = 0
|
||||||
|
_transitions = []
|
||||||
|
|
||||||
|
# Add track to sequence
|
||||||
|
track = create_track(otio_track, tracknum, track_kind)
|
||||||
|
sequence.addTrack(track)
|
||||||
|
|
||||||
|
# iterate over items in track
|
||||||
|
for itemnum, otio_clip in enumerate(otio_track):
|
||||||
|
if isinstance(otio_clip, (otio.schema.Track, otio.schema.Stack)):
|
||||||
|
inform('Nested sequences/tracks are created separately.')
|
||||||
|
|
||||||
|
# Add gap where the nested sequence would have been
|
||||||
|
playhead += otio_clip.source_range.duration.value
|
||||||
|
|
||||||
|
# Process nested sequence
|
||||||
|
build_sequence(
|
||||||
|
otio_clip,
|
||||||
|
project=project,
|
||||||
|
track_kind=otio_track.kind
|
||||||
|
)
|
||||||
|
|
||||||
|
elif isinstance(otio_clip, otio.schema.Clip):
|
||||||
|
# Create a Clip
|
||||||
|
clip = create_clip(otio_clip, tagsbin, sequencebin)
|
||||||
|
|
||||||
|
# Create TrackItem
|
||||||
|
trackitem = create_trackitem(
|
||||||
|
playhead,
|
||||||
|
track,
|
||||||
|
otio_clip,
|
||||||
|
clip
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add markers
|
||||||
|
add_markers(otio_clip, trackitem, tagsbin)
|
||||||
|
|
||||||
|
# Add trackitem to track
|
||||||
|
track.addTrackItem(trackitem)
|
||||||
|
|
||||||
|
# Update playhead
|
||||||
|
playhead = trackitem.timelineOut() + 1
|
||||||
|
|
||||||
|
elif isinstance(otio_clip, otio.schema.Transition):
|
||||||
|
# Store transitions for when all clips in the track are created
|
||||||
|
_transitions.append((otio_track, otio_clip))
|
||||||
|
|
||||||
|
elif isinstance(otio_clip, otio.schema.Gap):
|
||||||
|
# Hiero has no fillers, slugs or blanks at the moment
|
||||||
|
playhead += otio_clip.source_range.duration.value
|
||||||
|
|
||||||
|
# Apply transitions we stored earlier now that all clips are present
|
||||||
|
warnings = list()
|
||||||
|
for otio_track, otio_item in _transitions:
|
||||||
|
# Catch warnings form transitions in case of unsupported transitions
|
||||||
|
warning = apply_transition(otio_track, otio_item, track)
|
||||||
|
if warning:
|
||||||
|
warnings.append(warning)
|
||||||
|
|
||||||
|
if warnings:
|
||||||
|
inform(warnings)
|
||||||
|
|
@ -1,42 +1,15 @@
|
||||||
# MIT License
|
#!/usr/bin/env python
|
||||||
#
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2018 Daniel Flehner Heen (Storm Studios)
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
|
||||||
# in the Software without restriction, including without limitation the rights
|
|
||||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
# copies of the Software, and to permit persons to whom the Software is
|
|
||||||
# furnished to do so, subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be included in
|
|
||||||
# all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
|
__author__ = "Daniel Flehner Heen"
|
||||||
|
__credits__ = ["Jakub Jezek", "Daniel Flehner Heen"]
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import hiero.core
|
import hiero.core
|
||||||
from hiero.core import util
|
from hiero.core import util
|
||||||
|
|
||||||
import opentimelineio as otio
|
import opentimelineio as otio
|
||||||
|
from pype.hosts.hiero.otio.hiero_export import create_OTIO
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class OTIOExportTask(hiero.core.TaskBase):
|
class OTIOExportTask(hiero.core.TaskBase):
|
||||||
|
|
@ -44,295 +17,14 @@ class OTIOExportTask(hiero.core.TaskBase):
|
||||||
def __init__(self, initDict):
|
def __init__(self, initDict):
|
||||||
"""Initialize"""
|
"""Initialize"""
|
||||||
hiero.core.TaskBase.__init__(self, initDict)
|
hiero.core.TaskBase.__init__(self, initDict)
|
||||||
|
self.otio_timeline = None
|
||||||
|
|
||||||
def name(self):
|
def name(self):
|
||||||
return str(type(self))
|
return str(type(self))
|
||||||
|
|
||||||
def get_rate(self, 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(self, trackitem):
|
|
||||||
# Get rate from source or sequence
|
|
||||||
if trackitem.source().mediaSource().hasVideo():
|
|
||||||
rate_item = trackitem.source()
|
|
||||||
|
|
||||||
else:
|
|
||||||
rate_item = trackitem.sequence()
|
|
||||||
|
|
||||||
source_rate = self.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(self, trackitem, otio_track, prev_out):
|
|
||||||
gap_length = trackitem.timelineIn() - prev_out
|
|
||||||
if prev_out != 0:
|
|
||||||
gap_length -= 1
|
|
||||||
|
|
||||||
rate = self.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(self, 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 marker_color_map:
|
|
||||||
return marker_color_map[color.lower()]
|
|
||||||
|
|
||||||
return otio.schema.MarkerColor.RED
|
|
||||||
|
|
||||||
def add_markers(self, 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 = self.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=self.get_marker_color(tag),
|
|
||||||
marked_range=marked_range,
|
|
||||||
metadata=metadata
|
|
||||||
)
|
|
||||||
|
|
||||||
otio_item.markers.append(marker)
|
|
||||||
|
|
||||||
def add_clip(self, 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:
|
|
||||||
self.add_gap(trackitem, otio_track, 0)
|
|
||||||
|
|
||||||
elif itemindex and clip_diff != 1:
|
|
||||||
self.add_gap(trackitem, otio_track, prev_item.timelineOut())
|
|
||||||
|
|
||||||
# Create Clip
|
|
||||||
source_range, available_range = self.get_clip_ranges(trackitem)
|
|
||||||
|
|
||||||
otio_clip = otio.schema.Clip(
|
|
||||||
name=trackitem.name(),
|
|
||||||
source_range=source_range
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add media reference
|
|
||||||
media_reference = otio.schema.MissingReference()
|
|
||||||
if hiero_clip.mediaSource().isMediaPresent():
|
|
||||||
source = hiero_clip.mediaSource()
|
|
||||||
first_file = source.fileinfos()[0]
|
|
||||||
path = first_file.filename()
|
|
||||||
|
|
||||||
if "%" in path:
|
|
||||||
path = re.sub(r"%\d+d", "%d", path)
|
|
||||||
if "#" in path:
|
|
||||||
path = re.sub(r"#+", "%d", path)
|
|
||||||
|
|
||||||
media_reference = otio.schema.ExternalReference(
|
|
||||||
target_url=u'{}'.format(path),
|
|
||||||
available_range=available_range
|
|
||||||
)
|
|
||||||
|
|
||||||
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._preset.properties()["includeTags"]:
|
|
||||||
self.add_markers(trackitem, otio_clip)
|
|
||||||
self.add_markers(trackitem.source(), otio_clip)
|
|
||||||
|
|
||||||
otio_track.append(otio_clip)
|
|
||||||
|
|
||||||
# Add Transition if needed
|
|
||||||
if trackitem.inTransition() or trackitem.outTransition():
|
|
||||||
self.add_transition(trackitem, otio_track)
|
|
||||||
|
|
||||||
def add_transition(self, 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(self):
|
|
||||||
for track in self._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):
|
|
||||||
self.add_clip(trackitem, otio_track, itemindex)
|
|
||||||
|
|
||||||
self.otio_timeline.tracks.append(otio_track)
|
|
||||||
|
|
||||||
# Add tags as markers
|
|
||||||
if self._preset.properties()["includeTags"]:
|
|
||||||
self.add_markers(self._sequence, self.otio_timeline.tracks)
|
|
||||||
|
|
||||||
def create_OTIO(self):
|
|
||||||
self.otio_timeline = otio.schema.Timeline()
|
|
||||||
|
|
||||||
# Set global start time based on sequence
|
|
||||||
self.otio_timeline.global_start_time = otio.opentime.RationalTime(
|
|
||||||
self._sequence.timecodeStart(),
|
|
||||||
self._sequence.framerate().toFloat()
|
|
||||||
)
|
|
||||||
self.otio_timeline.name = self._sequence.name()
|
|
||||||
|
|
||||||
self.add_tracks()
|
|
||||||
|
|
||||||
def startTask(self):
|
def startTask(self):
|
||||||
self.create_OTIO()
|
self.otio_timeline = create_OTIO(
|
||||||
|
self._sequence)
|
||||||
|
|
||||||
def taskStep(self):
|
def taskStep(self):
|
||||||
return False
|
return False
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,9 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
__author__ = "Daniel Flehner Heen"
|
||||||
|
__credits__ = ["Jakub Jezek", "Daniel Flehner Heen"]
|
||||||
|
|
||||||
import hiero.ui
|
import hiero.ui
|
||||||
import OTIOExportTask
|
import OTIOExportTask
|
||||||
|
|
||||||
|
|
@ -14,6 +20,8 @@ except ImportError:
|
||||||
|
|
||||||
FormLayout = QFormLayout # lint:ok
|
FormLayout = QFormLayout # lint:ok
|
||||||
|
|
||||||
|
from pype.hosts.hiero.otio import hiero_export
|
||||||
|
|
||||||
|
|
||||||
class OTIOExportUI(hiero.ui.TaskUIBase):
|
class OTIOExportUI(hiero.ui.TaskUIBase):
|
||||||
def __init__(self, preset):
|
def __init__(self, preset):
|
||||||
|
|
@ -27,7 +35,7 @@ class OTIOExportUI(hiero.ui.TaskUIBase):
|
||||||
|
|
||||||
def includeMarkersCheckboxChanged(self, state):
|
def includeMarkersCheckboxChanged(self, state):
|
||||||
# Slot to handle change of checkbox state
|
# Slot to handle change of checkbox state
|
||||||
self._preset.properties()["includeTags"] = state == QtCore.Qt.Checked
|
hiero_export.hiero_sequence = state == QtCore.Qt.Checked
|
||||||
|
|
||||||
def populateUI(self, widget, exportTemplate):
|
def populateUI(self, widget, exportTemplate):
|
||||||
layout = widget.layout()
|
layout = widget.layout()
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,3 @@
|
||||||
# MIT License
|
|
||||||
#
|
|
||||||
# Copyright (c) 2018 Daniel Flehner Heen (Storm Studios)
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
|
||||||
# in the Software without restriction, including without limitation the rights
|
|
||||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
# copies of the Software, and to permit persons to whom the Software is
|
|
||||||
# furnished to do so, subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be included in
|
|
||||||
# all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
from OTIOExportTask import OTIOExportTask
|
from OTIOExportTask import OTIOExportTask
|
||||||
from OTIOExportUI import OTIOExportUI
|
from OTIOExportUI import OTIOExportUI
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,91 @@
|
||||||
# MIT License
|
#!/usr/bin/env python
|
||||||
#
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2018 Daniel Flehner Heen (Storm Studios)
|
|
||||||
#
|
__author__ = "Daniel Flehner Heen"
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
__credits__ = ["Jakub Jezek", "Daniel Flehner Heen"]
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
|
||||||
# in the Software without restriction, including without limitation the rights
|
|
||||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
# copies of the Software, and to permit persons to whom the Software is
|
|
||||||
# furnished to do so, subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be included in
|
|
||||||
# all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
import hiero.ui
|
import hiero.ui
|
||||||
import hiero.core
|
import hiero.core
|
||||||
|
|
||||||
from otioimporter.OTIOImport import load_otio
|
import PySide2.QtWidgets as qw
|
||||||
|
|
||||||
|
from pype.hosts.hiero.otio.hiero_import import load_otio
|
||||||
|
|
||||||
|
|
||||||
|
class OTIOProjectSelect(qw.QDialog):
|
||||||
|
|
||||||
|
def __init__(self, projects, *args, **kwargs):
|
||||||
|
super(OTIOProjectSelect, self).__init__(*args, **kwargs)
|
||||||
|
self.setWindowTitle('Please select active project')
|
||||||
|
self.layout = qw.QVBoxLayout()
|
||||||
|
|
||||||
|
self.label = qw.QLabel(
|
||||||
|
'Unable to determine which project to import sequence to.\n'
|
||||||
|
'Please select one.'
|
||||||
|
)
|
||||||
|
self.layout.addWidget(self.label)
|
||||||
|
|
||||||
|
self.projects = qw.QComboBox()
|
||||||
|
self.projects.addItems(map(lambda p: p.name(), projects))
|
||||||
|
self.layout.addWidget(self.projects)
|
||||||
|
|
||||||
|
QBtn = qw.QDialogButtonBox.Ok | qw.QDialogButtonBox.Cancel
|
||||||
|
self.buttonBox = qw.QDialogButtonBox(QBtn)
|
||||||
|
self.buttonBox.accepted.connect(self.accept)
|
||||||
|
self.buttonBox.rejected.connect(self.reject)
|
||||||
|
|
||||||
|
self.layout.addWidget(self.buttonBox)
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
|
||||||
|
|
||||||
|
def get_sequence(view):
|
||||||
|
sequence = None
|
||||||
|
if isinstance(view, hiero.ui.TimelineEditor):
|
||||||
|
sequence = view.sequence()
|
||||||
|
|
||||||
|
elif isinstance(view, hiero.ui.BinView):
|
||||||
|
for item in view.selection():
|
||||||
|
if not hasattr(item, 'acitveItem'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(item.activeItem(), hiero.core.Sequence):
|
||||||
|
sequence = item.activeItem()
|
||||||
|
|
||||||
|
return sequence
|
||||||
|
|
||||||
|
|
||||||
def OTIO_menu_action(event):
|
def OTIO_menu_action(event):
|
||||||
otio_action = hiero.ui.createMenuAction(
|
# Menu actions
|
||||||
'Import OTIO',
|
otio_import_action = hiero.ui.createMenuAction(
|
||||||
|
'Import OTIO...',
|
||||||
open_otio_file,
|
open_otio_file,
|
||||||
icon=None
|
icon=None
|
||||||
)
|
)
|
||||||
hiero.ui.registerAction(otio_action)
|
|
||||||
|
otio_add_track_action = hiero.ui.createMenuAction(
|
||||||
|
'New Track(s) from OTIO...',
|
||||||
|
open_otio_file,
|
||||||
|
icon=None
|
||||||
|
)
|
||||||
|
otio_add_track_action.setEnabled(False)
|
||||||
|
|
||||||
|
hiero.ui.registerAction(otio_import_action)
|
||||||
|
hiero.ui.registerAction(otio_add_track_action)
|
||||||
|
|
||||||
|
view = hiero.ui.currentContextMenuView()
|
||||||
|
|
||||||
|
if view:
|
||||||
|
sequence = get_sequence(view)
|
||||||
|
if sequence:
|
||||||
|
otio_add_track_action.setEnabled(True)
|
||||||
|
|
||||||
for action in event.menu.actions():
|
for action in event.menu.actions():
|
||||||
if action.text() == 'Import':
|
if action.text() == 'Import':
|
||||||
action.menu().addAction(otio_action)
|
action.menu().addAction(otio_import_action)
|
||||||
break
|
action.menu().addAction(otio_add_track_action)
|
||||||
|
|
||||||
|
elif action.text() == 'New Track':
|
||||||
|
action.menu().addAction(otio_add_track_action)
|
||||||
|
|
||||||
|
|
||||||
def open_otio_file():
|
def open_otio_file():
|
||||||
|
|
@ -45,8 +94,39 @@ def open_otio_file():
|
||||||
pattern='*.otio',
|
pattern='*.otio',
|
||||||
requiredExtension='.otio'
|
requiredExtension='.otio'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
selection = None
|
||||||
|
sequence = None
|
||||||
|
|
||||||
|
view = hiero.ui.currentContextMenuView()
|
||||||
|
if view:
|
||||||
|
sequence = get_sequence(view)
|
||||||
|
selection = view.selection()
|
||||||
|
|
||||||
|
if sequence:
|
||||||
|
project = sequence.project()
|
||||||
|
|
||||||
|
elif selection:
|
||||||
|
project = selection[0].project()
|
||||||
|
|
||||||
|
elif len(hiero.core.projects()) > 1:
|
||||||
|
dialog = OTIOProjectSelect(hiero.core.projects())
|
||||||
|
if dialog.exec_():
|
||||||
|
project = hiero.core.projects()[dialog.projects.currentIndex()]
|
||||||
|
|
||||||
|
else:
|
||||||
|
bar = hiero.ui.mainWindow().statusBar()
|
||||||
|
bar.showMessage(
|
||||||
|
'OTIO Import aborted by user',
|
||||||
|
timeout=3000
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
else:
|
||||||
|
project = hiero.core.projects()[-1]
|
||||||
|
|
||||||
for otio_file in files:
|
for otio_file in files:
|
||||||
load_otio(otio_file)
|
load_otio(otio_file, project, sequence)
|
||||||
|
|
||||||
|
|
||||||
# HieroPlayer is quite limited and can't create transitions etc.
|
# HieroPlayer is quite limited and can't create transitions etc.
|
||||||
|
|
@ -55,3 +135,7 @@ if not hiero.core.isHieroPlayer():
|
||||||
"kShowContextMenu/kBin",
|
"kShowContextMenu/kBin",
|
||||||
OTIO_menu_action
|
OTIO_menu_action
|
||||||
)
|
)
|
||||||
|
hiero.core.events.registerInterest(
|
||||||
|
"kShowContextMenu/kTimeline",
|
||||||
|
OTIO_menu_action
|
||||||
|
)
|
||||||
|
|
|
||||||
1
test_localsystem.txt
Normal file
1
test_localsystem.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
I have run
|
||||||
Loading…
Add table
Add a link
Reference in a new issue