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:
- The transport protocol — typed protocol messages that are wrapped in a small envelope and fanned out to every peer over RabbitMQ.
- 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_coreAPI — the core sync library (manager, protocol messages, network backends, proxy, patcher, colour, and annotation codec).SyncEvent/ OTIO add-ons API — the custom OpenTimelineIO schemas (SyncEventand 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 role —
driver, 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 aProtocolMessageclass.payload.command.payload— the result ofmsg.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:
- JSON-encodes it:
json.dumps(envelope).encode("utf-8"). - 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.1Parameters
| Parameter | Type | Description |
|---|---|---|
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.1Parameters
| Parameter | Type | Description |
|---|---|---|
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.1Parameters
| Parameter | Type | Description |
|---|---|---|
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.1Parameters
| Parameter | Type | Description |
|---|---|---|
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.1Parameters
| Parameter | Type | Description |
|---|---|---|
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.1Parameters
| Parameter | Type | Description |
|---|---|---|
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.1Parameters
| Parameter | Type | Description |
|---|---|---|
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.1Parameters
| Parameter | Type | Description |
|---|---|---|
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.1Parameters
| Parameter | Type | Description |
|---|---|---|
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.1Parameters
| Parameter | Type | Description |
|---|---|---|
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: PARTIALParameters
| Parameter | Type | Description |
|---|---|---|
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_CHILDParameters
| Parameter | Type | Description |
|---|---|---|
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_CHILDParameters
| Parameter | Type | Description |
|---|---|---|
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_CHILDParameters
| Parameter | Type | Description |
|---|---|---|
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_COMMANDSParameters
| Parameter | Type | Description |
|---|---|---|
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_PROPERTYParameters
| Parameter | Type | Description |
|---|---|---|
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_OWNERSHIPParameters
| Parameter | Type | Description |
|---|---|---|
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_OWNERSHIPParameters
| Parameter | Type | Description |
|---|---|---|
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_MASTERParameters
| Parameter | Type | Description |
|---|---|---|
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_PARTICIPANTExample
{
"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_PRESENTERParameters
| Parameter | Type | Description |
|---|---|---|
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_ROLEParameters
| Parameter | Type | Description |
|---|---|---|
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')
StateRequest
LiveSession.1event: STATE_REQUESTParameters
| Parameter | Type | Description |
|---|---|---|
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_SNAPSHOTParameters
| Parameter | Type | Description |
|---|---|---|
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_MASTERParameters
| Parameter | Type | Description |
|---|---|---|
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: SETParameters
| Parameter | Type | Description |
|---|---|---|
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: SETParameters
| Parameter | Type | Description |
|---|---|---|
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_TIMELINEParameters
| Parameter | Type | Description |
|---|---|---|
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_TIMELINEParameters
| Parameter | Type | Description |
|---|---|---|
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_TIMELINEParameters
| Parameter | Type | Description |
|---|---|---|
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_ANNOUNCEParameters
| Parameter | Type | Description |
|---|---|---|
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_DEPARTParameters
| Parameter | Type | Description |
|---|---|---|
peer_guidOptional |
str | GUID of the departing peer. |
Example
ReplaceTimeline
TIMELINE_1.0event: REPLACE_TIMELINEParameters
| Parameter | Type | Description |
|---|---|---|
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. |