Skip to content

Commit c68eb2e

Browse files
markreidvfxrameshm
authored and
rameshm
committed
fixes #1635, fix flatten stack splits contiguous clips issue and add tests
Signed-off-by: Ramesh Maddhu <[email protected]> Signed-off-by: Mark Reid <[email protected]>
1 parent ee3e317 commit c68eb2e

File tree

2 files changed

+180
-31
lines changed

2 files changed

+180
-31
lines changed

src/opentimelineio/stackAlgorithm.cpp

+75-30
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@ namespace opentimelineio { namespace OPENTIMELINEIO_VERSION {
1212
typedef std::map<Track*, std::map<Composable*, TimeRange>> RangeTrackMap;
1313
typedef std::vector<SerializableObject::Retainer<Track>> TrackRetainerVector;
1414

15+
static bool
16+
_is_not_visible(
17+
SerializableObject::Retainer<Composable> thing)
18+
{
19+
auto item = dynamic_retainer_cast<Item>(thing);
20+
if (!item)
21+
return false;
22+
23+
return !item->visible();
24+
}
25+
1526
static void
1627
_flatten_next_item(
1728
RangeTrackMap& range_track_map,
@@ -61,54 +72,88 @@ _flatten_next_item(
6172
}
6273
track_map = &result.first->second;
6374
}
64-
for (auto child: track->children())
75+
76+
auto children = track->children();
77+
for (size_t i = 0; i < children.size(); i++)
6578
{
66-
auto item = dynamic_retainer_cast<Item>(child);
67-
if (!item)
79+
std::optional<TimeRange> trim = std::nullopt;
80+
SerializableObject::Retainer<Composable> child = children[i];
81+
82+
// combine all non visible children into one continuous range
83+
while(track_index > 0 && _is_not_visible(child))
6884
{
69-
if (!dynamic_retainer_cast<Transition>(child))
85+
TimeRange child_trim = (*track_map)[child];
86+
87+
// offset ranges so they are in track range before being trimmed
88+
if (trim_range)
7089
{
71-
if (error_status)
72-
{
73-
*error_status = ErrorStatus(
74-
ErrorStatus::TYPE_MISMATCH,
75-
"expected item of type Item* || Transition*",
76-
child);
77-
}
78-
return;
90+
trim = TimeRange(
91+
child_trim.start_time() + trim_range->start_time(),
92+
child_trim.duration());
93+
(*track_map)[child] = child_trim;
94+
}
95+
96+
if (trim.has_value())
97+
{
98+
trim = trim->duration_extended_by(child_trim.duration());
99+
}
100+
else
101+
{
102+
trim = child_trim;
103+
}
104+
105+
if (i+1 < children.size())
106+
{
107+
child = children[i++];
108+
}
109+
else
110+
{
111+
// last item is not visible
112+
child = nullptr;
113+
break;
79114
}
80115
}
81116

82-
if (!item || item->visible() || track_index == 0)
117+
if (trim.has_value())
83118
{
84-
flat_track->insert_child(
85-
static_cast<int>(flat_track->children().size()),
86-
static_cast<Composable*>(child->clone(error_status)),
119+
_flatten_next_item(
120+
range_track_map,
121+
flat_track,
122+
tracks,
123+
track_index - 1,
124+
trim,
87125
error_status);
126+
88127
if (is_error(error_status))
89128
{
90129
return;
91130
}
92131
}
93-
else
132+
133+
if (child)
94134
{
95-
TimeRange trim = (*track_map)[item];
96-
if (trim_range)
135+
auto item = dynamic_retainer_cast<Item>(child);
136+
if (!item)
97137
{
98-
trim = TimeRange(
99-
trim.start_time() + trim_range->start_time(),
100-
trim.duration());
101-
(*track_map)[item] = trim;
138+
if (!dynamic_retainer_cast<Transition>(child))
139+
{
140+
if (error_status)
141+
{
142+
*error_status = ErrorStatus(
143+
ErrorStatus::TYPE_MISMATCH,
144+
"expected item of type Item* || Transition*",
145+
child);
146+
}
147+
return;
148+
}
102149
}
103150

104-
_flatten_next_item(
105-
range_track_map,
106-
flat_track,
107-
tracks,
108-
track_index - 1,
109-
trim,
151+
flat_track->insert_child(
152+
static_cast<int>(flat_track->children().size()),
153+
static_cast<Composable*>(child->clone(error_status)),
110154
error_status);
111-
if (track == nullptr || is_error(error_status))
155+
156+
if (is_error(error_status))
112157
{
113158
return;
114159
}

tests/test_stack_algo.cpp

+105-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <opentimelineio/clip.h>
88
#include <opentimelineio/stack.h>
99
#include <opentimelineio/track.h>
10+
#include <opentimelineio/gap.h>
1011
#include <opentimelineio/stackAlgorithm.h>
1112

1213
#include <iostream>
@@ -213,6 +214,109 @@ main(int argc, char** argv)
213214
assertEqual(result->duration().value(), 300);
214215
});
215216

217+
tests.add_test(
218+
"test_flatten_stack_04", [] {
219+
using namespace otio;
220+
221+
otio::RationalTime rt_0_24{0, 24};
222+
otio::RationalTime rt_150_24{150, 24};
223+
otio::TimeRange tr_AB_150_24{rt_0_24, rt_150_24};
224+
225+
otio::TimeRange tr_C_150_24{rt_0_24, otio::RationalTime(300, 24)};
226+
227+
// A and B are Gaps placed over clip C
228+
// C should remain uncut
229+
// 0 150 300
230+
// [ A | B ]
231+
// [ C ]
232+
//
233+
// should flatten to:
234+
// [ C ]
235+
236+
otio::SerializableObject::Retainer<otio::Gap> cl_A =
237+
new otio::Gap(tr_AB_150_24, "track1_A");
238+
otio::SerializableObject::Retainer<otio::Gap> cl_B =
239+
new otio::Gap(tr_AB_150_24, "track1_B");
240+
otio::SerializableObject::Retainer<otio::Clip> cl_C =
241+
new otio::Clip("track2_C", nullptr, tr_C_150_24);
242+
243+
otio::SerializableObject::Retainer<otio::Track> tr_over =
244+
new otio::Track();
245+
tr_over->append_child(cl_A);
246+
tr_over->append_child(cl_B);
247+
248+
otio::SerializableObject::Retainer<otio::Track> tr_under =
249+
new otio::Track();
250+
tr_under->append_child(cl_C);
251+
252+
otio::SerializableObject::Retainer<otio::Stack> st =
253+
new otio::Stack();
254+
st->append_child(tr_under);
255+
st->append_child(tr_over);
256+
257+
auto result = flatten_stack(st);
258+
259+
assertEqual(result->children().size(), 1);
260+
assertEqual(result->children()[0]->name(), std::string("track2_C"));
261+
assertEqual(result->duration().value(), 300);
262+
assertEqual(st->children().size(), 2);
263+
assertEqual(dynamic_cast<otio::Track*>(st->children()[0].value)->children().size(), 1);
264+
assertEqual(dynamic_cast<otio::Track*>(st->children()[1].value)->children().size(), 2);
265+
});
266+
267+
tests.add_test(
268+
"test_flatten_stack_05", [] {
269+
using namespace otio;
270+
271+
otio::RationalTime rt_0_24{0, 24};
272+
otio::RationalTime rt_150_24{150, 24};
273+
otio::TimeRange tr_150_24{rt_0_24, rt_150_24};
274+
275+
// A and B are Gaps placed over clips C and D
276+
// with a cut at the same location
277+
// 0 150 300
278+
// [ A | B ]
279+
// [ C | D ]
280+
//
281+
// should flatten to:
282+
// [ C | D ]
283+
284+
otio::SerializableObject::Retainer<otio::Gap> cl_A =
285+
new otio::Gap(tr_150_24, "track1_A");
286+
otio::SerializableObject::Retainer<otio::Gap> cl_B =
287+
new otio::Gap(tr_150_24, "track1_B");
288+
otio::SerializableObject::Retainer<otio::Clip> cl_C =
289+
new otio::Clip("track2_C", nullptr, tr_150_24);
290+
otio::SerializableObject::Retainer<otio::Clip> cl_D =
291+
new otio::Clip("track2_D", nullptr, tr_150_24);
292+
293+
294+
otio::SerializableObject::Retainer<otio::Track> tr_over =
295+
new otio::Track();
296+
tr_over->append_child(cl_A);
297+
tr_over->append_child(cl_B);
298+
299+
otio::SerializableObject::Retainer<otio::Track> tr_under =
300+
new otio::Track();
301+
tr_under->append_child(cl_C);
302+
tr_under->append_child(cl_D);
303+
304+
otio::SerializableObject::Retainer<otio::Stack> st =
305+
new otio::Stack();
306+
st->append_child(tr_under);
307+
st->append_child(tr_over);
308+
309+
auto result = flatten_stack(st);
310+
311+
assertEqual(result->children().size(), 2);
312+
assertEqual(result->children()[0]->name(), std::string("track2_C"));
313+
assertEqual(result->children()[1]->name(), std::string("track2_D"));
314+
assertEqual(result->duration().value(), 300);
315+
assertEqual(st->children().size(), 2);
316+
assertEqual(dynamic_cast<otio::Track*>(st->children()[0].value)->children().size(), 2);
317+
assertEqual(dynamic_cast<otio::Track*>(st->children()[1].value)->children().size(), 2);
318+
});
319+
216320
tests.run(argc, argv);
217321
return 0;
218-
}
322+
}

0 commit comments

Comments
 (0)