OTIO Sync Protocol

This project synchronises live review sessions (playback, selection, and annotations) between hosts such as OpenRV and xStudio. It is built on two layers:

  1. The transport protocol — typed protocol messages that are wrapped in a small envelope and fanned out to every peer over RabbitMQ.
  2. OTIO add-ons — custom OpenTimelineIO schemas that let a timeline carry review/annotation data and discrete sync events.

Auto-generated API reference (built with make html in docs/):

  • otio_sync_core API — the core sync library (manager, protocol messages, network backends, proxy, patcher, colour, and annotation codec).
  • SyncEvent / OTIO add-ons API — the custom OpenTimelineIO schemas (SyncEvent and its subclasses).

Protocol messages

The transport layer is defined by typed ProtocolMessage classes. Each class is the single source of truth for one message: its SCHEMA, its EVENT, and the shape of its payload. Messages are pure data — they implement to_payload() / from_payload() and register themselves on (SCHEMA, EVENT) so the receive-side dispatcher cannot drift from the definitions.

Examples grouped by family:

Family (SCHEMA) Messages (EVENT)
LiveSession.1 WHO_IS_MASTER, I_AM_MASTER, STATE_REQUEST, STATE_SNAPSHOT, NEW_PRESENTER, NEW_PARTICIPANT, SHARED_KEY_REQUEST, SHARED_KEY_RESPONSE
TIMELINE_1.0 ADD_TIMELINE, RENAME_TIMELINE
PLAYBACK_SETTINGS_1.0 / DISPLAY_SETTINGS_1.0 SET
SELECTION_1.0 SET
Annotation.1 PARTIAL
OTIO_SESSION_1.0 SET_PROPERTY, INSERT_CHILD, MOVE_CHILD, REMOVE_CHILD, REPLACE_ANNOTATION_COMMANDS

Who may send what

Not every peer may emit every message, and the rules are worth reading before implementing against this protocol — none of them are visible in a message's shape.

Two of them are properties of the session, carried on STATE_SNAPSHOT: the per-category write leases in broadcast_ownership decide which peer is currently driving visibility, position, display, or structure. If no peer holds the visibility lease, it falls back to the elected host.

The third is a property of the participant: a peer's session roledriver, reviewer, or viewer — carried as a field on PEER_ANNOUNCE and on each entry of the STATE_SNAPSHOT peer roster, with the session's policy in that message's session_roles section. Role is a ceiling; the other two are gates. A driver has permission to emit visibility; the peer holding the visibility lease (or the elected host if the lease is unclaimed) is the one permitted to broadcast it.

Role is enforced by the sender, and is not validated on receipt. A receiving peer applies a message without checking what role its sender declared, and no broker-side filtering is involved. An implementer should not assume messages have been filtered by the sender's role: this gates accidents in a cooperating session, it is not access control. Consistent with that, an absent role — from a peer running older code, or an entry learned before its owner announced — means the session's default role, never the most restrictive one, and an absent session_roles section means "no policy declared" rather than an empty policy. A session that declares nothing behaves exactly as one predating roles: every peer may emit everything.

A role may also change mid-session: a peer already holding driver may grant another participant a role via SET_PEER_ROLE. The message is broadcast — every peer merges it into its own copy of the session's role memory — and is applied by its target, which then re-announces; PEER_ANNOUNCE stays the only path that writes a role into the peer table. An implementer should not write a grant's role into the peer table directly on receipt.


How a message is wrapped and sent

When the manager broadcasts a message it calls _send_message() (manager.py), which wraps the typed message in an envelope. The envelope's command_schema, command.event, and command.payload come straight from the message class:

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "SELECTION_1.0",
    "command": {
      "event": "SET",
      "payload": {
        "clip_guid": "abc123",
        "view_mode": "source",
        "sync_timestamp": 1747123456.789
      }
    }
  }
}
  • session — scopes the message to one review session.
  • source_guid — the sending peer; receivers discard their own messages.
  • payload.command_schema + payload.command.event — the dispatch key (SCHEMA, EVENT) that maps back to a ProtocolMessage class.
  • payload.command.payload — the result of msg.to_payload().

One message (I_AM_MASTER) also sets a legacy top-level "schema" key (SYNC_REVIEW_1.0) via its ENVELOPE_SCHEMA, for compatibility with older peers.

Onto RabbitMQ

The envelope is handed to the network backend (rabbitmq_network.py), which:

  1. JSON-encodes it: json.dumps(envelope).encode("utf-8").
  2. Publishes it to a fanout exchange named sync_session_<session_id> with an empty routing key.

Because the exchange is a fanout, every peer that has bound an (exclusive, auto-named) queue to that exchange receives every message. On receipt a peer decodes the JSON, ignores anything from its own source_guid, looks up the (command_schema, event) pair, reconstructs the message via from_payload(), and dispatches it to the registered handler.

So the full path is:

ProtocolMessage  ──to_payload()──▶  envelope dict  ──json.dumps──▶
    fanout exchange "sync_session_<id>"  ──▶  every peer's queue  ──▶
        from_payload()  ──▶  handler

OTIO add-ons

OpenTimelineIO is extended through a plugin manifest (otio_event_plugin/plugin_manifest.json), which registers a SchemaDef pointing at schemadefs/SyncEvent.py. When this plugin is on OTIO_PLUGIN_MANIFEST_PATH, OTIO can read and write these types natively — they round-trip through otio_json like any built-in schema.

There are two kinds of add-on:

AnnotationEffect

An otio.schema.Effect subclass (AnnotationEffect.1) attached to a clip. It holds the persisted annotation layers/commands for that clip — i.e. the durable record of what was drawn, stored inside the timeline so it survives export and re-import.

SyncEvent and its subclasses

SyncEvent is the base SerializableObject for discrete, timestamped events. Subclasses describe a single thing that happened in a review, for example:

Schema Purpose
PaintStart.1 Opens a stroke (brush, colour, width, uuid)
PaintPoint.1 Appends a batch of points to the active stroke
PaintEnd.1 Closes the active stroke
TextAnnotation.1 A positioned text label with font metadata
Play.1, SetCurrentFrame.1 Playback state changes

These are the OTIO-native representation of annotation content. They are stored in the timeline (under an AnnotationEffect) and are also embedded inside some transport messages when annotation data needs to travel between peers.

Note: session/handshake concerns (presenter, participant, shared key) are not OTIO add-ons — they live on the transport layer as protocol messages (see below).

AnnotationEffect

AnnotationEffect.1
A schema for annotations.

Parameters

ParameterTypeDescription
nameOptional str
visibleOptional bool visible: expects either true or false
layersOptional list | None
commandsOptional list commands: expects a list of sync commands

Examples

{
  "OTIO_SCHEMA": "AnnotationEffect.1",
  "commands": [],
  "visible": true,
  "metadata": {},
  "name": "Annotation",
  "effect_name": "Annotation.1",
  "enabled": true
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.AnnotationEffect(name='Annotation', visible=True, commands=[])
{
  "OTIO_SCHEMA": "AnnotationEffect.1",
  "commands": [
    {
      "OTIO_SCHEMA": "PaintStart.1",
      "brush": "circle",
      "friendly_name": "Director",
      "ghost": true,
      "ghost_after": 3,
      "ghost_before": 3,
      "hold": false,
      "layer_range": null,
      "participant_hash": "d3447b5cb61b41de73a2de39c4f06ab790e66e4cad81f7d449c0147a546244b5",
      "rgba": [
        1.0,
        1.0,
        0.0,
        1.0
      ],
      "source_index": 0,
      "timestamp": "2025-01-31T16: 14: 00Z",
      "type": "color",
      "uuid": "paint_stroke_uuid_001",
      "visible": true
    }
  ],
  "visible": true,
  "metadata": {},
  "name": "Review Annotations",
  "effect_name": "Annotation.1",
  "enabled": true
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.AnnotationEffect(name='Review Annotations', visible=True, commands=[otio.schema.schemadef.PaintStart(uuid='paint_stroke_uuid_001', friendly_name='Director', participant_hash='d3447b5cb61b41de73a2de39c4f06ab790e66e4cad81f7d449c0147a546244b5', rgba=[1.0, 1.0, 0.0, 1.0], type='color', brush='circle', visible=True, hold=False, ghost=True, ghost_before=3, ghost_after=3, timestamp='2025-01-31T16:14:00Z')])

PaintStart

PaintStart.1
A schema for the event system to denote when painting starts.

Parameters

ParameterTypeDescription
source_indexOptional int The index of the source media for the paint.
uuidOptional str The unique identifier for the paint event
friendly_nameOptional str The friendly artist name for the paint event creator
participant_hashOptional str The unique identifier for the participant
rgbaOptional list The color of the paint event in RGBA format
typeOptional str The type of the paint event
brushOptional str The brush type of the paint event
visibleOptional bool The visible type of the paint event
nameOptional str
effect_nameOptional str
layer_rangeOptional TimeRange The range of the layer for the paint event
holdOptional bool The hold of the paint event
ghostOptional bool Is ghosting of the paint strokes enabled
ghost_beforeOptional int Number of frames to ghost before the current frame
ghost_afterOptional int Number of frames to ghost after the current frame
timestampOptional str

Examples

{
  "OTIO_SCHEMA": "PaintStart.1",
  "brush": "circle",
  "friendly_name": "Patrick Chevalier",
  "ghost": true,
  "ghost_after": 3,
  "ghost_before": 3,
  "hold": false,
  "layer_range": {
    "OTIO_SCHEMA": "TimeRange.1",
    "duration": {
      "OTIO_SCHEMA": "RationalTime.1",
      "rate": 24.0,
      "value": 1.0
    },
    "start_time": {
      "OTIO_SCHEMA": "RationalTime.1",
      "rate": 24.0,
      "value": 0.0
    }
  },
  "participant_hash": "d3447b5cb61b41de73a2de39c4f06ab790e66e4cad81f7d449c0147a546244b5",
  "rgba": [
    1.0,
    1.0,
    0.0,
    1.0
  ],
  "source_index": 0,
  "timestamp": "2025-01-31T16: 14: 00Z",
  "type": "color",
  "uuid": "paint_stroke_uuid_001",
  "visible": true
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.PaintStart(source_index=0, uuid='paint_stroke_uuid_001', friendly_name='Patrick Chevalier', participant_hash='d3447b5cb61b41de73a2de39c4f06ab790e66e4cad81f7d449c0147a546244b5', rgba=[1.0, 1.0, 0.0, 1.0], type='color', brush='circle', visible=True, layer_range=otio.opentime.TimeRange(start_time=otio.opentime.RationalTime(value=0.0, rate=24.0), duration=otio.opentime.RationalTime(value=1.0, rate=24.0)), hold=False, ghost=True, ghost_before=3, ghost_after=3, timestamp='2025-01-31T16:14:00Z')
{
  "OTIO_SCHEMA": "PaintStart.1",
  "brush": "gaussian",
  "friendly_name": "Jane Doe",
  "ghost": false,
  "ghost_after": null,
  "ghost_before": null,
  "hold": true,
  "layer_range": {
    "OTIO_SCHEMA": "TimeRange.1",
    "duration": {
      "OTIO_SCHEMA": "RationalTime.1",
      "rate": 24.0,
      "value": 1.0
    },
    "start_time": {
      "OTIO_SCHEMA": "RationalTime.1",
      "rate": 24.0,
      "value": 5.0
    }
  },
  "participant_hash": "abc123def456ghi789",
  "rgba": [
    1.0,
    0.0,
    0.0,
    1.0
  ],
  "source_index": 0,
  "timestamp": "2025-01-31T16: 14: 30Z",
  "type": "color",
  "uuid": "paint_stroke_uuid_002",
  "visible": true
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.PaintStart(source_index=0, uuid='paint_stroke_uuid_002', friendly_name='Jane Doe', participant_hash='abc123def456ghi789', rgba=[1.0, 0.0, 0.0, 1.0], type='color', brush='gaussian', visible=True, layer_range=otio.opentime.TimeRange(start_time=otio.opentime.RationalTime(value=5.0, rate=24.0), duration=otio.opentime.RationalTime(value=1.0, rate=24.0)), hold=True, ghost=False, timestamp='2025-01-31T16:14:30Z')
{
  "OTIO_SCHEMA": "PaintStart.1",
  "brush": "circle",
  "friendly_name": "John Smith",
  "ghost": null,
  "ghost_after": null,
  "ghost_before": null,
  "hold": null,
  "layer_range": {
    "OTIO_SCHEMA": "TimeRange.1",
    "duration": {
      "OTIO_SCHEMA": "RationalTime.1",
      "rate": 24.0,
      "value": 1.0
    },
    "start_time": {
      "OTIO_SCHEMA": "RationalTime.1",
      "rate": 24.0,
      "value": 10.0
    }
  },
  "participant_hash": "xyz789abc123",
  "rgba": [
    1.0,
    1.0,
    1.0,
    1.0
  ],
  "source_index": 0,
  "timestamp": "2025-01-31T16: 15: 00Z",
  "type": "erase",
  "uuid": "eraser_stroke_uuid_003",
  "visible": true
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.PaintStart(source_index=0, uuid='eraser_stroke_uuid_003', friendly_name='John Smith', participant_hash='xyz789abc123', rgba=[1.0, 1.0, 1.0, 1.0], type='erase', brush='circle', visible=True, layer_range=otio.opentime.TimeRange(start_time=otio.opentime.RationalTime(value=10.0, rate=24.0), duration=otio.opentime.RationalTime(value=1.0, rate=24.0)), timestamp='2025-01-31T16:15:00Z')

PaintVertices

PaintVertices.1
A schema for the definition of a paint stroke. Grouping paint vertices together to form a stroke, and be a little more efficient in storage.

Parameters

ParameterTypeDescription
xOptional list The x positions of the paint stroke
yOptional list The y positions of the paint stroke
sizeOptional list The sizes of the paint stroke
alphaOptional list The alpha values of the paint stroke, this is optional, and if not defined is assumed to be 1.0 for all points

Examples

{
  "OTIO_SCHEMA": "PaintVertices.1",
  "alpha": [],
  "size": [
    0.009,
    0.009,
    0.009,
    0.009
  ],
  "x": [
    0.0,
    0.1,
    0.2,
    0.3
  ],
  "y": [
    0.0,
    0.05,
    0.0,
    -0.05
  ]
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.PaintVertices(x=[0.0, 0.1, 0.2, 0.3], y=[0.0, 0.05, 0.0, -0.05], size=[0.009, 0.009, 0.009, 0.009])
{
  "OTIO_SCHEMA": "PaintVertices.1",
  "alpha": [],
  "size": [
    0.012,
    0.011,
    0.01,
    0.01,
    0.011,
    0.012
  ],
  "x": [
    0.0,
    0.2,
    0.4,
    0.6,
    0.8,
    1.0
  ],
  "y": [
    0.0,
    0.1,
    0.2,
    0.1,
    0.0,
    -0.1
  ]
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.PaintVertices(x=[0.0, 0.2, 0.4, 0.6, 0.8, 1.0], y=[0.0, 0.1, 0.2, 0.1, 0.0, -0.1], size=[0.012, 0.011, 0.01, 0.01, 0.011, 0.012])

PaintVertex

PaintVertex.1
A schema for the definition of a point vertex in a paint stroke.

Parameters

ParameterTypeDescription
xOptional float The x coordinate of the point vertex
yOptional float The y coordinate of the point vertex
sizeOptional float The size of the point vertex

Examples

{
  "OTIO_SCHEMA": "PaintVertex.1",
  "size": 0.009,
  "x": 0.15,
  "y": -0.08
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.PaintVertex(x=0.15, y=-0.08, size=0.009)
{
  "OTIO_SCHEMA": "PaintVertex.1",
  "size": 0.015,
  "x": -0.25,
  "y": 0.1
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.PaintVertex(x=-0.25, y=0.1, size=0.015)

PaintPoints

PaintPoint.1
A schema for the event system to denote when adding onto a paint stroke.

Parameters

ParameterTypeDescription
source_indexOptional int The index of the source media for the paint.
uuidOptional str The unique identifier for the paint event
layer_rangeOptional TimeRange The range of the layer for the paint event
pointsOptional PaintVertices The vertices of the paint event
timestampOptional str

Example

{
  "OTIO_SCHEMA": "PaintPoint.1",
  "layer_range": {
    "OTIO_SCHEMA": "TimeRange.1",
    "duration": {
      "OTIO_SCHEMA": "RationalTime.1",
      "rate": 24.0,
      "value": 1.0
    },
    "start_time": {
      "OTIO_SCHEMA": "RationalTime.1",
      "rate": 24.0,
      "value": 0.0
    }
  },
  "points": {
    "OTIO_SCHEMA": "PaintVertices.1",
    "alpha": [],
    "size": [
      0.009,
      0.009,
      0.009,
      0.009,
      0.009
    ],
    "x": [
      0.0,
      0.1,
      0.2,
      0.3,
      0.4
    ],
    "y": [
      0.0,
      0.1,
      0.0,
      0.0,
      -0.1
    ]
  },
  "source_index": 0,
  "timestamp": "2025-01-31T16: 14: 00.500Z",
  "uuid": "paint_stroke_uuid_001"
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.PaintPoints(source_index=0, uuid='paint_stroke_uuid_001', layer_range=otio.opentime.TimeRange(start_time=otio.opentime.RationalTime(value=0.0, rate=24.0), duration=otio.opentime.RationalTime(value=1.0, rate=24.0)), points=otio.schema.schemadef.PaintVertices(size=[0.009, 0.009, 0.009, 0.009, 0.009], x=[0.0, 0.1, 0.2, 0.3, 0.4], y=[0.0, 0.1, 0.0, 0.0, -0.1]), timestamp='2025-01-31T16:14:00.500Z')

PaintEnd

PaintEnd.1
A schema for the event system to denote when painting ends.

Parameters

ParameterTypeDescription
uuidOptional str The unique identifier for the paint event
pointsOptional PaintVertices The vertices of the paint event
timestampOptional str

Example

{
  "OTIO_SCHEMA": "PaintEnd.1",
  "points": {
    "OTIO_SCHEMA": "PaintVertices.1",
    "alpha": [],
    "size": [
      0.009,
      0.009,
      0.009,
      0.009,
      0.009
    ],
    "x": [
      0.0,
      0.1,
      0.2,
      0.3,
      0.4
    ],
    "y": [
      0.0,
      0.1,
      0.0,
      0.0,
      -0.1
    ]
  },
  "timestamp": "2025-01-31T16: 14: 01Z",
  "uuid": "paint_stroke_uuid_001"
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.PaintEnd(uuid='paint_stroke_uuid_001', timestamp='2025-01-31T16:14:01Z', points=otio.schema.schemadef.PaintVertices(size=[0.009, 0.009, 0.009, 0.009, 0.009], x=[0.0, 0.1, 0.2, 0.3, 0.4], y=[0.0, 0.1, 0.0, 0.0, -0.1]))

TextAnnotation

TextAnnotation.1
A schema for the event system to denote entering text.

Parameters

ParameterTypeDescription
uuidOptional str The unique identifier for the paint event
rgbaOptional list The color of the text annotation in RGBA format
friendly_nameOptional str The human usable name of the user who created the annotation
textOptional str The text of the annotation
spacingOptional float The spacing between lines of text
font_sizeOptional float The size of the font for the text annotation
scaleOptional float The scale of the text annotation
rotationOptional float The rotation of the text annotation in degrees
fontOptional str The font family of the text annotation
positionOptional list The position of the text annotation in the format [x, y]
timestampOptional str

Examples

{
  "OTIO_SCHEMA": "TextAnnotation.1",
  "font": "Arial",
  "font_size": 24.0,
  "friendly_name": "Director",
  "position": [
    5.5,
    2.0
  ],
  "rgba": [
    1.0,
    0.0,
    0.0,
    1.0
  ],
  "rotation": 0.0,
  "scale": 1.0,
  "spacing": 1.2,
  "text": "Fix the lighting in this shot",
  "timestamp": "2025-01-31T16: 14: 00Z",
  "uuid": "text_annotation_uuid_001"
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.TextAnnotation(uuid='text_annotation_uuid_001', friendly_name='Director', position=[5.5, 2.0], rgba=[1.0, 0.0, 0.0, 1.0], text='Fix the lighting in this shot', spacing=1.2, font_size=24.0, scale=1.0, rotation=0.0, font='Arial', timestamp='2025-01-31T16:14:00Z')
{
  "OTIO_SCHEMA": "TextAnnotation.1",
  "font": "Helvetica",
  "font_size": 36.0,
  "friendly_name": "Producer",
  "position": [
    -6.0,
    -3.0
  ],
  "rgba": [
    0.0,
    1.0,
    0.0,
    1.0
  ],
  "rotation": 0.0,
  "scale": 1.5,
  "spacing": 1.0,
  "text": "Approved!",
  "timestamp": "2025-01-31T16: 20: 00Z",
  "uuid": "text_annotation_uuid_002"
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.TextAnnotation(uuid='text_annotation_uuid_002', friendly_name='Producer', position=[-6.0, -3.0], rgba=[0.0, 1.0, 0.0, 1.0], text='Approved!', spacing=1.0, font_size=36.0, scale=1.5, rotation=0.0, font='Helvetica', timestamp='2025-01-31T16:20:00Z')
{
  "OTIO_SCHEMA": "TextAnnotation.1",
  "font": "Impact",
  "font_size": 28.0,
  "friendly_name": "VFX Supervisor",
  "position": [
    0.0,
    0.0
  ],
  "rgba": [
    1.0,
    1.0,
    0.0,
    1.0
  ],
  "rotation": 15.0,
  "scale": 1.2,
  "spacing": 1.1,
  "text": "Add more explosion here",
  "timestamp": "2025-01-31T16: 18: 00Z",
  "uuid": "text_annotation_uuid_003"
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.TextAnnotation(uuid='text_annotation_uuid_003', friendly_name='VFX Supervisor', position=[0.0, 0.0], rgba=[1.0, 1.0, 0.0, 1.0], text='Add more explosion here', spacing=1.1, font_size=28.0, scale=1.2, rotation=15.0, font='Impact', timestamp='2025-01-31T16:18:00Z')

EllipseAnnotation

EllipseAnnotation.1
A schema for an ellipse shape.

Parameters

ParameterTypeDescription
minOptional list The bounding box top-left corner coordinate in format [x, y]
maxOptional list The bounding box bottom-right corner coordinate in format [x, y]
rgbaOptional list The outline color of the ellipse in RGBA format
sizeOptional float The line width of the ellipse outline
inner_rgbaOptional list The inner fill color of the ellipse in RGBA format
visibleOptional bool The visibility of the ellipse annotation
uuidOptional str The unique identifier for the ellipse annotation
timestampOptional str

Examples

{
  "OTIO_SCHEMA": "EllipseAnnotation.1",
  "inner_rgba": [
    0.0,
    0.0,
    0.0,
    0.0
  ],
  "max": [
    0.25,
    -0.25
  ],
  "min": [
    -0.25,
    0.25
  ],
  "rgba": [
    1.0,
    0.0,
    0.0,
    1.0
  ],
  "size": 2.0,
  "timestamp": "2025-01-31T16: 14: 00Z",
  "uuid": "ellipse_annotation_uuid_001",
  "visible": true
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.EllipseAnnotation(uuid='ellipse_annotation_uuid_001', min=[-0.25, 0.25], max=[0.25, -0.25], rgba=[1.0, 0.0, 0.0, 1.0], size=2.0, inner_rgba=[0.0, 0.0, 0.0, 0.0], timestamp='2025-01-31T16:14:00Z')
{
  "OTIO_SCHEMA": "EllipseAnnotation.1",
  "inner_rgba": [
    0.0,
    0.0,
    1.0,
    0.5
  ],
  "max": [
    -0.15,
    0.15
  ],
  "min": [
    -0.45,
    0.45
  ],
  "rgba": [
    0.0,
    0.0,
    1.0,
    1.0
  ],
  "size": 1.5,
  "timestamp": "2025-01-31T16: 15: 00Z",
  "uuid": "ellipse_annotation_uuid_002",
  "visible": true
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.EllipseAnnotation(uuid='ellipse_annotation_uuid_002', min=[-0.45, 0.45], max=[-0.15, 0.15], rgba=[0.0, 0.0, 1.0, 1.0], size=1.5, inner_rgba=[0.0, 0.0, 1.0, 0.5], timestamp='2025-01-31T16:15:00Z')

RectangleAnnotation

RectangleAnnotation.1
A schema for a rectangle shape.

Parameters

ParameterTypeDescription
minOptional list The bounding box top-left corner coordinate in format [x, y]
maxOptional list The bounding box bottom-right corner coordinate in format [x, y]
rgbaOptional list The outline color of the rectangle in RGBA format
sizeOptional float The line width of the rectangle outline
inner_rgbaOptional list The inner fill color of the rectangle in RGBA format
visibleOptional bool The visibility of the rectangle annotation
uuidOptional str The unique identifier for the rectangle annotation
timestampOptional str

Examples

{
  "OTIO_SCHEMA": "RectangleAnnotation.1",
  "inner_rgba": [
    0.0,
    0.0,
    0.0,
    0.0
  ],
  "max": [
    0.2,
    -0.1
  ],
  "min": [
    -0.2,
    0.2
  ],
  "rgba": [
    0.0,
    1.0,
    0.0,
    1.0
  ],
  "size": 2.0,
  "timestamp": "2025-01-31T16: 14: 00Z",
  "uuid": "rectangle_annotation_uuid_001",
  "visible": true
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.RectangleAnnotation(uuid='rectangle_annotation_uuid_001', min=[-0.2, 0.2], max=[0.2, -0.1], rgba=[0.0, 1.0, 0.0, 1.0], size=2.0, inner_rgba=[0.0, 0.0, 0.0, 0.0], timestamp='2025-01-31T16:14:00Z')
{
  "OTIO_SCHEMA": "RectangleAnnotation.1",
  "inner_rgba": [
    1.0,
    0.0,
    0.0,
    0.7
  ],
  "max": [
    0.4,
    -0.1
  ],
  "min": [
    0.1,
    0.1
  ],
  "rgba": [
    1.0,
    1.0,
    0.0,
    1.0
  ],
  "size": 2.5,
  "timestamp": "2025-01-31T16: 16: 00Z",
  "uuid": "rectangle_annotation_uuid_002",
  "visible": true
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.RectangleAnnotation(uuid='rectangle_annotation_uuid_002', min=[0.1, 0.1], max=[0.4, -0.1], rgba=[1.0, 1.0, 0.0, 1.0], size=2.5, inner_rgba=[1.0, 0.0, 0.0, 0.7], timestamp='2025-01-31T16:16:00Z')

ArrowAnnotation

ArrowAnnotation.1
A schema for a straight arrow shape.

Parameters

ParameterTypeDescription
startOptional list The start tail coordinate in format [x, y]
endOptional list The end head coordinate in format [x, y]
rgbaOptional list The color of the arrow in RGBA format
sizeOptional float The line thickness of the arrow
visibleOptional bool The visibility of the arrow annotation
uuidOptional str The unique identifier for the arrow annotation
timestampOptional str

Example

{
  "OTIO_SCHEMA": "ArrowAnnotation.1",
  "end": [
    0.4,
    0.4
  ],
  "rgba": [
    1.0,
    1.0,
    1.0,
    1.0
  ],
  "size": 3.0,
  "start": [
    -0.4,
    -0.4
  ],
  "timestamp": "2025-01-31T16: 14: 00Z",
  "uuid": "arrow_annotation_uuid_001",
  "visible": true
}
# OTIO SyncEvent (serialized as OpenTimelineIO object)
import opentimelineio as otio
SyncEvent = otio.schema.schemadef.module_from_name('SyncEvent')

event = otio.schema.schemadef.ArrowAnnotation(uuid='arrow_annotation_uuid_001', start=[-0.4, -0.4], end=[0.4, 0.4], rgba=[1.0, 1.0, 1.0, 1.0], size=3.0, timestamp='2025-01-31T16:14:00Z')

PartialAnnotation

Annotation.1event: PARTIAL
Mid-stroke partial annotation (visual preview, not persisted). Hot path: fires repeatedly while a stroke is being drawn (before pen-up). Peers render the transient stroke visually, but do not write it to the local OTIO timeline. On pen-up/completion, a full stroke is committed via :class:`InsertChild` instead. No validation or reflective serialization is performed.

Parameters

ParameterTypeDescription
clip_guidOptional str Sync GUID of the clip being annotated.
frameOptional float 0-indexed clip-local frame number.
fpsOptional float Frame rate used to interpret 'frame'.
eventsOptional list Serialized SyncEvent dicts for the in-progress stroke.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "Annotation.1",
    "command": {
      "event": "PARTIAL",
      "payload": {
        "clip_guid": "abf2a376-917a-496d-9249-61ad5c2e8397",
        "frame": 89898.0,
        "fps": 24.0,
        "events": [
          {
            "OTIO_SCHEMA": "PaintStart.1",
            "brush": "circle",
            "friendly_name": "sam_81003",
            "ghost": false,
            "ghost_after": 0,
            "ghost_before": 0,
            "hold": false,
            "layer_range": null,
            "participant_hash": null,
            "rgba": [
              0.12207217514514923,
              0.12207217514514923,
              0.7411764860153198,
              1.0
            ],
            "source_index": 0,
            "timestamp": "2026-06-02T20: 46: 19.501940",
            "type": "color",
            "uuid": "aa726033-ff02-4d4c-bcca-f57f8ae77ea8",
            "visible": true
          },
          {
            "OTIO_SCHEMA": "PaintPoint.1",
            "layer_range": null,
            "points": {
              "OTIO_SCHEMA": "PaintVertices.1",
              "size": [
                0.006075292360037565,
                0.006075292360037565,
                0.006075292360037565,
                0.006075292360037565,
                0.006075292360037565,
                0.006075292360037565,
                0.006075292360037565,
                0.006075292360037565,
                0.006075292360037565,
                0.006075292360037565,
                0.006075292360037565,
                0.006075292360037565,
                0.006075292360037565
              ],
              "x": [
                -0.324410080909729,
                -0.3169737756252289,
                -0.3021010756492615,
                -0.2835102677345276,
                -0.26120126247406006,
                -0.17196524143218994,
                0.013943103142082691,
                0.05112481489777565,
                0.09202462434768677,
                0.10503822565078735,
                0.11991093307733536,
                0.1292063295841217,
                0.1292063295841217
              ],
              "y": [
                0.28072163462638855,
                0.27328526973724365,
                0.2621307969093323,
                0.25283536314964294,
                0.2509762942790985,
                0.24725812673568726,
                0.25841259956359863,
                0.2639898657798767,
                0.2751443386077881,
                0.28629887104034424,
                0.30117154121398926,
                0.3123260736465454,
                0.3160442113876343
              ]
            }
          }
        ]
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import PartialAnnotation

msg = PartialAnnotation(clip_guid='abf2a376-917a-496d-9249-61ad5c2e8397', frame=89898.0, fps=24.0, events=[{'OTIO_SCHEMA': 'PaintStart.1', 'brush': 'circle', 'friendly_name': 'sam_81003', 'ghost': False, 'ghost_after': 0, 'ghost_before': 0, 'hold': False, 'layer_range': None, 'participant_hash': None, 'rgba': [0.12207217514514923, 0.12207217514514923, 0.7411764860153198, 1.0], 'source_index': 0, 'timestamp': '2026-06-02T20:46:19.501940', 'type': 'color', 'uuid': 'aa726033-ff02-4d4c-bcca-f57f8ae77ea8', 'visible': True}, {'OTIO_SCHEMA': 'PaintPoint.1', 'layer_range': None, 'points': {'OTIO_SCHEMA': 'PaintVertices.1', 'size': [0.006075292360037565, 0.006075292360037565, 0.006075292360037565, 0.006075292360037565, 0.006075292360037565, 0.006075292360037565, 0.006075292360037565, 0.006075292360037565, 0.006075292360037565, 0.006075292360037565, 0.006075292360037565, 0.006075292360037565, 0.006075292360037565], 'x': [-0.324410080909729, -0.3169737756252289, -0.3021010756492615, -0.2835102677345276, -0.26120126247406006, -0.17196524143218994, 0.013943103142082691, 0.05112481489777565, 0.09202462434768677, 0.10503822565078735, 0.11991093307733536, 0.1292063295841217, 0.1292063295841217], 'y': [0.28072163462638855, 0.27328526973724365, 0.2621307969093323, 0.25283536314964294, 0.2509762942790985, 0.24725812673568726, 0.25841259956359863, 0.2639898657798767, 0.2751443386077881, 0.28629887104034424, 0.30117154121398926, 0.3123260736465454, 0.3160442113876343]}}])

InsertChild

OTIO_SESSION_1.0event: INSERT_CHILD
Inserts a child object into a parent container.

Parameters

ParameterTypeDescription
parent_uuidOptional str GUID of the parent container.
child_dataOptional Any OTIO child object (object on send, wire dict on receive).
indexOptional int Insert position; -1 appends.
sync_timestampOptional 'float | None' Epoch seconds when the mutation occurred.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "OTIO_SESSION_1.0",
    "command": {
      "event": "INSERT_CHILD",
      "payload": {
        "parent_uuid": "track-guid-001",
        "index": -1,
        "child_data": {
          "OTIO_SCHEMA": "Clip.1",
          "name": "New Clip"
        },
        "sync_timestamp": 1738339200.0
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import InsertChild

msg = InsertChild(parent_uuid='track-guid-001', index=-1, child_data={'OTIO_SCHEMA': 'Clip.1', 'name': 'New Clip'}, sync_timestamp=1738339200.0)

MoveChild

OTIO_SESSION_1.0event: MOVE_CHILD
Moves a child to a new index within its parent container.

Parameters

ParameterTypeDescription
parent_uuidOptional str GUID of the parent container.
child_uuidOptional str GUID of the child to move.
to_indexOptional int Target position in the parent.
sync_timestampOptional 'float | None' Epoch seconds when the mutation occurred.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "OTIO_SESSION_1.0",
    "command": {
      "event": "MOVE_CHILD",
      "payload": {
        "parent_uuid": "track-guid-001",
        "child_uuid": "clip-guid-001",
        "to_index": 2,
        "sync_timestamp": 1738339200.0
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import MoveChild

msg = MoveChild(parent_uuid='track-guid-001', child_uuid='clip-guid-001', to_index=2, sync_timestamp=1738339200.0)

RemoveChild

OTIO_SESSION_1.0event: REMOVE_CHILD
Removes a child from its parent container.

Parameters

ParameterTypeDescription
parent_uuidOptional str GUID of the parent container.
child_uuidOptional str GUID of the child to remove.
sync_timestampOptional 'float | None' Epoch seconds when the mutation occurred.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "OTIO_SESSION_1.0",
    "command": {
      "event": "REMOVE_CHILD",
      "payload": {
        "parent_uuid": "track-guid-001",
        "child_uuid": "clip-guid-001",
        "sync_timestamp": 1738339200.0
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import RemoveChild

msg = RemoveChild(parent_uuid='track-guid-001', child_uuid='clip-guid-001', sync_timestamp=1738339200.0)

ReplaceAnnotationCommands

OTIO_SESSION_1.0event: REPLACE_ANNOTATION_COMMANDS
Replaces the full annotation-command list on an annotation clip. Used when modifying/updating existing committed annotations in-place (e.g., editing text/captions or dragging/moving them), rather than appending a delta or committing a new stroke.

Parameters

ParameterTypeDescription
annotation_clip_guidOptional str GUID of the annotation clip to update.
commandsOptional list Full replacement list of OTIO SyncEvents (objects on send, wire dicts on receive).
sync_timestampOptional 'float | None' Epoch seconds when the mutation occurred.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "OTIO_SESSION_1.0",
    "command": {
      "event": "REPLACE_ANNOTATION_COMMANDS",
      "payload": {
        "annotation_clip_guid": "ann-clip-001",
        "commands": [
          {
            "OTIO_SCHEMA": "TextAnnotation.1",
            "text": "Fix lighting"
          }
        ],
        "sync_timestamp": 1738339200.0
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import ReplaceAnnotationCommands

msg = ReplaceAnnotationCommands(annotation_clip_guid='ann-clip-001', commands=[{'OTIO_SCHEMA': 'TextAnnotation.1', 'text': 'Fix lighting'}], sync_timestamp=1738339200.0)

SetProperty

OTIO_SESSION_1.0event: SET_PROPERTY
Sets a property or metadata path on an object.

Parameters

ParameterTypeDescription
target_uuidOptional str GUID of the target object.
pathOptional str Property name or 'metadata/...' sub-path.
valueOptional Any New primitive value.
sync_timestampOptional 'float | None' Epoch seconds when the mutation occurred.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "OTIO_SESSION_1.0",
    "command": {
      "event": "SET_PROPERTY",
      "payload": {
        "target_uuid": "obj-guid-001",
        "path": "name",
        "value": "Renamed Clip",
        "sync_timestamp": 1738339200.0
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import SetProperty

msg = SetProperty(target_uuid='obj-guid-001', path='name', value='Renamed Clip', sync_timestamp=1738339200.0)

ClaimOwnership

BROADCAST_OWNERSHIP_1.0event: CLAIM_OWNERSHIP
A peer's claim to the write lease for one broadcast-ownership channel. Sent both when a peer claims a free channel and when it re-claims a channel it already holds (refreshing the lease). ``claim_ts`` is a wall clock reading, not a local monotonic one — it is compared against another peer's ``claim_ts`` for the deterministic tiebreak (earlier wins, lower ``peer_guid`` breaks an exact tie), which only works if every peer evaluates the same two values (design.md D2). Every peer, including the claimant itself, resolves this message through the same rule.

Parameters

ParameterTypeDescription
categoryOptional str Lease channel being claimed: "position", "display", or "structure".
peer_guidOptional str GUID of the claiming peer.
claim_tsOptional 'float | None' Wall-clock epoch seconds when the claim was made; drives the deterministic tiebreak between simultaneous claims.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "BROADCAST_OWNERSHIP_1.0",
    "command": {
      "event": "CLAIM_OWNERSHIP",
      "payload": {
        "category": "position",
        "peer_guid": "peer-abc123",
        "claim_ts": 1738339200.0
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import ClaimOwnership

msg = ClaimOwnership(category='position', peer_guid='peer-abc123', claim_ts=1738339200.0)

ReleaseOwnership

BROADCAST_OWNERSHIP_1.0event: RELEASE_OWNERSHIP
A peer's explicit release of a broadcast-ownership channel it holds. Frees the channel (or promotes a pending claimant) immediately rather than waiting for the lease to expire. Best-effort: a peer that disconnects without sending this is still handled — the lease simply expires through the ordinary silence-based path.

Parameters

ParameterTypeDescription
categoryOptional str Lease channel being released: "position", "display", or "structure".
peer_guidOptional str GUID of the releasing peer.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "BROADCAST_OWNERSHIP_1.0",
    "command": {
      "event": "RELEASE_OWNERSHIP",
      "payload": {
        "category": "position",
        "peer_guid": "peer-abc123"
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import ReleaseOwnership

msg = ReleaseOwnership(category='position', peer_guid='peer-abc123')

IAmMaster

LiveSession.1event: I_AM_MASTER
Master's response to discovery, announcing itself as session master.

Parameters

ParameterTypeDescription
master_guidOptional str GUID of the peer that is the session master.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "LiveSession.1",
    "command": {
      "event": "I_AM_MASTER",
      "payload": {
        "master_guid": "peer-master-001"
      }
    }
  },
  "schema": "SYNC_REVIEW_1.0"
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import IAmMaster

msg = IAmMaster(master_guid='peer-master-001')

NewParticipant

LiveSession.1event: NEW_PARTICIPANT
Announces that a new participant has joined the sync review.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "LiveSession.1",
    "command": {
      "event": "NEW_PARTICIPANT",
      "payload": {
        "timestamp": "2025-01-31T16: 14: 05Z"
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import NewParticipant

msg = NewParticipant(timestamp='2025-01-31T16:14:05Z')

NewPresenter

LiveSession.1event: NEW_PRESENTER
Announces that a peer has become the session presenter.

Parameters

ParameterTypeDescription
presenter_hashOptional str Hash identifying the new presenter.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "LiveSession.1",
    "command": {
      "event": "NEW_PRESENTER",
      "payload": {
        "presenter_hash": "d3447b5cb61b41de73a2de39c4f06ab790e66e4cad81f7d449c0147a546244b5",
        "timestamp": "2025-01-31T16: 14: 00Z"
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import NewPresenter

msg = NewPresenter(presenter_hash='d3447b5cb61b41de73a2de39c4f06ab790e66e4cad81f7d449c0147a546244b5', timestamp='2025-01-31T16:14:00Z')

SetPeerRole

LiveSession.1event: SET_PEER_ROLE
A driver's grant of a session role to a named participant (``session-role-administration``). Two properties are not inferable from the field list alone: **Broadcast, not addressed to its target alone.** Every peer — issuer, target, and everyone else — merges ``{user: role}`` into its own copy of the session's identity-keyed role memory. This is what makes the grant reach the master's memory (and so every later joiner's :class:`StateSnapshot`) without a routing hop, and what makes it survive the target's reconnection: a unicast to the target alone would leave the master's memory unaware the grant ever happened. **Applied by its target, which then re-announces.** A receiving peer does **not** write ``role`` into its peer-table entry for ``user`` on receipt of this message. Only the peer whose own identity matches ``user`` adopts the role, for itself, and re-announces it — :class:`PeerAnnounce` remains the single write path into every peer's table. Every other peer learns the new role from that subsequent announcement, the same way it learns a role on joining.

Parameters

ParameterTypeDescription
userOptional str Identity key of the participant being granted a role — the same `identity["user"]` value the session's role memory is keyed on, never a peer GUID.
roleOptional str The role being granted: `driver`, `reviewer`, or `viewer`.
issuer_guidOptional 'str | None' GUID of the peer that issued the grant, carried for logging and provenance only. Not consulted by any receiving peer: who may issue a grant is checked by the issuer, before sending, against the same role table the broadcast guard uses.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "LiveSession.1",
    "command": {
      "event": "SET_PEER_ROLE",
      "payload": {
        "user": "alice",
        "role": "reviewer",
        "issuer_guid": "peer-abc123"
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import SetPeerRole

msg = SetPeerRole(user='alice', role='reviewer', issuer_guid='peer-abc123')

SharedKeyRequest

LiveSession.1event: SHARED_KEY_REQUEST
Requests the session's shared key from a peer.

Parameters

ParameterTypeDescription
keyOptional str The shared key being requested.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "LiveSession.1",
    "command": {
      "event": "SHARED_KEY_REQUEST",
      "payload": {
        "key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...\n-----END PUBLIC KEY-----",
        "timestamp": "2025-01-31T16: 14: 00Z"
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import SharedKeyRequest

msg = SharedKeyRequest(key='-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...\n-----END PUBLIC KEY-----', timestamp='2025-01-31T16:14:00Z')

SharedKeyResponse

LiveSession.1event: SHARED_KEY_RESPONSE
Responds to a shared-key request with the session's shared key.

Parameters

ParameterTypeDescription
keyOptional str The shared key being returned.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "LiveSession.1",
    "command": {
      "event": "SHARED_KEY_RESPONSE",
      "payload": {
        "key": "encrypted_shared_key_base64_encoded_string",
        "timestamp": "2025-01-31T16: 14: 01Z"
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import SharedKeyResponse

msg = SharedKeyResponse(key='encrypted_shared_key_base64_encoded_string', timestamp='2025-01-31T16:14:01Z')

StateRequest

LiveSession.1event: STATE_REQUEST
Joiner's request to the master for a full state snapshot.

Parameters

ParameterTypeDescription
target_guidOptional str GUID of the master the request is aimed at.
requester_guidOptional str GUID of the joining peer.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "LiveSession.1",
    "command": {
      "event": "STATE_REQUEST",
      "payload": {
        "target_guid": "peer-master-001",
        "requester_guid": "peer-abc123"
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import StateRequest

msg = StateRequest(target_guid='peer-master-001', requester_guid='peer-abc123')

StateSnapshot

LiveSession.1event: STATE_SNAPSHOT
Master's full session snapshot sent in response to a state request.

Parameters

ParameterTypeDescription
target_guidOptional str GUID of the joining peer this snapshot is for.
timelinesOptional dict Map of timeline GUID to OTIO timeline (objects on send, wire dicts on receive).
active_timeline_guidOptional 'str | None' GUID of the active timeline at snapshot time.
snapshot_timestampOptional 'float | None' Epoch seconds when the snapshot was taken.
playback_stateOptional 'dict | None' Optional current playback state to seed the joiner.
display_stateOptional 'dict | None' Optional current display state to seed the joiner.
host_guidOptional 'str | None' GUID of the session host (visibility authority) at snapshot time, so a joiner does not assume it is host and fight the real one.
peersOptional dict Peers present at snapshot time, as {guid: {app, capabilities, role, identity}}, so a joiner learns the peer set without every peer answering its announcement. Not the only discovery path: a joiner that receives no snapshot learns peers from their periodic announcements. Carries no liveness stamp — that is the receiver's own clock. `identity` is the same optional section PEER_ANNOUNCE carries, on the same terms — self-declared and **unverified** — and is present here so a peer that has gone quiet can still be named by a joiner that has never heard it announce. `role` is carried for the same reason and on the same terms as it is on PEER_ANNOUNCE — host eligibility is evaluated against this table, and a peer that has gone quiet is known to a joiner only through this roster, so a role omitted here would make that peer look role-less until its next heartbeat. An absent role means the session's default role.
session_rolesOptional 'dict | None' Session role policy at snapshot time, as {"default_role": str, "peer_roles": {user: role}}. `default_role` is what a participant the session does not recognise is given; `peer_roles` is the session's memory of who has held a role, keyed on the identity's `user` rather than on peer GUID — a driver who drops and rejoins has a new GUID, which is the only case that memory exists for. **Omitted when the session declares no policy**, exactly as a free channel is omitted from `broadcast_ownership` and an unset host is omitted from `host_guid`: an absent section means "no declared policy", not an empty one, so a peer predating this field cannot clear a session's policy by relaying state. Policy lives for the session only and is not persisted anywhere.
broadcast_ownershipOptional 'dict | None' Per-channel write-lease state at snapshot time, as {"position"|"display"|"structure": {"owner_guid": str, "remaining_ms": float}}. A channel with no live owner is omitted, not sent with a null owner, so a peer predating this field — or a snapshot taken while every channel happened to be free — cannot be read as clearing a lease another peer already holds.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "LiveSession.1",
    "command": {
      "event": "STATE_SNAPSHOT",
      "payload": {
        "target_guid": "peer-abc123",
        "timelines": {
          "tl-guid-1": {
            "OTIO_SCHEMA": "Timeline.1",
            "name": "Sequence 1"
          }
        },
        "active_timeline_guid": "tl-guid-1",
        "snapshot_timestamp": 1738339200.0,
        "broadcast_ownership": {
          "position": {
            "owner_guid": "peer-abc123",
            "remaining_ms": 850.0
          }
        }
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import StateSnapshot

msg = StateSnapshot(target_guid='peer-abc123', timelines={'tl-guid-1': {'OTIO_SCHEMA': 'Timeline.1', 'name': 'Sequence 1'}}, active_timeline_guid='tl-guid-1', snapshot_timestamp=1738339200.0, broadcast_ownership={'position': {'owner_guid': 'peer-abc123', 'remaining_ms': 850.0}})

WhoIsMaster

LiveSession.1event: WHO_IS_MASTER
Master-discovery broadcast asking any existing master to identify itself.

Parameters

ParameterTypeDescription
requester_guidOptional str GUID of the peer asking who the master is.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "LiveSession.1",
    "command": {
      "event": "WHO_IS_MASTER",
      "payload": {
        "requester_guid": "peer-abc123"
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import WhoIsMaster

msg = WhoIsMaster(requester_guid='peer-abc123')

DisplaySettingsSet

DISPLAY_SETTINGS_1.0event: SET
Display state broadcast (pan/zoom/exposure/channel). Known fields are declared for documentation; additional producer fields are preserved in ``extras``.

Parameters

ParameterTypeDescription
panOptional 'list | None' Normalised [x, y] pan offset.
zoomOptional 'float | None' Zoom multiplier (1.0 = none).
exposureOptional 'float | None' Exposure adjustment in stops (0.0 = none).
channelOptional 'str | None' Active channel: "RGBA", "R", "G", "B", or "A".
sync_timestampOptional 'float | None' Epoch seconds when the message was sent.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "DISPLAY_SETTINGS_1.0",
    "command": {
      "event": "SET",
      "payload": {
        "pan": [
          0.0,
          0.0
        ],
        "zoom": 1.0,
        "exposure": 0.5,
        "channel": "RGBA",
        "sync_timestamp": 1738339200.0
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import DisplaySettingsSet

msg = DisplaySettingsSet(pan=[0.0, 0.0], zoom=1.0, exposure=0.5, channel='RGBA', sync_timestamp=1738339200.0)

PlaybackSettingsSet

PLAYBACK_SETTINGS_1.0event: SET
Playback state broadcast. Hot path: fires on frame change during playback/scrubbing. Known fields are declared for documentation; any additional producer fields are preserved in ``extras`` and round-tripped unchanged.

Parameters

ParameterTypeDescription
playingOptional 'bool | None' Whether playback is running.
current_timeOptional 'dict | None' Current position as a serialized RationalTime.
playback_modeOptional 'str | None' Playback mode: "play-once", "loop", or "ping-pong".
timeline_guidOptional 'str | None' GUID of the timeline being viewed/played.
view_modeOptional 'str | None' View mode: "sequence" (position authoritative, clip derived from the frame) or "source" (clip_guid authoritative, current_time is the in-clip offset).
clip_guidOptional 'str | None' Active clip sync GUID. Authoritative in source mode; confirmation/highlight only in sequence mode (never seeked to).
sync_timestampOptional 'float | None' Epoch seconds when the message was sent.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "PLAYBACK_SETTINGS_1.0",
    "command": {
      "event": "SET",
      "payload": {
        "playing": true,
        "current_time": {
          "OTIO_SCHEMA": "RationalTime.1",
          "value": 48.0,
          "rate": 24.0
        },
        "looping": false,
        "timeline_guid": "tl-guid-1",
        "sync_timestamp": 1738339200.0
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import PlaybackSettingsSet

msg = PlaybackSettingsSet(playing=True, current_time={'OTIO_SCHEMA': 'RationalTime.1', 'value': 48.0, 'rate': 24.0}, looping=False, timeline_guid='tl-guid-1', sync_timestamp=1738339200.0)

AddTimeline

TIMELINE_1.0event: ADD_TIMELINE
Registers a new timeline (sequence or single-clip) with all peers.

Parameters

ParameterTypeDescription
timeline_guidOptional str GUID of the timeline being added.
timelineOptional Any OTIO timeline (object on send, wire dict on receive).
sync_timestampOptional 'float | None' Epoch seconds when the message was sent.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "TIMELINE_1.0",
    "command": {
      "event": "ADD_TIMELINE",
      "payload": {
        "timeline_guid": "tl-guid-1",
        "timeline": {
          "OTIO_SCHEMA": "Timeline.1",
          "name": "Sequence 1"
        },
        "sync_timestamp": 1738339200.0
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import AddTimeline

msg = AddTimeline(timeline_guid='tl-guid-1', timeline={'OTIO_SCHEMA': 'Timeline.1', 'name': 'Sequence 1'}, sync_timestamp=1738339200.0)

RemoveTimeline

TIMELINE_1.0event: REMOVE_TIMELINE
Removes an existing timeline from all peers. Carries only the GUID — peers already hold the timeline, so no OTIO payload is needed. Receivers that do not hold the GUID treat it as a no-op.

Parameters

ParameterTypeDescription
timeline_guidOptional str GUID of the timeline to remove.
sync_timestampOptional 'float | None' Epoch seconds when the message was sent.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "TIMELINE_1.0",
    "command": {
      "event": "REMOVE_TIMELINE",
      "payload": {
        "timeline_guid": "tl-guid-1",
        "sync_timestamp": 1738339200.0
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import RemoveTimeline

msg = RemoveTimeline(timeline_guid='tl-guid-1', sync_timestamp=1738339200.0)

RenameTimeline

TIMELINE_1.0event: RENAME_TIMELINE
Renames an existing timeline on all peers.

Parameters

ParameterTypeDescription
timeline_guidOptional str GUID of the timeline to rename.
nameOptional str New display name for the timeline.
sync_timestampOptional 'float | None' Epoch seconds when the message was sent.

Example

{
  "session": "default_session",
  "source_guid": "9bf2-4cd6-...-786d",
  "payload": {
    "command_schema": "TIMELINE_1.0",
    "command": {
      "event": "RENAME_TIMELINE",
      "payload": {
        "timeline_guid": "tl-guid-1",
        "name": "Reel 2 \u2014 Director Cut",
        "sync_timestamp": 1738339200.0
      }
    }
  }
}
# Protocol Message (transport layer)
from otio_sync_core.protocol_messages import RenameTimeline

msg = RenameTimeline(timeline_guid='tl-guid-1', name='Reel 2 — Director Cut', sync_timestamp=1738339200.0)

PeerAnnounce

LiveSession.1event: PEER_ANNOUNCE
Peer identity broadcast: who I am and what I can be authoritative for. Feeds the peer table that host election reads. Election is a pure function of that table, so every peer must learn of every other peer — not just of the master. Sent on joining and **periodically thereafter**. The periodic send is what makes silence meaningful: a peer may legitimately go quiet for a whole session, so only the absence of announcements distinguishes one that is idle from one that has died. Peers age out anyone they have not heard from within the liveness timeout. Nobody answers an announcement. Answering used to be how a joiner discovered peers that had long since gone quiet; a joiner now learns them from the roster in :class:`StateSnapshot`, and any it misses from their next periodic announcement. Dropping the answer removes the only step in this protocol whose message count grew with the size of the session.

Parameters

ParameterTypeDescription
peer_guidOptional str GUID of the announcing peer.
appOptional str Application name, e.g. "xstudio" or "openrv". Ranks the peer for host election; an unranked name is still eligible.
capabilitiesOptional list Roles this peer can hold, e.g. ["visibility"].
roleOptional 'str | None' Session role of the announcing peer: `driver`, `reviewer`, or `viewer` — what this participant is permitted to emit at all, which is a different question from who holds the canonical state (master), who chooses what the session looks at (host), or who is broadcasting a category right now (the write leases). Omitted when the peer declares none, and an absent role means **the session's default role**, not the most restrictive one: a peer running code that predates roles must not read as ineligible, or one old peer would make a session with drivers in it look driverless. Self-declared on the same terms as `app`, and enforced by the *sender*: a receiving peer applies a message without checking the sender's role.
identityOptional 'dict | None' Who is on the other end: {user, first_name, last_name, host, source}. Every field is optional and the whole section is omitted when the peer has no identity — a peer without one is a full participant, labelled by app and GUID. Self-declared and **unverified**, on the same terms as `app`: it identifies cooperating participants, it does not authenticate them. `source` records where it came from (`local`, `override`, or a future authenticated provider). The displayed name is derived from these fields by the receiver and is not transmitted.

Example

PeerDepart

LiveSession.1event: PEER_DEPART
Peer's notice that it is leaving the session. Removes the sender from every other peer's peer table, so a role elected from that table — host, in particular — moves off a peer that has gone. Without it the table is append-only and a departed host keeps visibility authority, freezing the session's view with no peer permitted to change it. **Best-effort.** It is sent once, on a path with no delivery guarantee, and a peer that crashes never sends it at all. Correctness therefore does not rest on it: peers also age out anyone they have not heard announce within the liveness timeout, and this message only makes the common case prompt. Do not add a consumer that assumes arrival.

Parameters

ParameterTypeDescription
peer_guidOptional str GUID of the departing peer.

Example

ReplaceTimeline

TIMELINE_1.0event: REPLACE_TIMELINE
Wholesale replacement of a timeline's structure ("brute-force push"). Carries a complete OTIO timeline and replaces the target's structure on each peer in one shot, rather than as incremental child mutations. Used for topology changes (clip insert/remove, large re-edit) on OTIO-origin timelines, where reconstructing the structure via RV's native OTIO reader is cheaper and higher-fidelity than a stream of per-child patches. Distinct from :class:`AddTimeline` (which models a *new* timeline and is a no-op when the GUID already exists): ``REPLACE_TIMELINE`` deliberately overwrites an existing timeline. Each object's ``metadata.sync.guid`` in the pushed timeline is preserved so attribute patches and annotations stay resolvable across the replace. Applying to an unknown GUID creates it.

Parameters

ParameterTypeDescription
timeline_guidOptional str GUID of the timeline to replace (or create).
timelineOptional Any Full OTIO timeline (object on send, wire dict on receive).
sync_timestampOptional 'float | None' Epoch seconds when the message was sent.

Example