Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix flattening tracks sometimes splits contiguous clips into segments issue #1818

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
105 changes: 75 additions & 30 deletions src/opentimelineio/stackAlgorithm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ namespace opentimelineio { namespace OPENTIMELINEIO_VERSION {
typedef std::map<Track*, std::map<Composable*, TimeRange>> RangeTrackMap;
typedef std::vector<SerializableObject::Retainer<Track>> TrackRetainerVector;

static bool
_is_not_visible(
SerializableObject::Retainer<Composable> thing)
{
auto item = dynamic_retainer_cast<Item>(thing);
if (!item)
return false;

return !item->visible();
}

static void
_flatten_next_item(
RangeTrackMap& range_track_map,
Expand Down Expand Up @@ -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<Item>(child);
if (!item)
std::optional<TimeRange> trim = std::nullopt;
SerializableObject::Retainer<Composable> child = children[i];

// combine all non visible children into one continuous range
while(track_index > 0 && _is_not_visible(child))
{
if (!dynamic_retainer_cast<Transition>(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<int>(flat_track->children().size()),
static_cast<Composable*>(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<Item>(child);
if (!item)
{
trim = TimeRange(
trim.start_time() + trim_range->start_time(),
trim.duration());
(*track_map)[item] = trim;
if (!dynamic_retainer_cast<Transition>(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<int>(flat_track->children().size()),
static_cast<Composable*>(child->clone(error_status)),
error_status);
if (track == nullptr || is_error(error_status))

if (is_error(error_status))
{
return;
}
Expand Down
106 changes: 105 additions & 1 deletion tests/test_stack_algo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <opentimelineio/clip.h>
#include <opentimelineio/stack.h>
#include <opentimelineio/track.h>
#include <opentimelineio/gap.h>
#include <opentimelineio/stackAlgorithm.h>

#include <iostream>
Expand Down Expand Up @@ -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<otio::Gap> cl_A =
new otio::Gap(tr_AB_150_24, "track1_A");
otio::SerializableObject::Retainer<otio::Gap> cl_B =
new otio::Gap(tr_AB_150_24, "track1_B");
otio::SerializableObject::Retainer<otio::Clip> cl_C =
new otio::Clip("track2_C", nullptr, tr_C_150_24);

otio::SerializableObject::Retainer<otio::Track> tr_over =
new otio::Track();
tr_over->append_child(cl_A);
tr_over->append_child(cl_B);

otio::SerializableObject::Retainer<otio::Track> tr_under =
new otio::Track();
tr_under->append_child(cl_C);

otio::SerializableObject::Retainer<otio::Stack> 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<otio::Track*>(st->children()[0].value)->children().size(), 1);
assertEqual(dynamic_cast<otio::Track*>(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<otio::Gap> cl_A =
new otio::Gap(tr_150_24, "track1_A");
otio::SerializableObject::Retainer<otio::Gap> cl_B =
new otio::Gap(tr_150_24, "track1_B");
otio::SerializableObject::Retainer<otio::Clip> cl_C =
new otio::Clip("track2_C", nullptr, tr_150_24);
otio::SerializableObject::Retainer<otio::Clip> cl_D =
new otio::Clip("track2_D", nullptr, tr_150_24);


otio::SerializableObject::Retainer<otio::Track> tr_over =
new otio::Track();
tr_over->append_child(cl_A);
tr_over->append_child(cl_B);

otio::SerializableObject::Retainer<otio::Track> tr_under =
new otio::Track();
tr_under->append_child(cl_C);
tr_under->append_child(cl_D);

otio::SerializableObject::Retainer<otio::Stack> 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<otio::Track*>(st->children()[0].value)->children().size(), 2);
assertEqual(dynamic_cast<otio::Track*>(st->children()[1].value)->children().size(), 2);
});

tests.run(argc, argv);
return 0;
}
}
Loading