Skip to content

Commit 665c482

Browse files
authored
Normalize track lengths with gaps before flatten stacks (#1703)
* Normalize track lengths with gaps before flatten stacks fixes #1430 Signed-off-by: Mark Reid <mindmark@gmail.com>
1 parent 383fcdd commit 665c482

File tree

3 files changed

+290
-2
lines changed

3 files changed

+290
-2
lines changed

src/opentimelineio/stackAlgorithm.cpp

+71-1
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
#include "opentimelineio/track.h"
66
#include "opentimelineio/trackAlgorithm.h"
77
#include "opentimelineio/transition.h"
8+
#include "opentimelineio/gap.h"
89

910
namespace opentimelineio { namespace OPENTIMELINEIO_VERSION {
1011

1112
typedef std::map<Track*, std::map<Composable*, TimeRange>> RangeTrackMap;
13+
typedef std::vector<SerializableObject::Retainer<Track>> TrackRetainerVector;
1214

1315
static void
1416
_flatten_next_item(
@@ -123,10 +125,55 @@ _flatten_next_item(
123125
}
124126
}
125127

128+
// add a gap to end of a track if it is shorter then the longest track.
129+
// shorter tracks are clones and get added to the tracks_retainer.
130+
// a new track will replace the original pointer in the track vector.
131+
static void
132+
_normalize_tracks_lengths(std::vector<Track*>& tracks,
133+
TrackRetainerVector& tracks_retainer,
134+
ErrorStatus* error_status)
135+
{
136+
RationalTime duration;
137+
for (auto track: tracks)
138+
{
139+
duration = std::max(duration, track->duration(error_status));
140+
if (is_error(error_status))
141+
{
142+
return;
143+
}
144+
}
145+
146+
for(int i = 0; i < tracks.size(); i++)
147+
{
148+
Track *old_track = tracks[i];
149+
RationalTime track_duration = old_track->duration(error_status);
150+
if (track_duration < duration)
151+
{
152+
Track *new_track = static_cast<Track*>(old_track->clone(error_status));
153+
if (is_error(error_status))
154+
{
155+
return;
156+
}
157+
// add track to retainer so it can be freed later
158+
tracks_retainer.push_back(SerializableObject::Retainer<Track>(new_track));
159+
new_track->append_child(new Gap(duration - track_duration), error_status);
160+
if (is_error(error_status))
161+
{
162+
return;
163+
}
164+
tracks[i] = new_track;
165+
}
166+
}
167+
}
168+
126169
Track*
127170
flatten_stack(Stack* in_stack, ErrorStatus* error_status)
128171
{
129172
std::vector<Track*> tracks;
173+
// tracks are cloned if they need to be normalized
174+
// they get added to this retainer so they can be
175+
// freed when the algorithm is complete
176+
TrackRetainerVector tracks_retainer;
130177
tracks.reserve(in_stack->children().size());
131178

132179
for (auto c: in_stack->children())
@@ -151,6 +198,12 @@ flatten_stack(Stack* in_stack, ErrorStatus* error_status)
151198
}
152199
}
153200

201+
_normalize_tracks_lengths(tracks, tracks_retainer, error_status);
202+
if (is_error(error_status))
203+
{
204+
return nullptr;
205+
}
206+
154207
Track* flat_track = new Track;
155208
flat_track->set_name("Flattened");
156209

@@ -168,14 +221,31 @@ flatten_stack(Stack* in_stack, ErrorStatus* error_status)
168221
Track*
169222
flatten_stack(std::vector<Track*> const& tracks, ErrorStatus* error_status)
170223
{
224+
std::vector<Track*> flat_tracks;
225+
// tracks are cloned if they need to be normalized
226+
// they get added to this retainer so they can be
227+
// freed when the algorithm is complete
228+
TrackRetainerVector tracks_retainer;
229+
flat_tracks.reserve(tracks.size());
230+
for (auto track : tracks)
231+
{
232+
flat_tracks.push_back(track);
233+
}
234+
235+
_normalize_tracks_lengths(flat_tracks, tracks_retainer, error_status);
236+
if (is_error(error_status))
237+
{
238+
return nullptr;
239+
}
240+
171241
Track* flat_track = new Track;
172242
flat_track->set_name("Flattened");
173243

174244
RangeTrackMap range_track_map;
175245
_flatten_next_item(
176246
range_track_map,
177247
flat_track,
178-
tracks,
248+
flat_tracks,
179249
-1,
180250
nullopt,
181251
error_status);

tests/CMakeLists.txt

+1-1
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 test_serialization test_serializableCollection test_timeline test_track test_editAlgorithm)
18+
list(APPEND tests_opentimelineio test_clip test_serialization test_serializableCollection test_stack_algo test_timeline test_track test_editAlgorithm)
1919
foreach(test ${tests_opentimelineio})
2020
add_executable(${test} utils.h utils.cpp ${test}.cpp)
2121

tests/test_stack_algo.cpp

+218
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright Contributors to the OpenTimelineIO project
3+
4+
#include "opentime/timeRange.h"
5+
#include "utils.h"
6+
7+
#include <opentimelineio/clip.h>
8+
#include <opentimelineio/stack.h>
9+
#include <opentimelineio/track.h>
10+
#include <opentimelineio/stackAlgorithm.h>
11+
12+
#include <iostream>
13+
14+
namespace otime = opentime::OPENTIME_VERSION;
15+
namespace otio = opentimelineio::OPENTIMELINEIO_VERSION;
16+
17+
int
18+
main(int argc, char** argv)
19+
{
20+
Tests tests;
21+
22+
tests.add_test(
23+
"test_flatten_stack_01", [] {
24+
using namespace otio;
25+
26+
otio::RationalTime rt_0_24{0, 24};
27+
otio::RationalTime rt_150_24{150, 24};
28+
otio::TimeRange tr_0_150_24{rt_0_24, rt_150_24};
29+
30+
// all three clips are identical, but placed such that A is over B and
31+
// has no gap or end over C
32+
// 0 150 300
33+
// [ A ]
34+
// [ B | C ]
35+
//
36+
// should flatten to:
37+
// [ A | C ]
38+
39+
otio::SerializableObject::Retainer<otio::Clip> cl_A =
40+
new otio::Clip("track1_A", nullptr, tr_0_150_24);
41+
otio::SerializableObject::Retainer<otio::Clip> cl_B =
42+
new otio::Clip("track1_B", nullptr, tr_0_150_24);
43+
otio::SerializableObject::Retainer<otio::Clip> cl_C =
44+
new otio::Clip("track1_C", nullptr, tr_0_150_24);
45+
46+
otio::SerializableObject::Retainer<otio::Track> tr_over =
47+
new otio::Track();
48+
tr_over->append_child(cl_A);
49+
50+
otio::SerializableObject::Retainer<otio::Track> tr_under =
51+
new otio::Track();
52+
tr_under->append_child(cl_B);
53+
tr_under->append_child(cl_C);
54+
55+
otio::SerializableObject::Retainer<otio::Stack> st =
56+
new otio::Stack();
57+
st->append_child(tr_under);
58+
st->append_child(tr_over);
59+
60+
auto result = flatten_stack(st);
61+
62+
assertEqual(result->children()[0]->name(), std::string("track1_A"));
63+
assertEqual(result->children().size(), 2);
64+
assertEqual(result->duration().value(), 300);
65+
});
66+
67+
tests.add_test(
68+
"test_flatten_stack_02", [] {
69+
using namespace otio;
70+
71+
otio::RationalTime rt_0_24{0, 24};
72+
otio::RationalTime rt_150_24{150, 24};
73+
otio::TimeRange tr_0_150_24{rt_0_24, rt_150_24};
74+
75+
// all four clips are identical, but placed such that A is over B and
76+
// has no gap or end over C. The bottom track is also shorter.
77+
// 0 150 300
78+
// [ A ]
79+
// [ B | C ]
80+
// [ D ]
81+
//
82+
// should flatten to:
83+
// [ A | C ]
84+
85+
otio::SerializableObject::Retainer<otio::Clip> cl_A =
86+
new otio::Clip("track1_A", nullptr, tr_0_150_24);
87+
otio::SerializableObject::Retainer<otio::Clip> cl_B =
88+
new otio::Clip("track1_B", nullptr, tr_0_150_24);
89+
otio::SerializableObject::Retainer<otio::Clip> cl_C =
90+
new otio::Clip("track1_C", nullptr, tr_0_150_24);
91+
otio::SerializableObject::Retainer<otio::Clip> cl_D =
92+
new otio::Clip("track1_D", nullptr, tr_0_150_24);
93+
94+
otio::SerializableObject::Retainer<otio::Track> tr_top =
95+
new otio::Track();
96+
tr_top->append_child(cl_A);
97+
98+
otio::SerializableObject::Retainer<otio::Track> tr_middle =
99+
new otio::Track();
100+
tr_middle->append_child(cl_B);
101+
tr_middle->append_child(cl_C);
102+
103+
otio::SerializableObject::Retainer<otio::Track> tr_bottom =
104+
new otio::Track();
105+
106+
tr_bottom->append_child(cl_D);
107+
108+
otio::SerializableObject::Retainer<otio::Stack> st =
109+
new otio::Stack();
110+
st->append_child(tr_bottom);
111+
st->append_child(tr_middle);
112+
st->append_child(tr_top);
113+
114+
auto result = flatten_stack(st);
115+
116+
assertEqual(result->children()[0]->name(), std::string("track1_A"));
117+
assertEqual(result->children().size(), 2);
118+
assertEqual(result->duration().value(), 300);
119+
});
120+
121+
tests.add_test(
122+
"test_flatten_stack_03", [] {
123+
using namespace otio;
124+
125+
otio::RationalTime rt_0_24{0, 24};
126+
otio::RationalTime rt_150_24{150, 24};
127+
otio::TimeRange tr_0_150_24{rt_0_24, rt_150_24};
128+
129+
// all three clips are identical but the middle track is empty
130+
// 0 150 300
131+
// [ A ]
132+
// []
133+
// [ B | C ]
134+
//
135+
// should flatten to:
136+
// [ A | C ]
137+
138+
otio::SerializableObject::Retainer<otio::Clip> cl_A =
139+
new otio::Clip("track1_A", nullptr, tr_0_150_24);
140+
otio::SerializableObject::Retainer<otio::Clip> cl_B =
141+
new otio::Clip("track1_B", nullptr, tr_0_150_24);
142+
otio::SerializableObject::Retainer<otio::Clip> cl_C =
143+
new otio::Clip("track1_C", nullptr, tr_0_150_24);
144+
145+
otio::SerializableObject::Retainer<otio::Track> tr_top =
146+
new otio::Track();
147+
tr_top->append_child(cl_A);
148+
149+
otio::SerializableObject::Retainer<otio::Track> tr_middle =
150+
new otio::Track();
151+
152+
otio::SerializableObject::Retainer<otio::Track> tr_bottom =
153+
new otio::Track();
154+
155+
tr_bottom->append_child(cl_B);
156+
tr_bottom->append_child(cl_C);
157+
158+
159+
otio::SerializableObject::Retainer<otio::Stack> st =
160+
new otio::Stack();
161+
st->append_child(tr_bottom);
162+
st->append_child(tr_middle);
163+
st->append_child(tr_top);
164+
165+
auto result = flatten_stack(st);
166+
167+
assertEqual(result->children()[0]->name(), std::string("track1_A"));
168+
assertEqual(result->children().size(), 2);
169+
assertEqual(result->duration().value(), 300);
170+
});
171+
172+
tests.add_test(
173+
"test_flatten_vector_01", [] {
174+
using namespace otio;
175+
176+
otio::RationalTime rt_0_24{0, 24};
177+
otio::RationalTime rt_150_24{150, 24};
178+
otio::TimeRange tr_0_150_24{rt_0_24, rt_150_24};
179+
180+
// all three clips are identical, but placed such that A is over B and
181+
// has no gap or end over C, tests vector version
182+
// 0 150 300
183+
// [ A ]
184+
// [ B | C ]
185+
//
186+
// should flatten to:
187+
// [ A | C ]
188+
189+
otio::SerializableObject::Retainer<otio::Clip> cl_A =
190+
new otio::Clip("track1_A", nullptr, tr_0_150_24);
191+
otio::SerializableObject::Retainer<otio::Clip> cl_B =
192+
new otio::Clip("track1_B", nullptr, tr_0_150_24);
193+
otio::SerializableObject::Retainer<otio::Clip> cl_C =
194+
new otio::Clip("track1_C", nullptr, tr_0_150_24);
195+
196+
otio::SerializableObject::Retainer<otio::Track> tr_over =
197+
new otio::Track();
198+
tr_over->append_child(cl_A);
199+
200+
otio::SerializableObject::Retainer<otio::Track> tr_under =
201+
new otio::Track();
202+
tr_under->append_child(cl_B);
203+
tr_under->append_child(cl_C);
204+
205+
std::vector<Track *> st;
206+
st.push_back(tr_under);
207+
st.push_back(tr_over);
208+
209+
auto result = flatten_stack(st);
210+
211+
assertEqual(result->children()[0]->name(), std::string("track1_A"));
212+
assertEqual(result->children().size(), 2);
213+
assertEqual(result->duration().value(), 300);
214+
});
215+
216+
tests.run(argc, argv);
217+
return 0;
218+
}

0 commit comments

Comments
 (0)