Skip to content

Commit

Permalink
Add RV as a participant using the new Live Review schema (AcademySoft…
Browse files Browse the repository at this point in the history
…wareFoundation#602)

### Add RV as a participant using the new Live Review schema

### Summarize your change.

The `read_otio_string` function was added to parse the OTIO object
containing the timeline sent by the Review App when RV is joining a new
session as a participant. Since the timeline contains some annotation
fields, the annotation schema was added in order to let OTIO know that
it is a known schema during the deserialization step. However, nothing
is done with the annotations at this stage.

### Describe the reason for the change.

This is the first step in adding back the new OTIO schema as the
preferred way of communication between RV and the Review App.

### Describe what you have tested and on which operating system.

Live review using the new schema was tested between a local Review App
where the feature was enabled with
https://creative-collab.dev.shotguncloud.com and RV on MacOS.

---------

Signed-off-by: Éloïse Brosseau <[email protected]>
  • Loading branch information
eloisebrosseau authored Oct 28, 2024
1 parent abc3cb8 commit e055db3
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 3 deletions.
1 change: 1 addition & 0 deletions docs/rv-packages/rv-otio-reader.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The RV package is installed in the usual Python package installation location: `
The package also uses a number of files located in `Plugins/SupportFiles/otio_reader` and detailed below.

- `manifest.json`: Provides examples of schemas and hooks used in the import process.
- `annotation_schema.py` An example schema of an annotation.
- `cdlExportHook.py`: An example of exporting an RVLinearize to a custom CDL effect in OTIO.
- `cdlHook.py`: An example of importing a custom CDL effect in OTIO into an RVLinearize node.
- `cdlSchema.py` An example schema of a CDL effect.
Expand Down
10 changes: 9 additions & 1 deletion src/plugins/rv-packages/otio_reader/PACKAGE
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ modes:
files:
- file: manifest.json
location: SupportFiles/$PACKAGE
- file: annotation_schema.py
location: SupportFiles/$PACKAGE
- file: cdlSchema.py
location: SupportFiles/$PACKAGE
- file: cdlHook.py
Expand Down Expand Up @@ -83,6 +85,13 @@ description: |
</p>
</li>

<li>
<p>
annotation_schema.py
An example schema of an annotation.
</p>
</li>

<li>
<p>
cdlHook.py
Expand Down Expand Up @@ -260,4 +269,3 @@ description: |
<p>
For pre- and post- hooks, no return value is expected.
</p>

59 changes: 59 additions & 0 deletions src/plugins/rv-packages/otio_reader/annotation_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# *****************************************************************************
# Copyright 2024 Autodesk, Inc. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
# *****************************************************************************

"""
For our OTIO output to effectively interface with other programs
using the OpenTimelineIO Python API, our custom schema need to be
specified and registered with the API.
As per OTIO documentation, a class such as this one must be created,
the schema must be registered with a PluginManifest, and the path to that
manifest must be added to $OTIO_PLUGIN_MANIFEST_PATH; then the schema
is ready to be used.
Example:
myObject = otio.schemadef.Annotation.Annotation(name, visible, layers)
"""

import opentimelineio as otio


@otio.core.register_type
class Annotation(otio.schema.Effect):
"""A schema for annotations."""

_serializable_label = "Annotation.1"
_name = "Annotation"

def __init__(
self, name: str = "", visible: bool = True, layers: list | None = None
) -> None:
super().__init__(name=name, effect_name="Annotation.1")
self.visible = visible
self.layers = layers

_visible = otio.core.serializable_field(
"visible", required_type=bool, doc=("Visible: expects either true or false")
)

_layers = otio.core.serializable_field(
"layers", required_type=list, doc=("Layers: expects a list of annotation types")
)

@property
def layers(self) -> list:
return self._layers

@layers.setter
def layers(self, val: list):
self._layers = val

def __str__(self) -> str:
return f"Annotation({self.name}, {self.effect_name}, {self.visible}, {self.layers})"

def __repr__(self) -> str:
return f"otio.schema.Annotation(name={self.name!r}, effect_name={self.effect_name!r}, visible={self.visible!r}, layers={self.layers!r})"
6 changes: 6 additions & 0 deletions src/plugins/rv-packages/otio_reader/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
{
"OTIO_SCHEMA" : "PluginManifest.1",
"schemadefs" : [
{
"OTIO_SCHEMA" : "SchemaDef.1",
"name" : "Annotation",
"execution_scope" : "in process",
"filepath" : "annotation_schema.py"
},
{
"OTIO_SCHEMA" : "SchemaDef.1",
"name" : "CDL",
Expand Down
22 changes: 20 additions & 2 deletions src/plugins/rv-packages/otio_reader/otio_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ class NoNodeFromHook(otio.exceptions.OTIOError):
pass


def read_otio_string(otio_string: str, host_prefix: str | None = None) -> object | None:
"""
Main entry point to expand a given otio string into the current RV session.
Returns the top level node created that represents this otio
timeline.
"""
otio_obj = otio.adapters.read_from_string(otio_string)
timeline = otio_obj["otio"]

context = {"sg_url": host_prefix} if host_prefix else None

return create_rv_node_from_otio(timeline, context), timeline.global_start_time


def read_otio_file(otio_file):
"""
Main entry point to expand a given otio (or file otio can read)
Expand Down Expand Up @@ -276,7 +291,7 @@ def _create_track(in_seq, context=None):
return new_seq


def _get_global_transform(tl):
def _get_global_transform(tl) -> dict:
# since there's no global scale in otio, calculate the minimum box size
# that can contain all clips
def find_display_bounds(tl):
Expand Down Expand Up @@ -491,9 +506,12 @@ def add_media(media_ref, active_key, cmd, *cmd_args):
return source_group, active_source


def _get_media_path(target_url, context=None):
def _get_media_path(target_url: str, context: dict | None = None) -> str:
context = context or {}

if "sg_url" in context:
return context.get("sg_url") + target_url

if not os.path.isabs(target_url):
# if this is a relative file path, assume relative to the otio file
otio_path = context.get("otio_file")
Expand Down

0 comments on commit e055db3

Please sign in to comment.