Skip to content

Commit b0856e8

Browse files
authored
Feature/available image bounds (#60)
Signed-off-by: vade <[email protected]>
1 parent 68779b9 commit b0856e8

File tree

5 files changed

+223
-1
lines changed

5 files changed

+223
-1
lines changed

Sources/objc/include/opentimelineio.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,11 @@ bool media_reference_is_missing_reference(CxxRetainer* self);
183183
bool media_reference_available_range(CxxRetainer* self, CxxTimeRange*);
184184
void media_reference_set_available_range(CxxRetainer* self, CxxTimeRange);
185185
void media_reference_clear_available_range(CxxRetainer* self);
186-
186+
187+
bool media_reference_available_image_bounds(CxxRetainer* self, CGRect* );
188+
void media_reference_set_available_image_bounds(CxxRetainer* self, CGRect image_bounds);
189+
void media_reference_clear_available_image_bounds(CxxRetainer* self);
190+
187191
// MARK: - Timeline
188192
void* timeline_get_tracks(CxxRetainer* self);
189193
void timeline_set_tracks(CxxRetainer* self, CxxRetainer* stack);

Sources/objc/opentimelineio.mm

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,39 @@ void media_reference_clear_available_range(CxxRetainer* self) {
631631
SO_cast<otio::MediaReference>(self)->set_available_range(std::nullopt);
632632
}
633633

634+
635+
// If true, value of passed in rect is set. If false, there was no media reference bounds
636+
bool media_reference_available_image_bounds(CxxRetainer* self, CGRect* rect) {
637+
std::optional<IMATH_NAMESPACE::Box2d> iBox2D = SO_cast<otio::MediaReference>(self)->available_image_bounds();
638+
639+
if (iBox2D) {
640+
rect->origin.x = iBox2D->min.x;
641+
rect->origin.y = iBox2D->min.y;
642+
rect->size.width = iBox2D->max.x - iBox2D->min.x;
643+
rect->size.height = iBox2D->max.y - iBox2D->min.y;
644+
645+
return true;
646+
}
647+
648+
return false;
649+
}
650+
651+
void media_reference_set_available_image_bounds(CxxRetainer* self, CGRect image_bounds) {
652+
std::optional<IMATH_NAMESPACE::Box2d> iBox2D = std::optional<IMATH_NAMESPACE::Box2d>();
653+
654+
iBox2D->min.x = image_bounds.origin.x;
655+
iBox2D->min.y = image_bounds.origin.y;
656+
iBox2D->max.x = image_bounds.size.width + image_bounds.origin.x;
657+
iBox2D->max.y = image_bounds.size.height + image_bounds.origin.y;
658+
659+
SO_cast<otio::MediaReference>(self)->set_available_image_bounds(iBox2D);
660+
}
661+
662+
void media_reference_clear_available_image_bounds(CxxRetainer* self) {
663+
SO_cast<otio::MediaReference>(self)->set_available_image_bounds(std::nullopt);
664+
}
665+
666+
634667
// MARK: - Timeline
635668

636669
void* timeline_get_tracks(CxxRetainer* self) {

Sources/swift/MediaReference.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,21 @@ public class MediaReference : SerializableObjectWithMetadata {
4949
public var isMissingReference: Bool {
5050
get { return media_reference_is_missing_reference(self) }
5151
}
52+
53+
public var availableImageBounds: CGRect?
54+
{
55+
get {
56+
var rect = CGRect(origin: CGPoint.init(x: 0, y: 0), size: CGSize(width: 0, height: 0))
57+
return media_reference_available_image_bounds(self, &rect) ? rect : nil
58+
}
59+
set {
60+
if let newValue {
61+
media_reference_set_available_image_bounds(self, newValue)
62+
}
63+
else {
64+
media_reference_clear_available_image_bounds(self)
65+
}
66+
}
67+
68+
}
5269
}

Tests/OpenTimelineIOTests/testTimeline.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,33 @@ final class testTimeline: XCTestCase {
5050
XCTFail("Cannot read OTIO file `\(timelineInputPath)`: \(error)")
5151
}
5252
}
53+
54+
func testTimelineClipAvailableBounds() {
55+
let inputName = "data/clip_example.otio"
56+
57+
guard let timelineInputPath = Bundle.module.path(forResource: inputName, ofType: "") else {
58+
XCTFail("Missing test data `\(inputName)`")
59+
return
60+
}
61+
62+
do {
63+
let otio = try SerializableObject.fromJSON(filename: timelineInputPath)
64+
65+
guard let timeline = otio as? Timeline else {
66+
XCTFail("Could not create Timeline object from \(timelineInputPath)")
67+
return
68+
}
69+
70+
if let firstClip = timeline.videoTracks.first!.children[1] as? Clip,
71+
let mediaReference = firstClip.mediaReference,
72+
let availableBounds = mediaReference.availableImageBounds
73+
{
74+
XCTAssertEqual(availableBounds, CGRect(origin: .zero, size: CGSize(width: 16, height: 9)))
75+
}
76+
77+
} catch let error {
78+
XCTFail("Cannot read OTIO file `\(timelineInputPath)`: \(error)")
79+
}
80+
}
81+
5382
}

Tests/data/clip_example.otio

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
{
2+
"OTIO_SCHEMA": "Timeline.1",
3+
"metadata": {},
4+
"name": "transition_test",
5+
"tracks": {
6+
"OTIO_SCHEMA": "Stack.1",
7+
"children": [
8+
{
9+
"OTIO_SCHEMA": "Track.1",
10+
"children": [
11+
{
12+
"OTIO_SCHEMA": "Gap.1",
13+
"effects": [],
14+
"markers": [],
15+
"enabled": true,
16+
"metadata": {},
17+
"name": "Gap A",
18+
"source_range": {
19+
"OTIO_SCHEMA": "TimeRange.1",
20+
"duration": {
21+
"OTIO_SCHEMA": "RationalTime.1",
22+
"rate": 24,
23+
"value": 8
24+
},
25+
"start_time": {
26+
"OTIO_SCHEMA": "RationalTime.1",
27+
"rate": 24,
28+
"value": 0
29+
}
30+
}
31+
},
32+
{
33+
"OTIO_SCHEMA": "Clip.1",
34+
"effects": [],
35+
"markers": [],
36+
"enabled": true,
37+
"media_reference": {
38+
"OTIO_SCHEMA": "MissingReference.1",
39+
"available_range": {
40+
"OTIO_SCHEMA": "TimeRange.1",
41+
"duration": {
42+
"OTIO_SCHEMA": "RationalTime.1",
43+
"rate": 24,
44+
"value": 8
45+
},
46+
"start_time": {
47+
"OTIO_SCHEMA": "RationalTime.1",
48+
"rate": 24,
49+
"value": 0
50+
}
51+
},
52+
"metadata": {},
53+
"available_image_bounds": {
54+
"OTIO_SCHEMA": "Box2d.1",
55+
"min": {
56+
"OTIO_SCHEMA":"V2d.1",
57+
"x": 0.0,
58+
"y": 0.0
59+
},
60+
"max": {
61+
"OTIO_SCHEMA":"V2d.1",
62+
"x": 16.0,
63+
"y": 9.0
64+
}
65+
},
66+
"name": null
67+
},
68+
"metadata": {},
69+
"name": "Clip-001",
70+
"source_range": {
71+
"OTIO_SCHEMA": "TimeRange.1",
72+
"duration": {
73+
"OTIO_SCHEMA": "RationalTime.1",
74+
"rate": 24,
75+
"value": 3
76+
},
77+
"start_time": {
78+
"OTIO_SCHEMA": "RationalTime.1",
79+
"rate": 24,
80+
"value": 3
81+
}
82+
}
83+
},
84+
{
85+
"OTIO_SCHEMA": "Transition.1",
86+
"metadata": {},
87+
"name": "Dissolve",
88+
"transition_type": "SMPTE_Dissolve",
89+
"parameters": {},
90+
"in_offset": {
91+
"OTIO_SCHEMA": "RationalTime.1",
92+
"rate": 24,
93+
"value": 2
94+
},
95+
"out_offset": {
96+
"OTIO_SCHEMA": "RationalTime.1",
97+
"rate": 24,
98+
"value": 1
99+
}
100+
},
101+
{
102+
"OTIO_SCHEMA": "Gap.1",
103+
"effects": [],
104+
"markers": [],
105+
"enabled": true,
106+
"metadata": {},
107+
"name": "Gap B",
108+
"source_range": {
109+
"OTIO_SCHEMA": "TimeRange.1",
110+
"duration": {
111+
"OTIO_SCHEMA": "RationalTime.1",
112+
"rate": 24,
113+
"value": 8
114+
},
115+
"start_time": {
116+
"OTIO_SCHEMA": "RationalTime.1",
117+
"rate": 24,
118+
"value": 0
119+
}
120+
}
121+
}
122+
],
123+
"effects": [],
124+
"kind": "Video",
125+
"markers": [],
126+
"enabled": true,
127+
"metadata": {},
128+
"name": "Track-001",
129+
"source_range": null
130+
}
131+
],
132+
"effects": [],
133+
"markers": [],
134+
"enabled": true,
135+
"metadata": {},
136+
"name": "tracks",
137+
"source_range": null
138+
}
139+
}

0 commit comments

Comments
 (0)