Skip to content

Commit 0ea78dd

Browse files
Merge branch 'master' into MB_DEV
* master: (23 commits) Indicate Empty track in otioview and display track name (AcademySoftwareFoundation#677) FCP 7 XML - Fix failure on empty name tags (AcademySoftwareFoundation#674) xges: Effects and Markers Support (AcademySoftwareFoundation#609) Add List of Supported Formats to Conform.py Help Text (AcademySoftwareFoundation#676) Update Copyright/License on ffmpeg_burnins.py (AcademySoftwareFoundation#679) Fix the windows build (AcademySoftwareFoundation#669) Version bump to beta 13 Set final version for beta 12.0 (AcademySoftwareFoundation#665) Rodeofx fix cmx 3600 multiple markers per clip issue 593 (AcademySoftwareFoundation#664) Tweaks to cmake so that pip and local builds both work (AcademySoftwareFoundation#663) Fixed issue where CMX3600 adapter would try to add the same clip to multiple tracks. Also moved some code out of a loop it didn't need to be in. (AcademySoftwareFoundation#644) RV adapter metadata updates (AcademySoftwareFoundation#640) Expose json indent to the otio_json adapter (AcademySoftwareFoundation#641) fix otioconvert for Kdenlive with python3 (AcademySoftwareFoundation#646) Add basic debugging instructions to quickstart. (AcademySoftwareFoundation#655) Add kdenlive adapter to adapters list. (AcademySoftwareFoundation#661) Timecode rate is ignored (AcademySoftwareFoundation#612) Detect if plugin doesn't have a docstring and return an error which says which plugin it is that doesn't have the docstring. (AcademySoftwareFoundation#635) Add hook function args to otioview and otioconvert (AcademySoftwareFoundation#651) Updating Copyright notices (AcademySoftwareFoundation#660) ... # Conflicts: # contrib/opentimelineio_contrib/adapters/advanced_authoring_format.py # src/py-opentimelineio/opentimelineio/adapters/fcp_xml.py
2 parents ca782fa + 89badc7 commit 0ea78dd

File tree

9 files changed

+26106
-880
lines changed

9 files changed

+26106
-880
lines changed

contrib/opentimelineio_contrib/adapters/tests/tests_xges_adapter.py

Lines changed: 1347 additions & 211 deletions
Large diffs are not rendered by default.

contrib/opentimelineio_contrib/adapters/xges.py

Lines changed: 2279 additions & 636 deletions
Large diffs are not rendered by default.

docs/tutorials/otio-plugins.md

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ RvSession Adapter harness
535535
### xges
536536

537537
```
538-
OpenTimelineIO GStreamer Editing Services XML Adapter.
538+
OpenTimelineIO GStreamer Editing Services XML Adapter.
539539
```
540540

541541
*source*: `opentimelineio_contrib/adapters/xges.py`
@@ -591,14 +591,42 @@ schemadef</a>
591591
### xges
592592

593593
```
594-
OpenTimelineIO GStreamer Editing Services XML Adapter.
594+
OpenTimelineIO GStreamer Editing Services XML Adapter.
595595
```
596596

597597
*source*: `opentimelineio_contrib/adapters/xges.py`
598598

599599

600600
*Serializable Classes*:
601601

602+
- GESMarker:
603+
```
604+
An OpenTimelineIO Schema that is a timestamp with metadata,
605+
essentially mimicking the GstMarker of the GES C libarary.
606+
```
607+
- GESMarkerList:
608+
```
609+
An OpenTimelineIO Schema that is a list of GESMarkers,
610+
ordered by
611+
their positions, essentially mimicking the GstMarkerList of the GES
612+
C libarary.
613+
```
614+
- GstCaps:
615+
```
616+
An OpenTimelineIO Schema that acts as an ordered collection of
617+
GstStructures, essentially mimicking the GstCaps of the Gstreamer C
618+
libarary. Each GstStructure is linked to a GstCapsFeatures, which is
619+
a list of features.
620+
621+
In particular, this schema mimics the gst_caps_to_string and
622+
gst_caps_from_string C methods.
623+
```
624+
- GstCapsFeatures:
625+
```
626+
An OpenTimelineIO Schema that contains a collection of
627+
features,
628+
mimicking a GstCapsFeatures of the Gstreamer C libarary.
629+
```
602630
- GstStructure:
603631
```
604632
An OpenTimelineIO Schema that acts as a named dictionary with
@@ -630,6 +658,12 @@ An OpenTimelineIO Schema that acts as a named dictionary with
630658
string str or None str, s
631659
GstFraction str or fraction
632660
Fraction
661+
GstStructure GstStructure structure
662+
schema
663+
GstCaps GstCaps
664+
schema
665+
GESMarkerList GESMarkerList
666+
schema
633667
634668
Note that other types can be given: these must be given as strings
635669
and the user will be responsible for making sure they are already in

src/opentimelineview/ruler_widget.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,8 @@ def set_time_space_callback(self, time_space):
134134

135135
def mouseMoveEvent(self, mouse_event):
136136
pos = self.mapToScene(mouse_event.pos())
137-
self.setPos(QtCore.QPointF(pos.x(),
137+
self.setPos(QtCore.QPointF(max(pos.x() - track_widgets.CURRENT_ZOOM_LEVEL *
138+
track_widgets.TRACK_NAME_WIDGET_WIDTH, 0),
138139
track_widgets.TIME_SLIDER_HEIGHT -
139140
track_widgets.MARKER_SIZE))
140141
self.update_frame()
@@ -192,7 +193,9 @@ def map_to_time_space(self, item):
192193
is_tail = False
193194
f = "-?-"
194195

195-
ratio = (self.x() - item.x()) / float(item.rect().width())
196+
ratio = (self.x() - item.x() +
197+
track_widgets.CURRENT_ZOOM_LEVEL *
198+
track_widgets.TRACK_NAME_WIDGET_WIDTH) / float(item.rect().width())
196199

197200
# The 'if' condition should be : ratio < 0 or ration >= 1
198201
# However, we are cheating in order to display the last frame of

src/opentimelineview/timeline_widget.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -205,11 +205,11 @@ def _add_tracks(self):
205205
if isinstance(self.composition, otio.schema.Stack):
206206
video_tracks = [
207207
t for t in self.composition
208-
if t.kind == otio.schema.TrackKind.Video and list(t)
208+
if t.kind == otio.schema.TrackKind.Video
209209
]
210210
audio_tracks = [
211211
t for t in self.composition
212-
if t.kind == otio.schema.TrackKind.Audio and list(t)
212+
if t.kind == otio.schema.TrackKind.Audio
213213
]
214214
video_tracks.reverse()
215215

@@ -219,8 +219,7 @@ def _add_tracks(self):
219219
t.kind not in (
220220
otio.schema.TrackKind.Video,
221221
otio.schema.TrackKind.Audio
222-
) and
223-
list(t)
222+
)
224223
)
225224
]
226225
else:
@@ -463,14 +462,14 @@ def wheelEvent(self, event):
463462
scale_by = 1.0 + float(event.delta()) / 1000
464463
self.scale(scale_by, 1)
465464
zoom_level = 1.0 / self.matrix().m11()
465+
track_widgets.CURRENT_ZOOM_LEVEL = zoom_level
466466

467467
# some items we do want to keep the same visual size. So we need to
468468
# inverse the effect of the zoom
469469
items_to_scale = [
470470
i for i in self.scene().items()
471-
if isinstance(i, track_widgets.BaseItem) or
472-
isinstance(i, track_widgets.Marker) or
473-
isinstance(i, ruler_widget.Ruler)
471+
if (isinstance(i, (track_widgets.BaseItem, track_widgets.Marker,
472+
ruler_widget.Ruler, track_widgets.TimeSlider)))
474473
]
475474

476475
for item in items_to_scale:
@@ -712,14 +711,11 @@ def frame_all(self):
712711
scaleFactor = self.size().width() / self.sceneRect().width()
713712
self.scale(scaleFactor * zoom_level, 1)
714713
zoom_level = 1.0 / self.matrix().m11()
715-
714+
track_widgets.CURRENT_ZOOM_LEVEL = zoom_level
716715
items_to_scale = [
717716
i for i in self.scene().items()
718-
if (
719-
isinstance(i, track_widgets.BaseItem) or
720-
isinstance(i, track_widgets.Marker) or
721-
isinstance(i, ruler_widget.Ruler)
722-
)
717+
if (isinstance(i, (track_widgets.BaseItem, track_widgets.Marker,
718+
ruler_widget.Ruler, track_widgets.TimeSlider)))
723719
]
724720
# some items we do want to keep the same visual size. So we need to
725721
# inverse the effect of the zoom

src/opentimelineview/track_widgets.py

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
from PySide2 import QtGui, QtCore, QtWidgets
2626
import opentimelineio as otio
2727

28-
2928
TIME_SLIDER_HEIGHT = 20
3029
MEDIA_TYPE_SEPARATOR_HEIGHT = 5
3130
TRACK_HEIGHT = 45
@@ -35,6 +34,8 @@
3534
MARKER_SIZE = 10
3635
EFFECT_HEIGHT = (1.0 / 3.0) * TRACK_HEIGHT
3736
HIGHLIGHT_WIDTH = 5
37+
TRACK_NAME_WIDGET_WIDTH = 100.0
38+
CURRENT_ZOOM_LEVEL = 1.0
3839

3940

4041
class BaseItem(QtWidgets.QGraphicsRectItem):
@@ -67,6 +68,8 @@ def __init__(self, item, timeline_range, *args, **kwargs):
6768
self._set_labels()
6869
self._set_tooltip()
6970

71+
self.x_value = 0.0
72+
7073
def paint(self, *args, **kwargs):
7174
new_args = [args[0],
7275
QtWidgets.QStyleOptionGraphicsItem()] + list(args[2:])
@@ -185,10 +188,11 @@ def _set_tooltip(self):
185188
self.setToolTip(self.item.name)
186189

187190
def counteract_zoom(self, zoom_level=1.0):
191+
self.setX(self.x_value + TRACK_NAME_WIDGET_WIDTH * zoom_level)
188192
for label in (
189-
self.source_name_label,
190-
self.source_in_label,
191-
self.source_out_label
193+
self.source_name_label,
194+
self.source_in_label,
195+
self.source_out_label
192196
):
193197
label.setTransform(QtGui.QTransform.fromScale(zoom_level, 1.0))
194198

@@ -230,6 +234,39 @@ def __init__(self, *args, **kwargs):
230234
self.source_name_label.setText('GAP')
231235

232236

237+
class TrackNameItem(BaseItem):
238+
239+
def __init__(self, track, rect, *args, **kwargs):
240+
super(TrackNameItem, self).__init__(None, None, rect,
241+
*args, **kwargs)
242+
track_name = 'Track' if track.name == '' else track.name
243+
if len(track_name) > 7:
244+
track_name = track_name[:7] + '...'
245+
self.source_name_label.setText(track_name + '\n({})'.format(track.kind))
246+
self.source_name_label.setY(
247+
(TRACK_HEIGHT -
248+
self.source_name_label.boundingRect().height()) / 2.0
249+
)
250+
self.setToolTip('{} items'.format(len(track)))
251+
252+
def itemChange(self, change, value):
253+
return super(BaseItem, self).itemChange(change, value)
254+
255+
def _add_markers(self):
256+
return
257+
258+
def _set_labels(self):
259+
return
260+
261+
def _set_tooltip(self):
262+
return
263+
264+
def counteract_zoom(self, zoom_level=1.0):
265+
name_width = self.source_name_label.boundingRect().width()
266+
self.source_name_label.setX(0.5 * (self.boundingRect().width() - name_width))
267+
self.setTransform(QtGui.QTransform.fromScale(zoom_level, 1.0))
268+
269+
233270
class EffectItem(QtWidgets.QGraphicsRectItem):
234271

235272
def __init__(self, item, rect, *args, **kwargs):
@@ -370,6 +407,16 @@ def __init__(self, track, *args, **kwargs):
370407

371408
def _populate(self):
372409
track_map = self.track.range_of_all_children()
410+
track_name_rect = QtCore.QRectF(
411+
0,
412+
0,
413+
TRACK_NAME_WIDGET_WIDTH,
414+
TRACK_HEIGHT
415+
)
416+
track_name_item = TrackNameItem(self.track, track_name_rect)
417+
track_name_item.setParentItem(self)
418+
track_name_item.setX(0)
419+
track_name_item.counteract_zoom()
373420
for n, item in enumerate(self.track):
374421
timeline_range = track_map[item]
375422

@@ -396,6 +443,8 @@ def _populate(self):
396443
continue
397444

398445
new_item.setParentItem(self)
446+
new_item.x_value = otio.opentime.to_seconds(
447+
timeline_range.start_time) * TIME_MULTIPLIER
399448
new_item.setX(
400449
otio.opentime.to_seconds(timeline_range.start_time) *
401450
TIME_MULTIPLIER
@@ -450,12 +499,16 @@ def __init__(self, *args, **kwargs):
450499

451500
def mousePressEvent(self, mouse_event):
452501
pos = self.mapToScene(mouse_event.pos())
453-
self._ruler.setPos(QtCore.QPointF(pos.x(),
454-
TIME_SLIDER_HEIGHT -
455-
MARKER_SIZE))
502+
self._ruler.setPos(QtCore.QPointF(
503+
max(pos.x() - TRACK_NAME_WIDGET_WIDTH * CURRENT_ZOOM_LEVEL, 0),
504+
TIME_SLIDER_HEIGHT -
505+
MARKER_SIZE))
456506
self._ruler.update_frame()
457507

458508
super(TimeSlider, self).mousePressEvent(mouse_event)
459509

460510
def add_ruler(self, ruler):
461511
self._ruler = ruler
512+
513+
def counteract_zoom(self, zoom_level=1.0):
514+
self.setX(zoom_level * TRACK_NAME_WIDGET_WIDTH)

src/py-opentimelineio/opentimelineio/adapters/fcp_xml.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -181,18 +181,19 @@ def _element_identification_string(element):
181181

182182
def _name_from_element(element):
183183
"""
184-
Fetches the name from the ``name`` element child of the provided element.
185-
If no element exists, returns ``None``.
184+
Fetches a name suitable for OTIO objects from the ``name`` element child
185+
of the provided element.
186+
If no element exists, returns empty string.
186187
187188
:param element: The element to find the name for.
188189
189-
:return: The name string or ``None``
190+
:return: The name string or and empty string
190191
"""
191192
name_elem = element.find("./name")
192193
if name_elem is not None:
193-
return name_elem.text
194+
return name_elem.text if name_elem.text is not None else ""
194195

195-
return None
196+
return ""
196197

197198

198199
def _rate_for_element(element):
@@ -1107,8 +1108,7 @@ def effect_from_filter_element(self, filter_element):
11071108
"could not find effect in filter: {}".format(filter_element)
11081109
)
11091110

1110-
name = effect_element.find("./name").text
1111-
name = name if name else "NameNotFound"
1111+
name = _name_from_element(effect_element)
11121112

11131113
effect_metadata = _xml_tree_to_dict(effect_element, {"name"})
11141114

@@ -1139,7 +1139,7 @@ def transition_for_element(self, item_element, context):
11391139
cut_point = _transition_cut_point(item_element, context)
11401140

11411141
transition = schema.Transition(
1142-
name=item_element.find('./effect/name').text,
1142+
name=_name_from_element(item_element.find('./effect')),
11431143
transition_type=schema.TransitionTypes.SMPTE_Dissolve,
11441144
in_offset=cut_point - start,
11451145
out_offset=end - cut_point,

0 commit comments

Comments
 (0)