diff --git a/src/opentimelineio/stackAlgorithm.cpp b/src/opentimelineio/stackAlgorithm.cpp index c8b60abbb..ac0cd3ccb 100644 --- a/src/opentimelineio/stackAlgorithm.cpp +++ b/src/opentimelineio/stackAlgorithm.cpp @@ -12,6 +12,17 @@ namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { typedef std::map> RangeTrackMap; typedef std::vector> TrackRetainerVector; +static bool +_is_not_visible( + SerializableObject::Retainer thing) +{ + auto item = dynamic_retainer_cast(thing); + if (!item) + return false; + + return !item->visible(); +} + static void _flatten_next_item( RangeTrackMap& range_track_map, @@ -61,54 +72,88 @@ _flatten_next_item( } track_map = &result.first->second; } - for (auto child: track->children()) + + auto children = track->children(); + for (size_t i = 0; i < children.size(); i++) { - auto item = dynamic_retainer_cast(child); - if (!item) + std::optional trim = std::nullopt; + SerializableObject::Retainer child = children[i]; + + // combine all non visible children into one continuous range + while(track_index > 0 && _is_not_visible(child)) { - if (!dynamic_retainer_cast(child)) + TimeRange child_trim = (*track_map)[child]; + + // offset ranges so they are in track range before being trimmed + if (trim_range) { - if (error_status) - { - *error_status = ErrorStatus( - ErrorStatus::TYPE_MISMATCH, - "expected item of type Item* || Transition*", - child); - } - return; + trim = TimeRange( + child_trim.start_time() + trim_range->start_time(), + child_trim.duration()); + (*track_map)[child] = child_trim; + } + + if (trim.has_value()) + { + trim = trim->duration_extended_by(child_trim.duration()); + } + else + { + trim = child_trim; + } + + if (i+1 < children.size()) + { + child = children[i++]; + } + else + { + // last item is not visible + child = nullptr; + break; } } - if (!item || item->visible() || track_index == 0) + if (trim.has_value()) { - flat_track->insert_child( - static_cast(flat_track->children().size()), - static_cast(child->clone(error_status)), + _flatten_next_item( + range_track_map, + flat_track, + tracks, + track_index - 1, + trim, error_status); + if (is_error(error_status)) { return; } } - else + + if (child) { - TimeRange trim = (*track_map)[item]; - if (trim_range) + auto item = dynamic_retainer_cast(child); + if (!item) { - trim = TimeRange( - trim.start_time() + trim_range->start_time(), - trim.duration()); - (*track_map)[item] = trim; + if (!dynamic_retainer_cast(child)) + { + if (error_status) + { + *error_status = ErrorStatus( + ErrorStatus::TYPE_MISMATCH, + "expected item of type Item* || Transition*", + child); + } + return; + } } - _flatten_next_item( - range_track_map, - flat_track, - tracks, - track_index - 1, - trim, + flat_track->insert_child( + static_cast(flat_track->children().size()), + static_cast(child->clone(error_status)), error_status); - if (track == nullptr || is_error(error_status)) + + if (is_error(error_status)) { return; } diff --git a/tests/test_stack_algo.cpp b/tests/test_stack_algo.cpp index 39a953d73..40512290e 100644 --- a/tests/test_stack_algo.cpp +++ b/tests/test_stack_algo.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -213,6 +214,109 @@ main(int argc, char** argv) assertEqual(result->duration().value(), 300); }); + tests.add_test( + "test_flatten_stack_04", [] { + using namespace otio; + + otio::RationalTime rt_0_24{0, 24}; + otio::RationalTime rt_150_24{150, 24}; + otio::TimeRange tr_AB_150_24{rt_0_24, rt_150_24}; + + otio::TimeRange tr_C_150_24{rt_0_24, otio::RationalTime(300, 24)}; + + // A and B are Gaps placed over clip C + // C should remain uncut + // 0 150 300 + // [ A | B ] + // [ C ] + // + // should flatten to: + // [ C ] + + otio::SerializableObject::Retainer cl_A = + new otio::Gap(tr_AB_150_24, "track1_A"); + otio::SerializableObject::Retainer cl_B = + new otio::Gap(tr_AB_150_24, "track1_B"); + otio::SerializableObject::Retainer cl_C = + new otio::Clip("track2_C", nullptr, tr_C_150_24); + + otio::SerializableObject::Retainer tr_over = + new otio::Track(); + tr_over->append_child(cl_A); + tr_over->append_child(cl_B); + + otio::SerializableObject::Retainer tr_under = + new otio::Track(); + tr_under->append_child(cl_C); + + otio::SerializableObject::Retainer st = + new otio::Stack(); + st->append_child(tr_under); + st->append_child(tr_over); + + auto result = flatten_stack(st); + + assertEqual(result->children().size(), 1); + assertEqual(result->children()[0]->name(), std::string("track2_C")); + assertEqual(result->duration().value(), 300); + assertEqual(st->children().size(), 2); + assertEqual(dynamic_cast(st->children()[0].value)->children().size(), 1); + assertEqual(dynamic_cast(st->children()[1].value)->children().size(), 2); + }); + + tests.add_test( + "test_flatten_stack_05", [] { + using namespace otio; + + otio::RationalTime rt_0_24{0, 24}; + otio::RationalTime rt_150_24{150, 24}; + otio::TimeRange tr_150_24{rt_0_24, rt_150_24}; + + // A and B are Gaps placed over clips C and D + // with a cut at the same location + // 0 150 300 + // [ A | B ] + // [ C | D ] + // + // should flatten to: + // [ C | D ] + + otio::SerializableObject::Retainer cl_A = + new otio::Gap(tr_150_24, "track1_A"); + otio::SerializableObject::Retainer cl_B = + new otio::Gap(tr_150_24, "track1_B"); + otio::SerializableObject::Retainer cl_C = + new otio::Clip("track2_C", nullptr, tr_150_24); + otio::SerializableObject::Retainer cl_D = + new otio::Clip("track2_D", nullptr, tr_150_24); + + + otio::SerializableObject::Retainer tr_over = + new otio::Track(); + tr_over->append_child(cl_A); + tr_over->append_child(cl_B); + + otio::SerializableObject::Retainer tr_under = + new otio::Track(); + tr_under->append_child(cl_C); + tr_under->append_child(cl_D); + + otio::SerializableObject::Retainer st = + new otio::Stack(); + st->append_child(tr_under); + st->append_child(tr_over); + + auto result = flatten_stack(st); + + assertEqual(result->children().size(), 2); + assertEqual(result->children()[0]->name(), std::string("track2_C")); + assertEqual(result->children()[1]->name(), std::string("track2_D")); + assertEqual(result->duration().value(), 300); + assertEqual(st->children().size(), 2); + assertEqual(dynamic_cast(st->children()[0].value)->children().size(), 2); + assertEqual(dynamic_cast(st->children()[1].value)->children().size(), 2); + }); + tests.run(argc, argv); return 0; -} \ No newline at end of file +}