Introduction¶
Overview¶
The goal of this format is to facilitate interchange of annotations and notes between different review systems or provide a very simple offline review format for a simple review system.
The ORIAnnotations module provides a set of helper classes to manage media and annotations, primarily using OpenTimelineIO (OTIO) as the interchange format. This allows for easy integration with various production tracking systems without requiring specialized tools.
Requirements¶
The main requirement is on Opentimelineio.
pip install opentimelineio
However, to build the documentation, you also need:
Example Usage¶
1import sys, os
2
3project_root = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")
4
5manifest_paths = [os.path.join(project_root, "otio_event_plugin", "plugin_manifest.json")]
6if os.environ.get("OTIO_PLUGIN_MANIFEST_PATH", "").strip(os.pathsep):
7 manifest_paths.extend(os.environ["OTIO_PLUGIN_MANIFEST_PATH"].strip(os.pathsep).split(os.pathsep))
8os.environ["OTIO_PLUGIN_MANIFEST_PATH"] = os.pathsep.join(manifest_paths)
9sys.path.append(os.path.join(project_root, "python"))
10
11
12
13import opentimelineio as otio
14import ORIAnnotations
15import os
16import dataclasses
17
18
19reviewdata = [
20 {'media': "/Users/sam/git/Annotations/testmedia/chimera_cars_srgb-test_mov-prores_ks.mov",
21 'startframe': 0,
22 'framerate': 25,
23 'duration': 200,
24 'reviewframes': [{'note': "Hello",
25 'frame': 1,
26 'image': '/Users/sam/git/Annotations/test_export/chimera_cars_srgb-test_mov-prores_ks.00001.png'
27 },
28 {'note': "Hello",
29 'frame': 15,
30 'image': '/Users/sam/git/Annotations/test_export/chimera_cars_srgb-test_mov-prores_ks.00015.png',
31 "annotation_commands": """
32 {"OTIO_SCHEMA": "PaintStart.1", "brush": "circle","friendly_name": "defaultSequence_p_sourceGroup000000.pen:13:1:sam","rgba": [1.0,1.0,1.0,1.0],"source_index": 0,"timestamp": "2025-07-06T23:21:48.664116","type": "color","uuid": "6500d2f3-e758-4cd6-9bf2-83c7d712786d","visible": true}
33 {"OTIO_SCHEMA": "PaintPoint.1", "points": {"OTIO_SCHEMA": "PaintVertices.1", "size": [0.00995635986328125, 0.00995635986328125], "x": [-0.47882136702537537, -0.480663001537323],"y": [0.046961307525634766, 0.046961307525634766]}}
34"""
35 },
36 ]
37 },
38 {'media': "/Users/sam/git/Annotations/testmedia/chimera_coaster_srgb-test_mov-dnxhd.mov",
39 'startframe': 0,
40 'framerate': 25,
41 'duration': 200,
42 'reviewframes': [{'note': "Hello", 'frame': 1, 'image': '/Users/sam/git/Annotations/test_export/chimera_coaster_srgb-test_mov-dnxhd.00001.png'},
43 {'note': "Hello", 'frame': 25, 'image': '/Users/sam/git/Annotations/test_export/chimera_coaster_srgb-test_mov-dnxhd.00025.png'},
44 ]
45 },
46 {'media': "/Users/sam/git/Annotations/testmedia/chimera_fountains_srgb-test_mov-dnxhd.mov",
47 'startframe': 0,
48 'framerate': 25,
49 'duration': 200,
50 'reviewframes': [{'note': "Hello", 'frame': 28, 'image': '/Users/sam/git/Annotations/test_export/chimera_fountains_srgb-test_mov-dnxhd.00028.png'},
51 ]
52 }
53]
54
55
56medialist = []
57reviewitems = []
58
59for reviewmedia in reviewdata:
60 media = ORIAnnotations.Media(media_path=reviewmedia['media'],
61 name=os.path.basename(reviewmedia['media']),
62 frame_rate=reviewmedia['framerate'],
63 start_frame=reviewmedia['startframe'],
64 duration=reviewmedia['duration']
65 )
66 medialist.append(media)
67 ri = ORIAnnotations.ReviewItem(media=media)
68 reviewitems.append(ri)
69 frames = []
70 for reviewframe in reviewmedia['reviewframes']:
71 frame = ORIAnnotations.ReviewItemFrame(note=reviewframe['note'], annotation_image=reviewframe['image'], frame=reviewframe['frame'], review_item=ri)
72 if 'annotation_commands' in reviewframe:
73 # For testing we assume this is a jsonlines format string, so we are going to read it in and turn it into OTIO schema objects.
74 lines = reviewframe['annotation_commands'].splitlines()
75 events = []
76 for line in lines:
77 if len(line) == 0:
78 continue
79 print("DECODING:", line)
80 event = otio.adapters.read_from_string(line, adapter_name="otio_json")
81 events.append(event)
82 frame.annotation_commands = events
83 frames.append(frame)
84 ri.review_frames = frames
85
86review = ORIAnnotations.Review(title="Review", review_items=reviewitems)
87print("MediaList:", medialist)
88reviewgroup = ORIAnnotations.ReviewGroup(media=medialist, reviews=[review])
89timeline = reviewgroup.export_otio_timeline()
90print("About to export:")
91outputfile = "tests/test_export_unittest.otio"
92otio.adapters.write_to_file(timeline, outputfile)
93print("Exported to:", outputfile)
94
95
96# Now we read the file back in.
97
98newtimeline = otio.adapters.read_from_file(outputfile)
99rg = ORIAnnotations.ReviewGroup()
100rg.read_otio_timeline(newtimeline)
101alt_test_output_file = "tests/test_export_unittest2.otio"
102otio.adapters.write_to_file(newtimeline, alt_test_output_file)
103print("Exported to:", alt_test_output_file)
You can run this from the command line, once the libraries are installed with:
python test/testannotations.py