Skip to content

Commit 8dba0c2

Browse files
darbyjohnstonMichaelPlug
authored andcommitted
Fix for SerializableCollection::children_if (AcademySoftwareFoundation#1404)
* Fix for children_if * Missing shallow_search parameter fixes * Refactor test_children_if tests * Lint fixes * Add Python tests Signed-off-by: Darby Johnston <[email protected]> Signed-off-by: Michele Spina <[email protected]>
1 parent 2a9aab8 commit 8dba0c2

File tree

14 files changed

+496
-32
lines changed

14 files changed

+496
-32
lines changed

src/opentimelineio/serializableCollection.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "opentimelineio/composition.h"
77
#include "opentimelineio/serializableObjectWithMetadata.h"
8+
#include "opentimelineio/timeline.h"
89
#include "opentimelineio/version.h"
910

1011
namespace opentimelineio { namespace OPENTIMELINEIO_VERSION {
@@ -98,8 +99,8 @@ SerializableCollection::children_if(
9899
out.push_back(valid_child);
99100
}
100101

101-
// if not a shallow_search, for children that are serialiable collections or compositions,
102-
// recurse into their children
102+
// if not a shallow_search, for children that are serializable collections,
103+
// compositions, or timelines, recurse into their children
103104
if (!shallow_search)
104105
{
105106
if (auto collection =
@@ -129,6 +130,19 @@ SerializableCollection::children_if(
129130
out.push_back(valid_child);
130131
}
131132
}
133+
else if (auto timeline = dynamic_cast<Timeline*>(child.value))
134+
{
135+
const auto valid_children =
136+
timeline->children_if<T>(error_status, search_range);
137+
if (is_error(error_status))
138+
{
139+
return out;
140+
}
141+
for (const auto& valid_child: valid_children)
142+
{
143+
out.push_back(valid_child);
144+
}
145+
}
132146
}
133147
}
134148
return out;

src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -289,12 +289,12 @@ A :class:`~SerializableCollection` is useful for serializing multiple timelines,
289289
.def("__iter__", [](SerializableCollection* c) {
290290
return new SerializableCollectionIterator(c);
291291
})
292-
.def("clip_if", [](SerializableCollection* t, optional<TimeRange> const& search_range) {
293-
return clip_if(t, search_range);
294-
}, "search_range"_a = nullopt)
295-
.def("children_if", [](SerializableCollection* t, py::object descended_from_type, optional<TimeRange> const& search_range) {
296-
return children_if(t, descended_from_type, search_range);
297-
}, "descended_from_type"_a = py::none(), "search_range"_a = nullopt);
292+
.def("clip_if", [](SerializableCollection* t, optional<TimeRange> const& search_range, bool shallow_search) {
293+
return clip_if(t, search_range, shallow_search);
294+
}, "search_range"_a = nullopt, "shallow_search"_a = false)
295+
.def("children_if", [](SerializableCollection* t, py::object descended_from_type, optional<TimeRange> const& search_range, bool shallow_search) {
296+
return children_if(t, descended_from_type, search_range, shallow_search);
297+
}, "descended_from_type"_a = py::none(), "search_range"_a = nullopt, "shallow_search"_a = false);
298298

299299
}
300300

@@ -611,9 +611,9 @@ Should be subclassed (for example by :class:`.Track` and :class:`.Stack`), not u
611611
"markers"_a = py::none(),
612612
"effects"_a = py::none(),
613613
py::arg_v("metadata"_a = py::none()))
614-
.def("clip_if", [](Stack* t, optional<TimeRange> const& search_range) {
615-
return clip_if(t, search_range);
616-
}, "search_range"_a = nullopt);
614+
.def("clip_if", [](Stack* t, optional<TimeRange> const& search_range, bool shallow_search) {
615+
return clip_if(t, search_range, shallow_search);
616+
}, "search_range"_a = nullopt, "shallow_search"_a = false);
617617

618618
py::class_<Timeline, SerializableObjectWithMetadata, managing_ptr<Timeline>>(m, "Timeline", py::dynamic_attr())
619619
.def(py::init([](std::string name,
@@ -641,12 +641,12 @@ Should be subclassed (for example by :class:`.Track` and :class:`.Stack`), not u
641641
})
642642
.def("video_tracks", &Timeline::video_tracks)
643643
.def("audio_tracks", &Timeline::audio_tracks)
644-
.def("clip_if", [](Timeline* t, optional<TimeRange> const& search_range) {
645-
return clip_if(t, search_range);
646-
}, "search_range"_a = nullopt)
647-
.def("children_if", [](Timeline* t, py::object descended_from_type, optional<TimeRange> const& search_range) {
648-
return children_if(t, descended_from_type, search_range);
649-
}, "descended_from_type"_a = py::none(), "search_range"_a = nullopt);
644+
.def("clip_if", [](Timeline* t, optional<TimeRange> const& search_range, bool shallow_search) {
645+
return clip_if(t, search_range, shallow_search);
646+
}, "search_range"_a = nullopt, "shallow_search"_a = false)
647+
.def("children_if", [](Timeline* t, py::object descended_from_type, optional<TimeRange> const& search_range, bool shallow_search) {
648+
return children_if(t, descended_from_type, search_range, shallow_search);
649+
}, "descended_from_type"_a = py::none(), "search_range"_a = nullopt, "shallow_search"_a = false);
650650
}
651651

652652
static void define_effects(py::module m) {

src/py-opentimelineio/opentimelineio/schema/serializable_collection.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ def __repr__(self):
3131

3232

3333
@add_method(_otio.SerializableCollection)
34-
def each_child(self, search_range=None, descended_from_type=_otio.Composable):
34+
def each_child(self, search_range=None, descended_from_type=_otio.Composable,
35+
shallow_search=False):
3536
""" Generator that returns each child contained in the serializable
3637
collection in the order in which it is found.
3738
@@ -42,13 +43,15 @@ def each_child(self, search_range=None, descended_from_type=_otio.Composable):
4243
with the search range will be yielded.
4344
:param type descended_from_type: if specified, only children who are a descendent
4445
of the descended_from_type will be yielded.
46+
:param bool shallow_search: if True, will only search children of self and not
47+
recurse into children of children.
4548
"""
46-
for child in self.children_if(descended_from_type, search_range):
49+
for child in self.children_if(descended_from_type, search_range, shallow_search):
4750
yield child
4851

4952

5053
@add_method(_otio.SerializableCollection)
51-
def each_clip(self, search_range=None):
54+
def each_clip(self, search_range=None, shallow_search=False):
5255
""" Generator that returns each clip contained in the serializable
5356
collection in the order in which it is found.
5457
@@ -57,6 +60,8 @@ def each_clip(self, search_range=None):
5760
5861
:param TimeRange search_range: if specified, only children whose range overlaps
5962
with the search range will be yielded.
63+
:param bool shallow_search: if True, will only search children of self and not
64+
recurse into children of children.
6065
"""
61-
for child in self.clip_if(search_range):
66+
for child in self.clip_if(search_range, shallow_search):
6267
yield child

src/py-opentimelineio/opentimelineio/schema/stack.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77

88
@add_method(_otio.Stack)
9-
def each_clip(self, search_range=None):
9+
def each_clip(self, search_range=None, shallow_search=False):
1010
"""Generator that returns each clip contained in the stack
1111
in the order in which it is found.
1212
@@ -15,6 +15,8 @@ def each_clip(self, search_range=None):
1515
1616
:param TimeRange search_range: if specified, only children whose range overlaps
1717
with the search range will be yielded.
18+
:param bool shallow_search: if True, will only search children of self and not
19+
recurse into children of children.
1820
"""
19-
for child in self.clip_if(search_range):
21+
for child in self.clip_if(search_range, shallow_search):
2022
yield child

src/py-opentimelineio/opentimelineio/schema/timeline.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ def __repr__(self):
2121

2222

2323
@add_method(_otio.Timeline)
24-
def each_child(self, search_range=None, descended_from_type=_otio.Composable):
24+
def each_child(self, search_range=None, descended_from_type=_otio.Composable,
25+
shallow_search=False):
2526
"""Generator that returns each child contained in the timeline
2627
in the order in which it is found.
2728
@@ -32,13 +33,15 @@ def each_child(self, search_range=None, descended_from_type=_otio.Composable):
3233
with the search range will be yielded.
3334
:param type descended_from_type: if specified, only children who are a descendent
3435
of the descended_from_type will be yielded.
36+
:param bool shallow_search: if True, will only search children of self and not
37+
recurse into children of children.
3538
"""
36-
for child in self.children_if(descended_from_type, search_range):
39+
for child in self.children_if(descended_from_type, search_range, shallow_search):
3740
yield child
3841

3942

4043
@add_method(_otio.Timeline)
41-
def each_clip(self, search_range=None):
44+
def each_clip(self, search_range=None, shallow_search=False):
4245
"""Generator that returns each clip contained in the timeline
4346
in the order in which it is found.
4447
@@ -47,6 +50,8 @@ def each_clip(self, search_range=None):
4750
4851
:param TimeRange search_range: if specified, only children whose range overlaps
4952
with the search range will be yielded.
53+
:param bool shallow_search: if True, will only search children of self and not
54+
recurse into children of children.
5055
"""
51-
for child in self.clip_if(search_range):
56+
for child in self.clip_if(search_range, shallow_search):
5257
yield child

src/py-opentimelineio/opentimelineio/schema/track.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ def each_clip(self, search_range=None, shallow_search=False):
1414
Use :meth:`clip_if` instead.
1515
1616
:param TimeRange search_range: if specified, only children whose range overlaps with
17-
the search range will be yielded.
18-
:param bool shallow_search: if True, will only search children of self, not
19-
and not recurse into children of children.
17+
the search range will be yielded.
18+
:param bool shallow_search: if True, will only search children of self and not
19+
recurse into children of children.
2020
"""
21-
for child in self.clip_if(search_range):
21+
for child in self.clip_if(search_range, shallow_search):
2222
yield child

tests/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ foreach(test ${tests_opentime})
1515
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
1616
endforeach()
1717

18-
list(APPEND tests_opentimelineio test_clip)
18+
list(APPEND tests_opentimelineio test_clip test_serializableCollection test_timeline test_track)
1919
foreach(test ${tests_opentimelineio})
2020
add_executable(${test} utils.h utils.cpp ${test}.cpp)
2121

tests/test_clip.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <opentimelineio/deserialization.h>
88
#include <opentimelineio/externalReference.h>
99
#include <opentimelineio/missingReference.h>
10+
#include <opentimelineio/serializableCollection.h>
1011
#include <opentimelineio/timeline.h>
1112

1213
#include <iostream>

tests/test_serializableCollection.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright Contributors to the OpenTimelineIO project
3+
4+
#include "utils.h"
5+
6+
#include <opentimelineio/clip.h>
7+
#include <opentimelineio/serializableCollection.h>
8+
#include <opentimelineio/timeline.h>
9+
#include <opentimelineio/track.h>
10+
11+
#include <iostream>
12+
13+
namespace otime = opentime::OPENTIME_VERSION;
14+
namespace otio = opentimelineio::OPENTIMELINEIO_VERSION;
15+
16+
int
17+
main(int argc, char** argv)
18+
{
19+
Tests tests;
20+
21+
tests.add_test(
22+
"test_children_if", [] {
23+
using namespace otio;
24+
otio::SerializableObject::Retainer<otio::Clip> cl =
25+
new otio::Clip();
26+
otio::SerializableObject::Retainer<otio::Track> tr =
27+
new otio::Track();
28+
tr->append_child(cl);
29+
otio::SerializableObject::Retainer<otio::Timeline> tl =
30+
new otio::Timeline();
31+
tl->tracks()->append_child(tr);
32+
otio::SerializableObject::Retainer<otio::SerializableCollection>
33+
sc = new otio::SerializableCollection();
34+
sc->insert_child(0, tl);
35+
opentimelineio::v1_0::ErrorStatus err;
36+
auto result = sc->children_if<otio::Clip>(&err, {}, false);
37+
assertEqual(result.size(), 1);
38+
assertEqual(result[0].value, cl.value);
39+
});
40+
tests.add_test(
41+
"test_children_if_search_range", [] {
42+
using namespace otio;
43+
const TimeRange range(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0));
44+
otio::SerializableObject::Retainer<otio::Clip> cl0 =
45+
new otio::Clip();
46+
cl0->set_source_range(range);
47+
otio::SerializableObject::Retainer<otio::Clip> cl1 =
48+
new otio::Clip();
49+
cl1->set_source_range(range);
50+
otio::SerializableObject::Retainer<otio::Clip> cl2 =
51+
new otio::Clip();
52+
cl2->set_source_range(range);
53+
otio::SerializableObject::Retainer<otio::Track> tr =
54+
new otio::Track();
55+
tr->append_child(cl0);
56+
tr->append_child(cl1);
57+
tr->append_child(cl2);
58+
otio::SerializableObject::Retainer<otio::Timeline> tl =
59+
new otio::Timeline();
60+
tl->tracks()->append_child(tr);
61+
otio::SerializableObject::Retainer<otio::SerializableCollection>
62+
sc = new otio::SerializableCollection();
63+
sc->insert_child(0, tl);
64+
opentimelineio::v1_0::ErrorStatus err;
65+
auto result = sc->children_if<otio::Clip>(&err, range);
66+
assertEqual(result.size(), 1);
67+
assertEqual(result[0].value, cl0.value);
68+
});
69+
tests.add_test(
70+
"test_children_if_shallow_search", [] {
71+
using namespace otio;
72+
otio::SerializableObject::Retainer<otio::Clip> cl =
73+
new otio::Clip();
74+
otio::SerializableObject::Retainer<otio::Track> tr =
75+
new otio::Track();
76+
tr->append_child(cl);
77+
otio::SerializableObject::Retainer<otio::Timeline> tl =
78+
new otio::Timeline();
79+
tl->tracks()->append_child(tr);
80+
otio::SerializableObject::Retainer<otio::SerializableCollection>
81+
sc = new otio::SerializableCollection();
82+
sc->insert_child(0, tl);
83+
opentimelineio::v1_0::ErrorStatus err;
84+
auto result = sc->children_if<otio::Clip>(&err, nullopt, true);
85+
assertEqual(result.size(), 0);
86+
result = sc->children_if<otio::Clip>(&err, nullopt, false);
87+
assertEqual(result.size(), 1);
88+
assertEqual(result[0].value, cl.value);
89+
});
90+
91+
tests.run(argc, argv);
92+
return 0;
93+
}

tests/test_serializable_collection.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,54 @@ def test_repr(self):
7171
")"
7272
)
7373

74+
def test_children_if(self):
75+
cl = otio.schema.Clip()
76+
tr = otio.schema.Track()
77+
tr.append(cl)
78+
tl = otio.schema.Timeline()
79+
tl.tracks.append(tr)
80+
sc = otio.schema.SerializableCollection()
81+
sc.append(tl)
82+
result = sc.children_if(otio.schema.Clip)
83+
self.assertEqual(len(result), 1)
84+
self.assertEqual(result[0], cl)
85+
86+
def test_children_if_search_range(self):
87+
range = otio.opentime.TimeRange(
88+
otio.opentime.RationalTime(0.0, 24.0),
89+
otio.opentime.RationalTime(24.0, 24.0))
90+
cl0 = otio.schema.Clip()
91+
cl0.source_range = range
92+
cl1 = otio.schema.Clip()
93+
cl1.source_range = range
94+
cl2 = otio.schema.Clip()
95+
cl2.source_range = range
96+
tr = otio.schema.Track()
97+
tr.append(cl0)
98+
tr.append(cl1)
99+
tr.append(cl2)
100+
tl = otio.schema.Timeline()
101+
tl.tracks.append(tr)
102+
sc = otio.schema.SerializableCollection()
103+
sc.append(tl)
104+
result = sc.children_if(otio.schema.Clip, range)
105+
self.assertEqual(len(result), 1)
106+
self.assertEqual(result[0], cl0)
107+
108+
def test_children_if_shallow_search(self):
109+
cl = otio.schema.Clip()
110+
tr = otio.schema.Track()
111+
tr.append(cl)
112+
tl = otio.schema.Timeline()
113+
tl.tracks.append(tr)
114+
sc = otio.schema.SerializableCollection()
115+
sc.append(tl)
116+
result = sc.children_if(otio.schema.Clip, shallow_search=True)
117+
self.assertEqual(len(result), 0)
118+
result = sc.children_if(otio.schema.Clip, shallow_search=False)
119+
self.assertEqual(len(result), 1)
120+
self.assertEqual(result[0], cl)
121+
74122

75123
if __name__ == '__main__':
76124
unittest.main()

0 commit comments

Comments
 (0)