Skip to content

Commit 6a92cb2

Browse files
authored
Merge branch 'OpenTimelineIO:main' into metadata
2 parents 96f0104 + f357b4d commit 6a92cb2

5 files changed

Lines changed: 382 additions & 40 deletions

File tree

Sources/objc/include/opentimelineio.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ CxxTimeRange timeline_range_of_child(CxxRetainer* self, CxxRetainer* child, CxxE
209209
NSArray* timeline_audio_tracks(CxxRetainer* self);
210210
NSArray* timeline_video_tracks(CxxRetainer* self);
211211

212+
NSArray* timeline_find_clips(CxxRetainer* self, CxxErrorStruct* cxxErr);
213+
212214
// MARK: - Track
213215
NSString* track_get_kind(CxxRetainer* self);
214216
void track_set_kind(CxxRetainer* self, NSString*);

Sources/objc/opentimelineio.mm

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,18 @@ CxxTimeRange timeline_range_of_child(CxxRetainer* self, CxxRetainer* child, CxxE
774774
return array;
775775
}
776776

777+
NSArray* timeline_find_clips(CxxRetainer* self, CxxErrorStruct* cxxErr) {
778+
auto array = [NSMutableArray new];
779+
_AutoErrorHandler aeh(cxxErr);
780+
// find_clips() returns pointers to Clips owned by the Timeline's composition
781+
// hierarchy. NSValue wraps them for transport to Swift, where findOrCreate()
782+
// creates properly-retained wrappers. Same pattern as composition_children_in_range().
783+
for (auto t: SO_cast<otio::Timeline>(self)->find_clips(&aeh.error_status)) {
784+
[array addObject: [NSValue valueWithPointer: t]];
785+
}
786+
return array;
787+
}
788+
777789
// MARK: - Track
778790
NSString* track_get_kind(CxxRetainer* self) {
779791
return make_nsstring(SO_cast<otio::Track>(self)->kind());

Sources/swift/Timeline.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,19 @@ public class Timeline : SerializableObjectWithMetadata {
7777
return try OTIOError.returnOrThrow { TimeRange(timeline_range_of_child(self, child, &$0)) }
7878
}
7979

80+
public func findClips() throws -> [Clip] {
81+
let children_array = try OTIOError.returnOrThrow { timeline_find_clips(self, &$0) }
82+
var result = [Clip]()
83+
for child in children_array {
84+
if let nsptr = child as? NSValue, let cxxPtr = nsptr.pointerValue {
85+
if let clip = SerializableObject.findOrCreate(cxxPtr: cxxPtr) as? Clip {
86+
result.append(clip)
87+
}
88+
}
89+
}
90+
return result
91+
}
92+
8093
override internal init(_ cxxPtr: CxxSerializableObjectPtr) {
8194
super.init(cxxPtr)
8295
}

Tests/OpenTimelineIOTests/testTimeline.swift

Lines changed: 52 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,72 +10,84 @@ import XCTest
1010
import Foundation
1111

1212
final class testTimeline: XCTestCase {
13+
enum Error: Swift.Error {
14+
case SetupFailed(String)
15+
}
16+
1317
override func setUpWithError() throws {
1418
}
1519

1620
override func tearDownWithError() throws {
1721
}
1822

19-
func testMetadataRead() {
20-
let inputName = "data/timeline.otio"
23+
func testMetadataRead() throws {
2124
let knownDictKey = "foo"
2225
let knownKey = "some_key"
2326
let knownValue = "some_value"
2427

25-
guard let timelineInputPath = Bundle.module.path(forResource: inputName, ofType: "") else {
26-
XCTFail("Missing test data `\(inputName)`")
27-
return
28-
}
29-
30-
do {
31-
let otio = try SerializableObject.fromJSON(filename: timelineInputPath)
32-
33-
guard let timeline = otio as? Timeline else {
34-
XCTFail("Could not create Timeline object from \(timelineInputPath)")
35-
return
36-
}
28+
let timeline = try timeline(from: "data/timeline.otio")
29+
let timelineMetadata = timeline.metadata
3730

38-
let timelineMetadata = timeline.metadata
39-
40-
if let knownMetadata = timelineMetadata[knownDictKey] as? Metadata.Dictionary {
41-
if let value = knownMetadata[knownKey] as? String {
42-
XCTAssertTrue(value == knownValue)
43-
} else {
44-
XCTFail("Expects (\(knownKey), \(knownValue)), but found none in \(knownMetadata)")
45-
}
31+
if let knownMetadata = timelineMetadata[knownDictKey] as? Metadata.Dictionary {
32+
if let value = knownMetadata[knownKey] as? String {
33+
XCTAssertTrue(value == knownValue)
4634
} else {
47-
XCTFail("Cannot read timeline metadata \(String(describing: timelineMetadata[knownDictKey])) as `Metadata.Dictionary`")
35+
XCTFail("Expects (\(knownKey), \(knownValue)), but found none in \(knownMetadata)")
4836
}
49-
} catch let error {
50-
XCTFail("Cannot read OTIO file `\(timelineInputPath)`: \(error)")
37+
} else {
38+
XCTFail("Cannot read timeline metadata \(String(describing: timelineMetadata[knownDictKey])) as `Metadata.Dictionary`")
5139
}
5240
}
5341

54-
func testTimelineClipAvailableBounds() {
55-
let inputName = "data/clip_example.otio"
42+
func testTimelineClipAvailableBounds() throws {
43+
let timeline = try timeline(from: "data/clip_example.otio")
5644

57-
guard let timelineInputPath = Bundle.module.path(forResource: inputName, ofType: "") else {
58-
XCTFail("Missing test data `\(inputName)`")
59-
return
45+
if let firstClip = timeline.videoTracks.first!.children[1] as? Clip,
46+
let mediaReference = firstClip.mediaReference,
47+
let availableBounds = mediaReference.availableImageBounds
48+
{
49+
XCTAssertEqual(availableBounds, CGRect(origin: .zero, size: CGSize(width: 16, height: 9)))
50+
}
51+
}
52+
53+
func testTimelineFindClips() throws {
54+
// SETUP
55+
let timeline = try timeline(from: "data/nested_example.otio")
56+
57+
// EXERCISE
58+
let clips = try timeline.findClips()
59+
60+
// VERIFY
61+
XCTAssertEqual(
62+
clips.map(\.name),
63+
[
64+
"Normal Clip 1",
65+
"Clip Inside A Stack 1",
66+
"Normal Clip 2",
67+
"Clip Inside A Stack 2",
68+
"Normal Clip 3",
69+
"Clip Inside A Track",
70+
"Normal Clip 4"
71+
]
72+
)
73+
}
74+
75+
func timeline(from inputFilePath: String) throws -> Timeline {
76+
guard let timelineInputPath = Bundle.module.path(forResource: inputFilePath, ofType: "") else {
77+
throw Error.SetupFailed("Missing test data `\(inputFilePath)`")
6078
}
6179

6280
do {
6381
let otio = try SerializableObject.fromJSON(filename: timelineInputPath)
64-
82+
6583
guard let timeline = otio as? Timeline else {
66-
XCTFail("Could not create Timeline object from \(timelineInputPath)")
67-
return
84+
throw Error.SetupFailed("Could not create Timeline object from \(timelineInputPath)")
6885
}
6986

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-
}
87+
return timeline
7688

7789
} catch let error {
78-
XCTFail("Cannot read OTIO file `\(timelineInputPath)`: \(error)")
90+
throw Error.SetupFailed("Cannot read OTIO file `\(timelineInputPath)`: \(error)")
7991
}
8092
}
8193

0 commit comments

Comments
 (0)