Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Sources/objc/include/opentimelineio.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ CxxTimeRange timeline_range_of_child(CxxRetainer* self, CxxRetainer* child, CxxE
NSArray* timeline_audio_tracks(CxxRetainer* self);
NSArray* timeline_video_tracks(CxxRetainer* self);

NSArray* timeline_find_clips(CxxRetainer* self, CxxErrorStruct* cxxErr);

// MARK: - Track
NSString* track_get_kind(CxxRetainer* self);
void track_set_kind(CxxRetainer* self, NSString*);
Expand Down
12 changes: 12 additions & 0 deletions Sources/objc/opentimelineio.mm
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,18 @@ CxxTimeRange timeline_range_of_child(CxxRetainer* self, CxxRetainer* child, CxxE
return array;
}

NSArray* timeline_find_clips(CxxRetainer* self, CxxErrorStruct* cxxErr) {
auto array = [NSMutableArray new];
Comment thread
vade marked this conversation as resolved.
_AutoErrorHandler aeh(cxxErr);
// find_clips() returns pointers to Clips owned by the Timeline's composition
// hierarchy. NSValue wraps them for transport to Swift, where findOrCreate()
// creates properly-retained wrappers. Same pattern as composition_children_in_range().
for (auto t: SO_cast<otio::Timeline>(self)->find_clips(&aeh.error_status)) {
[array addObject: [NSValue valueWithPointer: t]];
}
return array;
}

// MARK: - Track
NSString* track_get_kind(CxxRetainer* self) {
return make_nsstring(SO_cast<otio::Track>(self)->kind());
Expand Down
13 changes: 13 additions & 0 deletions Sources/swift/Timeline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,19 @@ public class Timeline : SerializableObjectWithMetadata {
return try OTIOError.returnOrThrow { TimeRange(timeline_range_of_child(self, child, &$0)) }
}

public func findClips() throws -> [Clip] {
let children_array = try OTIOError.returnOrThrow { timeline_find_clips(self, &$0) }
var result = [Clip]()
for child in children_array {
if let nsptr = child as? NSValue, let cxxPtr = nsptr.pointerValue {
if let clip = SerializableObject.findOrCreate(cxxPtr: cxxPtr) as? Clip {
result.append(clip)
}
}
}
return result
}

override internal init(_ cxxPtr: CxxSerializableObjectPtr) {
super.init(cxxPtr)
}
Expand Down
92 changes: 52 additions & 40 deletions Tests/OpenTimelineIOTests/testTimeline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,72 +10,84 @@ import XCTest
import Foundation

final class testTimeline: XCTestCase {
enum Error: Swift.Error {
case SetupFailed(String)
}

override func setUpWithError() throws {
}

override func tearDownWithError() throws {
}

func testMetadataRead() {
let inputName = "data/timeline.otio"
func testMetadataRead() throws {
let knownDictKey = "foo"
let knownKey = "some_key"
let knownValue = "some_value"

guard let timelineInputPath = Bundle.module.path(forResource: inputName, ofType: "") else {
XCTFail("Missing test data `\(inputName)`")
return
}

do {
let otio = try SerializableObject.fromJSON(filename: timelineInputPath)

guard let timeline = otio as? Timeline else {
XCTFail("Could not create Timeline object from \(timelineInputPath)")
return
}
let timeline = try timeline(from: "data/timeline.otio")
let timelineMetadata = timeline.metadata

let timelineMetadata = timeline.metadata

if let knownMetadata = timelineMetadata[knownDictKey] as? Metadata.Dictionary {
if let value = knownMetadata[knownKey] as? String {
XCTAssertTrue(value == knownValue)
} else {
XCTFail("Expects (\(knownKey), \(knownValue)), but found none in \(knownMetadata)")
}
if let knownMetadata = timelineMetadata[knownDictKey] as? Metadata.Dictionary {
if let value = knownMetadata[knownKey] as? String {
XCTAssertTrue(value == knownValue)
} else {
XCTFail("Cannot read timeline metadata \(String(describing: timelineMetadata[knownDictKey])) as `Metadata.Dictionary`")
XCTFail("Expects (\(knownKey), \(knownValue)), but found none in \(knownMetadata)")
}
} catch let error {
XCTFail("Cannot read OTIO file `\(timelineInputPath)`: \(error)")
} else {
XCTFail("Cannot read timeline metadata \(String(describing: timelineMetadata[knownDictKey])) as `Metadata.Dictionary`")
}
}

func testTimelineClipAvailableBounds() {
let inputName = "data/clip_example.otio"
func testTimelineClipAvailableBounds() throws {
let timeline = try timeline(from: "data/clip_example.otio")

guard let timelineInputPath = Bundle.module.path(forResource: inputName, ofType: "") else {
XCTFail("Missing test data `\(inputName)`")
return
if let firstClip = timeline.videoTracks.first!.children[1] as? Clip,
let mediaReference = firstClip.mediaReference,
let availableBounds = mediaReference.availableImageBounds
{
XCTAssertEqual(availableBounds, CGRect(origin: .zero, size: CGSize(width: 16, height: 9)))
}
}

func testTimelineFindClips() throws {
// SETUP
let timeline = try timeline(from: "data/nested_example.otio")

// EXERCISE
let clips = try timeline.findClips()

// VERIFY
XCTAssertEqual(
clips.map(\.name),
[
"Normal Clip 1",
"Clip Inside A Stack 1",
"Normal Clip 2",
"Clip Inside A Stack 2",
"Normal Clip 3",
"Clip Inside A Track",
"Normal Clip 4"
]
)
}

func timeline(from inputFilePath: String) throws -> Timeline {
guard let timelineInputPath = Bundle.module.path(forResource: inputFilePath, ofType: "") else {
throw Error.SetupFailed("Missing test data `\(inputFilePath)`")
}

do {
let otio = try SerializableObject.fromJSON(filename: timelineInputPath)

guard let timeline = otio as? Timeline else {
XCTFail("Could not create Timeline object from \(timelineInputPath)")
return
throw Error.SetupFailed("Could not create Timeline object from \(timelineInputPath)")
}

if let firstClip = timeline.videoTracks.first!.children[1] as? Clip,
let mediaReference = firstClip.mediaReference,
let availableBounds = mediaReference.availableImageBounds
{
XCTAssertEqual(availableBounds, CGRect(origin: .zero, size: CGSize(width: 16, height: 9)))
}
return timeline

} catch let error {
XCTFail("Cannot read OTIO file `\(timelineInputPath)`: \(error)")
throw Error.SetupFailed("Cannot read OTIO file `\(timelineInputPath)`: \(error)")
}
}

Expand Down
Loading