diff --git a/src/opentimelineio/CMakeLists.txt b/src/opentimelineio/CMakeLists.txt index 1860b8e97..318eac362 100644 --- a/src/opentimelineio/CMakeLists.txt +++ b/src/opentimelineio/CMakeLists.txt @@ -9,6 +9,7 @@ set(OPENTIMELINEIO_HEADER_FILES composable.h composition.h deserialization.h + algo/editAlgorithm.h effect.h errorStatus.h externalReference.h @@ -44,6 +45,7 @@ add_library(opentimelineio ${OTIO_SHARED_OR_STATIC_LIB} composable.cpp composition.cpp deserialization.cpp + algo/editAlgorithm.cpp effect.cpp errorStatus.cpp externalReference.cpp diff --git a/src/opentimelineio/algo/editAlgorithm.cpp b/src/opentimelineio/algo/editAlgorithm.cpp new file mode 100644 index 000000000..b0694264a --- /dev/null +++ b/src/opentimelineio/algo/editAlgorithm.cpp @@ -0,0 +1,886 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the OpenTimelineIO project + +#include + +#include "opentimelineio/algo/editAlgorithm.h" + +#include "opentimelineio/effect.h" +#include "opentimelineio/gap.h" +#include "opentimelineio/linearTimeWarp.h" +#include "opentimelineio/track.h" +#include "opentimelineio/transition.h" + +namespace otime = opentime::OPENTIME_VERSION; + +using otime::RationalTime; +using otime::TimeRange; + +namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { namespace algo { + + +#include + +inline std::ostream& +operator<<(std::ostream& os, const RationalTime& value) +{ + os << std::fixed << value.value() << "/" << value.rate(); + return os; +} + +inline std::ostream& +operator<<(std::ostream& os, const TimeRange& value) +{ + os << std::fixed << value.start_time().value() << "/" << + value.duration().value() << "/" << + value.duration().rate(); + return os; +} + +namespace +{ + +// We are not testing values outside of one million seconds. +// At one million second, and double precision, the smallest +// resolvable number that can be added to one million and return +// a new value one million + epsilon is 5.82077e-11. +// +// This was calculated by searching iteratively for epsilon +// around 1,000,000, with epsilon starting from 1 and halved +// at every iteration, until epsilon when added to 1,000,000 +// resulted in 1,000,000. +constexpr double double_epsilon = 5.82077e-11; + +inline bool +isEqual(double a, double b) +{ + return (std::abs(a - b) <= double_epsilon); +} + +} // namespace + +void +overwrite( + Item* item, + Composition* composition, + TimeRange const& range, + bool const remove_transitions, + Item* fill_template, + ErrorStatus* error_status) +{ + const TimeRange composition_range = composition->trimmed_range(); + const RationalTime start_time = range.start_time(); + if (start_time >= composition_range.end_time_exclusive()) + { + // Append the item and a possible fill (gap). + const RationalTime fill_duration = + range.start_time() - composition_range.end_time_exclusive(); + if (!isEqual(fill_duration.value(), 0.0)) + { + const TimeRange fill_range = TimeRange( + RationalTime(0.0, fill_duration.rate()), + fill_duration); + if (!fill_template) + fill_template = new Gap(fill_range); + composition->append_child(fill_template); + } + composition->append_child(item); + } + else if (start_time < composition_range.start_time() && + range.end_time_exclusive() < composition_range.start_time()) + { + const RationalTime fill_duration = + composition_range.start_time() - start_time - range.duration(); + if (!isEqual(fill_duration.value(), 0.0)) + { + const TimeRange fill_range = TimeRange( + RationalTime(0.0, fill_duration.rate()), + fill_duration); + if (!fill_template) + fill_template = new Gap(fill_range); + composition->insert_child(0, fill_template); + } + composition->insert_child(0, item); + } + else + { + // Check for transitions to remove first. + if (remove_transitions) + { + auto transitions = composition->find_children( + error_status, + range, + true); + if (!transitions.empty()) + { + for (const auto& transition : transitions) + { + int index = composition->index_of_child(transition); + if (index < 0 + || static_cast(index) + >= composition->children().size()) + continue; + composition->remove_child(transition); + } + } + } + + // Find the items to overwrite. + auto items = + composition->find_children(error_status, range, true); + if (items.empty()) + { + if (error_status) + *error_status = ErrorStatus::NOT_AN_ITEM; + return; + } + TimeRange item_range = + composition->trimmed_range_of_child(items.front()).value(); + if (1 == items.size() && item_range.contains(range, 0.0)) + { + auto first_item = items.front(); + + bool is_fill_fit = false; + + // We check if we are replacing a gap with a clip with timewarp, + // which is the special case of fill() ReferencePoint::Fit. + if (dynamic_retainer_cast(first_item)) + { + auto effects = item->effects(); + for (auto& effect : effects) + { + if (dynamic_retainer_cast(effect)) + { + is_fill_fit = true; + break; + } + } + } + + // The item overwrites a portion inside an item. + const RationalTime first_duration = + range.start_time() - item_range.start_time(); + const RationalTime second_duration = + item_range.duration() - range.duration() - first_duration; + int first_index = composition->index_of_child(first_item); + int insert_index = first_index; + TimeRange trimmed_range = first_item->trimmed_range(); + TimeRange source_range(trimmed_range.start_time(), first_duration); + if (isEqual(first_duration.value(), 0.0)) + { + composition->remove_child(first_index); + } + else + { + first_item->set_source_range(source_range); + ++insert_index; + } + item_range = item->trimmed_range(); + if (range.duration() < item_range.duration() && !is_fill_fit) + item->set_source_range( + TimeRange(trimmed_range.start_time(), range.duration())); + composition->insert_child(insert_index, item); + if (!isEqual(second_duration.value(), 0.0)) + { + auto second_item = dynamic_cast(items.front()->clone()); + trimmed_range = second_item->trimmed_range(); + source_range = TimeRange( + trimmed_range.start_time() + first_duration + + range.duration(), + second_duration); + ++insert_index; + second_item->set_source_range(source_range); + composition->insert_child(insert_index, second_item); + } + } + else + { + // Determine if the first item is partially overwritten. + int insert_index = composition->index_of_child(items.front()); + bool first_partial = false; + TimeRange first_source_range; + if (item_range.start_time() < range.start_time()) + { + first_partial = true; + const TimeRange trimmed_range = items.front()->trimmed_range(); + first_source_range = TimeRange( + trimmed_range.start_time(), + range.start_time() - item_range.start_time()); + ++insert_index; + } + + // Determine if the last item is partially overwritten. + bool last_partial = false; + TimeRange last_source_range; + if (items.size() >= 1) + { + item_range = + composition->trimmed_range_of_child(items.back()).value(); + if (item_range.end_time_inclusive() + > range.end_time_inclusive()) + { + last_partial = true; + const TimeRange trimmed_range = + items.back()->trimmed_range(); + RationalTime duration = item_range.end_time_inclusive() + - range.end_time_inclusive(); + if (items.size() == 1) + { + duration += range.start_time(); + last_source_range = TimeRange( + trimmed_range.start_time() + range.duration(), + duration); + } + else + { + last_source_range = TimeRange( + trimmed_range.start_time() + duration, + trimmed_range.duration() - duration); + } + } + } + + // Adjust the first and last items. + if (first_partial) + { + items.front()->set_source_range(first_source_range); + items.erase(items.begin()); + } + if (last_partial) + { + items.back()->set_source_range(last_source_range); + items.erase(items.end() - 1); + } + + // Remove the completely overwritten items. + while (!items.empty()) + { + composition->remove_child(items.back()); + items.pop_back(); + } + + // Insert the item. + const TimeRange trimmed_range = item->trimmed_range(); + item->set_source_range( + TimeRange(trimmed_range.start_time(), range.duration())); + composition->insert_child(insert_index, item); + } + } +} + +void +insert( + Item* const insert_item, + Composition* composition, + RationalTime const& time, + bool const remove_transitions, + Item* fill_template, + ErrorStatus* error_status) +{ + // Check for transitions to remove first. + if (remove_transitions) + { + TimeRange range(time, RationalTime(1.0, time.rate())); + auto transitions = composition->find_children( + error_status, + range, + true); + if (!transitions.empty()) + { + for (const auto& transition : transitions) + { + int index = composition->index_of_child(transition); + if (index < 0 + || static_cast(index) + >= composition->children().size()) + continue; + composition->remove_child(transition); + } + } + } + + const TimeRange composition_range = composition->trimmed_range(); + + // Find the item to insert into. + auto item = dynamic_retainer_cast( + composition->child_at_time(time, error_status)); + if (!item) + { + if (time >= composition_range.end_time_exclusive()) + { + // Append the item and a possible fill (gap). + const RationalTime fill_duration = + time - composition_range.end_time_exclusive(); + if (!isEqual(fill_duration.value(), 0.0)) + { + const TimeRange fill_range = TimeRange( + RationalTime(0.0, fill_duration.rate()), + fill_duration); + if (!fill_template) + fill_template = new Gap(fill_range); + composition->append_child(fill_template); + } + composition->append_child(insert_item); + } + else if (time < composition_range.start_time()) + { + composition->insert_child(0, insert_item); + } + else + { + if (error_status) + *error_status = ErrorStatus::INTERNAL_ERROR; + } + return; + } + + const int index = composition->index_of_child(item); + const TimeRange range = composition->trimmed_range_of_child_at_index(index); + int insert_index = index; + + // Item is partially split + bool split = false; + const TimeRange first_source_range( + item->trimmed_range().start_time(), + time - range.start_time()); + if (!isEqual(first_source_range.duration().value(), 0.0)) + { + split = true; + item->set_source_range(first_source_range); + ++insert_index; + } + + // Insert the new item + composition->insert_child(insert_index, insert_item); + const TimeRange insert_range = composition->trimmed_range_of_child_at_index(insert_index); + + // Second item from splitting item + if (split) + { + const TimeRange second_source_range( + first_source_range.start_time() + + insert_range.start_time() + insert_range.duration(), + range.end_time_exclusive() - time); + // Clone the item for the second partially overwritten item. + if (!isEqual(second_source_range.duration().value(), 0.0)) + { + auto second_item = dynamic_cast(item->clone()); + second_item->set_source_range(second_source_range); + composition->insert_child(insert_index + 1, second_item); + } + } +} + +void trim( + Item* item, + RationalTime const& delta_in, + RationalTime const& delta_out, + Item* fill_template, + ErrorStatus* error_status) +{ + Composition* composition = item->parent(); + if (!composition) + { + if (error_status) + *error_status = ErrorStatus::NOT_A_CHILD_OF; + return; + } + auto children = composition->children(); + const int index = composition->index_of_child(item); + if (index < 0) + { + if (error_status) + *error_status = ErrorStatus::NOT_AN_ITEM; + return; + } + + const TimeRange range = item->trimmed_range(); + RationalTime start_time = range.start_time(); + RationalTime end_time_exclusive = range.end_time_exclusive(); + if (delta_in.value() != 0.0) + { + start_time += delta_in; + if (index > 0) + { + auto previous = dynamic_retainer_cast(children[index - 1]); + TimeRange previous_range = previous->trimmed_range(); + previous_range = TimeRange(previous_range.start_time(), + previous_range.duration() + delta_in); + previous->set_source_range(previous_range); + } + } + if (delta_out.value() != 0.0) + { + const int next_index = index + 1; + if (static_cast(next_index) < children.size()) + { + auto next = dynamic_retainer_cast(children[next_index]); + auto gap_next = dynamic_retainer_cast(children[next_index]); + if (gap_next && delta_out.value() > 0.0) + { + end_time_exclusive += delta_out; + } + else if (delta_out.value() < 0.0) + { + if (gap_next) + { + end_time_exclusive += delta_out; + const TimeRange gap_range = gap_next->trimmed_range(); + const TimeRange gap_new_range( + gap_range.start_time() - delta_out, + gap_range.duration() + delta_out); + gap_next->set_source_range(gap_new_range); + } + else + { + end_time_exclusive += delta_out; + + const RationalTime fill_duration = -delta_out; + if (fill_duration.value() > 0.0) + { + const TimeRange fill_range = TimeRange( + RationalTime(0.0, fill_duration.rate()), + fill_duration); + if (!fill_template) + fill_template = new Gap(fill_range); + composition->insert_child(next_index, fill_template); + } + } + } + } + } + const TimeRange new_range = + TimeRange::range_from_start_end_time(start_time, end_time_exclusive); + item->set_source_range(new_range); +} + +void +slice( + Composition* composition, + RationalTime const& time, + bool const remove_transitions, + ErrorStatus* error_status) +{ + auto item = dynamic_retainer_cast( + composition->child_at_time(time, error_status)); + if (!item) + { + if (error_status) + *error_status = ErrorStatus::NOT_AN_ITEM; + return; + } + + const int index = composition->index_of_child(item); + const TimeRange range = composition->trimmed_range_of_child_at_index(index); + + + // Check for slice at start of clip (invalid slice) + const RationalTime duration = time - range.start_time(); + if (isEqual(duration.value(), 0.0)) + return; + + // Accumulate intersecting transitions + std::vector transitions; + if (auto track = dynamic_cast(composition)) + { + const auto neighbors = track->neighbors_of(item, error_status); + if (auto transition = dynamic_cast(neighbors.second.value)) + { + const auto transition_range = + track->trimmed_range_of_child(transition).value(); + if (transition_range.contains(time)) + { + transitions.push_back(transition); + } + } + if (auto transition = dynamic_cast(neighbors.first.value)) + { + const auto transition_range = + track->trimmed_range_of_child(transition).value(); + if (transition_range.contains(time)) + { + transitions.push_back(transition); + } + } + } + + // Remove transitions + if (!transitions.empty()) + { + if (remove_transitions) + { + for (auto transition : transitions) + { + const int child_index = composition->index_of_child(transition); + composition->remove_child(child_index); + } + } + else + { + if (error_status) + *error_status = ErrorStatus::CANNOT_TRIM_TRANSITION; + return; + } + } + + // Adjust the source range for the first slice. + const TimeRange first_source_range( + item->trimmed_range().start_time(), + duration); + + item->set_source_range(first_source_range); + + // Clone the item for the second slice. + auto second_item = dynamic_cast(item->clone()); + const TimeRange second_source_range( + first_source_range.start_time() + first_source_range.duration(), + range.duration() - first_source_range.duration()); + if (!(isEqual(second_source_range.duration().value(), 0.0))) + { + second_item->set_source_range(second_source_range); + composition->insert_child(static_cast(index) + 1, second_item); + } +} + +void slip( + Item* item, + RationalTime const& delta) +{ + const TimeRange range = item->trimmed_range(); + RationalTime start_time = range.start_time(); + start_time += delta; + + // Clamp to available range of media if present + const TimeRange available_range = item->available_range(); + if (!isEqual(available_range.duration().value(), 0.0)) + { + if (start_time < available_range.start_time()) + { + start_time = available_range.start_time(); + } + else if (start_time + range.duration() > + available_range.end_time_exclusive()) + { + // S---E (move <- source start time so that E matches) + // A-----E + const RationalTime end_diff = start_time + range.duration() - + available_range.end_time_exclusive(); + start_time -= end_diff; + } + } + + const TimeRange new_range(start_time, range.duration()); + item->set_source_range(new_range); +} + + +void slide( + Item* item, + RationalTime const& delta) +{ + Composition* composition = item->parent(); + if (!composition) + { + return; + } + + const int index = composition->index_of_child(item); + + // Exit early if we are at the first clip or if the delta is 0. + if (index <= 0 || delta.value() == 0.0) + { + return; + } + + auto children = composition->children(); + auto previous = dynamic_retainer_cast(children[index - 1]); + const TimeRange range = previous->trimmed_range(); + const TimeRange available_range = previous->available_range(); + RationalTime offset = delta; + + if (delta.value() < 0.0) + { + // Check we don't move left beyond the previous clip's duration + if (range.duration() <= -delta) + { + return; + } + } + else + { + // Check we don't move right beyond the previous clip's + // available duration + if (!isEqual(available_range.duration().value(), 0.0) && + range.duration() + delta > available_range.duration()) + { + offset = available_range.duration() - range.duration(); + } + } + + const otime::TimeRange new_range(range.start_time(), + range.duration() + offset); + previous->set_source_range(new_range); +} + +void ripple( + Item* item, + RationalTime const& delta_in, + RationalTime const& delta_out, + ErrorStatus* error_status) +{ + if (error_status) *error_status = ErrorStatus::OK; + + const TimeRange range = item->trimmed_range(); + RationalTime start_time = range.start_time(); + RationalTime end_time_exclusive = range.end_time_exclusive(); + if (delta_in.value() != 0.0) + { + RationalTime in_offset = delta_in; + if (delta_in < start_time) + { + in_offset = -start_time; + } + else if (start_time + delta_in > end_time_exclusive) + { + in_offset = delta_in - end_time_exclusive; + } + start_time += in_offset; + } + if (delta_out.value() != 0.0) + { + RationalTime out_offset = delta_out; + if (delta_out.value() > 0.0) + { + const TimeRange available_range = item->available_range(); + + // Check we don't move right beyond the clip's + // available duration + if (!(isEqual(available_range.duration().value(), 0.0)) && + range.duration() + delta_out > available_range.duration()) + { + out_offset = available_range.duration() - range.duration(); + } + } + + end_time_exclusive += out_offset; + } + const TimeRange new_range = + TimeRange::range_from_start_end_time(start_time, end_time_exclusive); + item->set_source_range(new_range); +} + +void +roll( + Item* item, + RationalTime const& delta_in, + RationalTime const& delta_out, + ErrorStatus* error_status) +{ + Composition* composition = item->parent(); + if (!composition) + { + if (error_status) + *error_status = ErrorStatus::NOT_A_CHILD_OF; + return; + } + auto children = composition->children(); + const int index = composition->index_of_child(item); + if (index < 0) + { + if (error_status) + *error_status = ErrorStatus::NOT_AN_ITEM; + return; + } + + const TimeRange range = item->trimmed_range(); + const TimeRange available_range = item->available_range(); + RationalTime start_time = range.start_time(); + RationalTime end_time_exclusive = range.end_time_exclusive(); + if (delta_in.value() != 0.0) + { + const RationalTime available_start_time = available_range.start_time(); + RationalTime in_offset = delta_in; + if (-in_offset > start_time) + in_offset = -start_time; + if (index > 0) + { + auto previous = dynamic_retainer_cast(children[index - 1]); + TimeRange previous_range = previous->trimmed_range(); + + // Clamp to previous clip's range first + RationalTime duration = previous_range.duration(); + if (duration < -in_offset) + { + duration -= RationalTime(1.0, duration.rate()); + in_offset -= duration; + } + previous_range = TimeRange(previous_range.start_time(), + previous_range.duration() + in_offset); + previous->set_source_range(previous_range); + } + start_time += in_offset; + + // If available range present, clamp to its start_time. + if (!(isEqual(available_range.duration().value(), 0.0))) + { + if (start_time < available_start_time) + start_time = available_start_time; + } + } + if (delta_out.value() != 0.0) + { + const size_t next_index = index + 1; + if (next_index < children.size()) + { + auto next = dynamic_retainer_cast(children[next_index]); + TimeRange next_range = next->trimmed_range(); + const TimeRange next_available_range = next->available_range(); + RationalTime next_start_time = next_range.start_time(); + RationalTime out_offset = delta_out; + + // If avalable range, clamp to it. + if (!(isEqual(available_range.duration().value(), 0.0))) + { + RationalTime available_start_time = + next_available_range.start_time(); + if (-out_offset > available_start_time) + out_offset = -available_start_time; + } + else + { + if (-out_offset > next_start_time) + out_offset = -next_start_time; + } + + end_time_exclusive += out_offset; + next_start_time += out_offset; + next_range = TimeRange(next_start_time, + next_range.duration()); + next->set_source_range(next_range); + } + } + const TimeRange new_range = + TimeRange::range_from_start_end_time(start_time, end_time_exclusive); + item->set_source_range(new_range); +} + +void +fill( + Item* item, + Composition* track, + RationalTime const& track_time, + ReferencePoint const reference_point, + ErrorStatus* error_status) +{ + // Find the gap to replace. + auto gap = dynamic_retainer_cast( + track->child_at_time(track_time, error_status, true)); + if (!gap) + { + if (error_status) + *error_status = ErrorStatus::NOT_A_GAP; + return; + } + + const TimeRange clip_range = item->trimmed_range(); + const TimeRange gap_range = gap->trimmed_range(); + TimeRange gap_track_range = track->trimmed_range_of_child(gap).value(); + RationalTime duration = clip_range.duration(); + + switch (reference_point) + { + case ReferencePoint::Sequence: { + RationalTime start_time = clip_range.start_time(); + const RationalTime gap_start_time = gap_range.start_time(); + auto track_item = dynamic_cast(item->clone()); + + // Check if start time is less than gap's start time (trim it if so) + if (start_time < gap_start_time) + { + duration -= gap_start_time - start_time; + start_time = gap_start_time; + } + + // Check if end time is longer (trim it if it is) + if (clip_range.end_time_exclusive() + > gap_range.end_time_exclusive()) + { + duration = gap_range.end_time_exclusive() - start_time; + } + const TimeRange new_clip_range(start_time, duration); + track_item->set_source_range(new_clip_range); + + if (duration + > gap_track_range.end_time_exclusive() - track_time) + { + duration = + gap_track_range.end_time_exclusive() - track_time; + } + + const TimeRange time_range(track_time, duration); + overwrite( + track_item, + track, + time_range, + true, + nullptr, + error_status); + return; + } + + case ReferencePoint::Fit: { + const double pct = gap_range.duration().to_seconds() + / duration.to_seconds(); + const std::string& name = item->name(); + LinearTimeWarp* timeWarp = + new LinearTimeWarp(name, name + "_timeWarp", pct); + auto& effects = item->effects(); + std::vector effectList; + for (auto& effect : effects) + effectList.push_back(effect); + effectList.push_back(timeWarp); + item = new Item(name, clip_range, AnyDictionary(), effectList); + const TimeRange time_range( + track_time, + gap_track_range.end_time_exclusive() - track_time); + overwrite(item, track, time_range, true, nullptr, error_status); + return; + } + + case ReferencePoint::Source: + default: { + const TimeRange time_range(track_time, duration); + overwrite(item, track, time_range, true, nullptr, error_status); + return; + } + } +} + +void remove( + Composition* composition, + RationalTime const& time, + bool const fill, + Item* fill_template, + ErrorStatus* error_status) +{ + auto item = dynamic_retainer_cast( + composition->child_at_time(time, error_status)); + if (!item) + { + if (error_status) + *error_status = ErrorStatus::NOT_AN_ITEM; + return; + } + + const int index = composition->index_of_child(item); + const TimeRange item_range = item->trimmed_range(); + composition->remove_child(index); + if (fill) + { + if (!fill_template) + fill_template = new Gap(item_range); + composition->insert_child(index, fill_template); + } +} + +}}} // namespace opentimelineio::OPENTIMELINEIO_VERSION::algo diff --git a/src/opentimelineio/algo/editAlgorithm.h b/src/opentimelineio/algo/editAlgorithm.h new file mode 100644 index 000000000..f973cb8f1 --- /dev/null +++ b/src/opentimelineio/algo/editAlgorithm.h @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the OpenTimelineIO project + +#pragma once + +#include "opentimelineio/composition.h" + +namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { namespace algo { + +//! Enum used by 3/4 Point Edit (aka. as fill) +enum class ReferencePoint +{ + Source, + Sequence, + Fit +}; + +// Overwrite an item or items. +// +// | A | B | -> |A| C |B| +// ^ ^ +// | C | +// +// item = item to overwrite (usually a clip) -- C in the diagram. +// composition = usually a track item. +// range = time range to overwrite. +// remove_transitions = whether to remove transitions within range. +// fill_template = item to fill in (usually a gap). +// +// If overwrite range starts after B's end, a gap hole is filled with +// fill_template and then then C is appended. +// +// If overwrite range starts before B's end and extends after, B is partitioned +// and C is appended at the end. +// +// If overwrite range starts before A and partially overlaps it, C is +// added at the beginning and A is partitioned. +// +// If overwrite range starts and ends before A, a gap hole is filled with +// fill_template. +void overwrite( + Item* item, + Composition* composition, + TimeRange const& range, + bool remove_transitions = true, + Item* fill_template = nullptr, + ErrorStatus* error_status = nullptr); + +// Insert an item. +// | A | B | -> | A | C | A | B | +// ^ +// | C | +// +// item = item to insert (usually a clip) +// composition = usually a track item. +// time = time to insert at. If < composition's start time, it will insert at 0 index. +// If > composition's end_time_exclusive, it will append at end. +// remove_transitions = whether to remove transitions that intersect time. +// fill_template = item to fill in (usually a gap), +// when time > composition's time. +// +// If A and B's length is L1 and C's length is L2, the end result is L1 + L2. +// A is split. +// +void insert( + Item* const item, + Composition* composition, + RationalTime const& time, + bool const remove_transitions = true, + Item* fill_template = nullptr, + ErrorStatus* error_status = nullptr); + +// +// Adjust a single item's start time or duration. +// +// | A | B | C | -> | A |FILL| B | C | +// <--* +// +// item = Item to apply trim to (usually a clip) +// delta_in = RationalTime that the item's source_range().start_time() +// will be adjusted by +// delta_out = RationalTime that the item's +// source_range().end_time_exclusive() will be adjusted by +// fill_template = item to fill in (usually a gap), +// when time > composition's time. +// +// Do not affect other clips. +// Fill now-"empty" time with gap or template +// Unless item is meeting a Gap, then, existing Gap's duration will be augmented +// +void trim( + Item* item, + RationalTime const& delta_in, + RationalTime const& delta_out, + Item* fill_template = nullptr, + ErrorStatus* error_status = nullptr); + +// Slice an item. +// +// | A | B | -> |A|A| B | +// ^ +// composition = usually a track item. +// time = time to slice at. +void slice( + Composition* composition, + RationalTime const& time, + bool const remove_transitions = true, + ErrorStatus* error_status = nullptr); + +// +// Slip an item start_time by + or -, clamping to available_range if available. +// +// | A | +// <-----> +// +// item = item to slip (usually a clip) +// delta = +/- rational time to slip the item by. +// +// Do not affect item duration. +// Do not affect surrounding items. +// Clamp to available_range of media (if available) +void slip(Item* item, RationalTime const& delta); + +// +// Slide an item start_time by + or -, adjusting the previous item's duration. +// Clamps previous item's duration to available_range if available. +// +// | A | B | C | -> | A | B | C | +// *---> +// +// item = item to slip (usually a clip) +// delta = +/- rational time to slide the item by. +// +// If item is the first clip, it does nothing. +// +void slide(Item* item, RationalTime const& delta); + +// +// Adjust a source_range without affecting any other items. +// +// | A | B | -> | A | B |FILL| +// <--* +// +// item = Item to apply ripple to (usually a clip) +// delta_in = RationalTime that the item's source_range().start_time() +// will be adjusted by +// delta_out = RationalTime that the item's +// source_range().end_time_exclusive() will be adjusted by +void ripple( + Item* item, + RationalTime const& delta_in, + RationalTime const& delta_out, + ErrorStatus* error_status = nullptr); + +// +// Any trim-like action results in adjacent items source_range being adjusted +// to fit. +// No new items are ever created. +// Clamped to available media (if available) +// Start time in parent of Item before input item will never change +// End time in parent of Item after input item will never change +// +// | A | B | -> | A | B | +// <--* +// +// item = Item to apply roll to (usually a clip) +// delta_in = RationalTime that the item's source_range().start_time() +// will be adjusted by +// delta_out = RationalTime that the item's +// source_range().end_time_exclusive() will be adjusted by +void roll( + Item* item, + RationalTime const& delta_in, + RationalTime const& delta_out, + ErrorStatus* error_status = nullptr); + +// Create a 3/4 Point Edit or Fill. +// +// | A |GAP| B | -> | A | C | B | +// ^ ^ +// C--| C |--C +// +// item = item to place onto the track (usually a clip) +// track = track that will now own this item. +// track_time = RationalTime +// reference_point = For 4 point editing, the reference point dictates what +// transform to use when running the fill. +// +void fill( + Item* item, + Composition* track, + RationalTime const& track_time, + ReferencePoint const reference_point = ReferencePoint::Source, + ErrorStatus* error_status = nullptr); + +// +// Remove item(s) at a time and fill them, optionally with a gap. +// +// | A | C | B | -> | A |GAP| B | +// ^ +// | +// +// track = track to remove item from. +// time = RationalTime +// fill = whether to fill the hole with fill_template. +// fill_template = if nullptr, use a gap to fill the hole. +// +// if fill is not set, A and B become concatenated, with no fill. +// +void remove( + Composition* composition, + RationalTime const& time, + bool const fill = true, + Item* fill_template = nullptr, + ErrorStatus* error_status = nullptr); + +}}} // namespace opentimelineio::OPENTIMELINEIO_VERSION::algo diff --git a/src/opentimelineio/composition.cpp b/src/opentimelineio/composition.cpp index a9920f5c3..cd9811d31 100644 --- a/src/opentimelineio/composition.cpp +++ b/src/opentimelineio/composition.cpp @@ -165,6 +165,26 @@ Composition::remove_child(int index, ErrorStatus* error_status) return true; } +int +Composition::index_of_child(Composable const* child, ErrorStatus* error_status) + const +{ + for (size_t i = 0; i < _children.size(); i++) + { + if (_children[i] == child) + { + return int(i); + } + } + + if (error_status) + { + *error_status = ErrorStatus::NOT_A_CHILD_OF; + error_status->object_details = this; + } + return -1; +} + bool Composition::read_from(Reader& reader) { @@ -216,26 +236,6 @@ Composition::handles_of_child( return std::make_pair(optional(), optional()); } -int -Composition::_index_of_child(Composable const* child, ErrorStatus* error_status) - const -{ - for (size_t i = 0; i < _children.size(); i++) - { - if (_children[i] == child) - { - return int(i); - } - } - - if (error_status) - { - *error_status = ErrorStatus::NOT_A_CHILD_OF; - error_status->object_details = this; - } - return -1; -} - std::vector Composition::_path_from_child( Composable const* child, @@ -313,7 +313,7 @@ Composition::range_of_child(Composable const* child, ErrorStatus* error_status) assert(!parents.empty()); for (auto parent: parents) { - auto index = parent->_index_of_child(current, error_status); + const int index = parent->index_of_child(current, error_status); if (is_error(error_status)) { return TimeRange(); @@ -364,7 +364,7 @@ Composition::trimmed_range_of_child( assert(!parents.empty()); for (auto parent: parents) { - auto index = parent->_index_of_child(current, error_status); + const int index = parent->index_of_child(current, error_status); if (is_error(error_status)) { return TimeRange(); diff --git a/src/opentimelineio/composition.h b/src/opentimelineio/composition.h index 755280a72..814e4d05c 100644 --- a/src/opentimelineio/composition.h +++ b/src/opentimelineio/composition.h @@ -57,6 +57,10 @@ class Composition : public Item return insert_child(int(_children.size()), child, error_status); } + int index_of_child( + Composable const* child, + ErrorStatus* error_status = nullptr) const; + bool is_parent_of(Composable const* other) const; virtual std::pair, optional> @@ -118,9 +122,6 @@ class Composition : public Item bool read_from(Reader&) override; void write_to(Writer&) const override; - int _index_of_child( - Composable const* child, - ErrorStatus* error_status = nullptr) const; std::vector _path_from_child( Composable const* child, ErrorStatus* error_status = nullptr) const; diff --git a/src/opentimelineio/errorStatus.cpp b/src/opentimelineio/errorStatus.cpp index 96c7f8ea9..e19f18e77 100644 --- a/src/opentimelineio/errorStatus.cpp +++ b/src/opentimelineio/errorStatus.cpp @@ -64,6 +64,8 @@ ErrorStatus::outcome_to_string(Outcome o) return "active key not found in media references"; case MEDIA_REFERENCES_CONTAIN_EMPTY_KEY: return "the media references cannot contain an empty key"; + case NOT_A_GAP: + return "object is not descendent of Gap type"; default: return "unknown/illegal ErrorStatus::Outcome code"; }; diff --git a/src/opentimelineio/errorStatus.h b/src/opentimelineio/errorStatus.h index 0bbc82b68..67a8059fd 100644 --- a/src/opentimelineio/errorStatus.h +++ b/src/opentimelineio/errorStatus.h @@ -41,7 +41,8 @@ struct ErrorStatus OBJECT_CYCLE, CANNOT_COMPUTE_BOUNDS, MEDIA_REFERENCES_DO_NOT_CONTAIN_ACTIVE_KEY, - MEDIA_REFERENCES_CONTAIN_EMPTY_KEY + MEDIA_REFERENCES_CONTAIN_EMPTY_KEY, + NOT_A_GAP }; ErrorStatus() diff --git a/src/opentimelineio/track.cpp b/src/opentimelineio/track.cpp index 128776244..d6a1c782a 100644 --- a/src/opentimelineio/track.cpp +++ b/src/opentimelineio/track.cpp @@ -166,7 +166,7 @@ Track::neighbors_of( std::pair, Retainer> result{ nullptr, nullptr }; - auto index = _index_of_child(item, error_status); + const int index = index_of_child(item, error_status); if (is_error(error_status)) { return result; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 00e545e6c..e206fb8cf 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,7 +15,7 @@ foreach(test ${tests_opentime}) WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) endforeach() -list(APPEND tests_opentimelineio test_clip test_serialization test_serializableCollection test_timeline test_track) +list(APPEND tests_opentimelineio test_clip test_serialization test_serializableCollection test_timeline test_track test_editAlgorithm) foreach(test ${tests_opentimelineio}) add_executable(${test} utils.h utils.cpp ${test}.cpp) diff --git a/tests/test_editAlgorithm.cpp b/tests/test_editAlgorithm.cpp new file mode 100644 index 000000000..7abbb9758 --- /dev/null +++ b/tests/test_editAlgorithm.cpp @@ -0,0 +1,3088 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the OpenTimelineIO project + +#include "utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +// Uncomment this for debugging output +#define DEBUG + + +namespace otime = opentime::OPENTIME_VERSION; +namespace otio = opentimelineio::OPENTIMELINEIO_VERSION; + +using otime::RationalTime; +using otime::TimeRange; +using otio::algo::ReferencePoint; + +#ifdef DEBUG + +#include + + +std::ostream& operator << (std::ostream& os, const RationalTime& value) +{ + os << std::fixed << value.value() << "/" << value.rate(); + return os; +} + +std::ostream& operator << (std::ostream& os, const TimeRange& value) +{ + os << std::fixed << value.start_time().value() << "/" << + value.duration().value() << "/" << + value.duration().rate(); + return os; +} + +#endif + +namespace { + +void +assert_duration(const RationalTime& new_duration, const RationalTime& duration) +{ +#ifdef DEBUG + std::cout << "\tnew duration=" << new_duration << " old duration=" << duration + << std::endl; +#endif + assertEqual(new_duration, duration); +} + +void +debug_track_ranges(const std::string& title, otio::Track* track) +{ +#ifdef DEBUG + std::cout << "\t" << title << " TRACK RANGES" << std::endl; + for (const auto& child: track->children()) + { + auto clip = otio::dynamic_retainer_cast(child); + if (clip) + { + auto range = track->trimmed_range_of_child(child).value(); + std::cout << "\t\t" << clip->name() << " " << range + << std::endl; + } + auto gap = otio::dynamic_retainer_cast(child); + if (gap) + { + auto range = track->trimmed_range_of_child(child).value(); + std::string name = gap->name(); + if (name.empty()) name = "gap"; + std::cout << "\t\t" << name << " " << range << std::endl; + } + auto transition = otio::dynamic_retainer_cast(child); + if (transition) + { + auto range = track->trimmed_range_of_child(child).value(); + std::string name = transition->name(); + if (name.empty()) name = "transition"; + std::cout << "\t\t" << name << " " << range << std::endl; + } + } + std::cout << "\t" << title << " TRACK RANGES END" << std::endl; +#endif +} + +void +debug_clip_ranges(const std::string& title, otio::Track* track) +{ +#ifdef DEBUG + std::cout << "\t" << title << " CLIP TRIMMED RANGES" << std::endl; + for (const auto& child: track->children()) + { + auto clip = otio::dynamic_retainer_cast(child); + if (clip) + { + auto range = clip->trimmed_range(); + std::cout << "\t\t" << clip->name() << " " << range << std::endl; + } + auto gap = otio::dynamic_retainer_cast(child); + if (gap) + { + auto range = gap->trimmed_range(); + std::string name = gap->name(); + if (name.empty()) name = "gap"; + std::cout << "\t\t" << name << " " << range << std::endl; + } + } + std::cout << "\t" << title << " CLIP TRIMMED RANGES END" << std::endl; +#endif +} + +void +assert_clip_ranges( + otio::Track* track, + const std::vector& expected_ranges) +{ + std::vector ranges; + size_t children = 0; + for (const auto& child: track->children()) + { + auto item = otio::dynamic_retainer_cast(child); + if (item) + { + ranges.push_back(item->trimmed_range()); + ++children; + } + } + debug_clip_ranges("TEST", track); + assertEqual(children, expected_ranges.size()); + assertEqual(expected_ranges, ranges); +} + +void +assert_track_ranges( + otio::Track* track, + const std::vector& expected_ranges) +{ + std::vector ranges; + size_t children = 0; + for (const auto& child: track->children()) + { + auto item = otio::dynamic_retainer_cast(child); + if (item) + { + ranges.push_back(track->trimmed_range_of_child(child).value()); + ++children; + } + } + debug_track_ranges("TEST", track); + assertEqual(children, ranges.size()); + assertEqual(expected_ranges, ranges); +} + +void +test_edit_slice( + const TimeRange& clip_range, + const RationalTime& slice_time, + const std::vector& slice_ranges) +{ + // Create a track with one clip. + otio::SerializableObject::Retainer clip_0 = + new otio::Clip("clip_0", nullptr, clip_range); + otio::SerializableObject::Retainer track = new otio::Track(); + track->append_child(clip_0); + + debug_track_ranges("START", track); + + // Slice. + otio::algo::slice(track, slice_time); + + // Asserts. + assert_track_ranges(track, slice_ranges); +} + + +void +test_edit_slice_transitions( + const RationalTime& slice_time, + const std::vector& slice_ranges) +{ + // Create a track with two clips and one transition. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + TimeRange(RationalTime(0.0, 24.0), RationalTime(50.0, 24.0))); + otio::SerializableObject::Retainer clip_2 = new otio::Clip( + "clip_2", + nullptr, + TimeRange(RationalTime(0.0, 24.0), RationalTime(30.0, 24.0))); + otio::SerializableObject::Retainer clip_3 = new otio::Clip( + "clip_3", + nullptr, + TimeRange(RationalTime(0.0, 24.0), RationalTime(25.0, 24.0))); + otio::SerializableObject::Retainer transition_0 = + new otio::Transition( + "transition_0", + otio::Transition::Type::SMPTE_Dissolve, + RationalTime(5.0, 24.0), + RationalTime(3.0, 24.0)); + otio::SerializableObject::Retainer transition_1 = + new otio::Transition( + "transition_1", + otio::Transition::Type::SMPTE_Dissolve, + RationalTime(5.0, 24.0), + RationalTime(3.0, 24.0)); + otio::SerializableObject::Retainer track = new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + track->insert_child(1, transition_0); + track->append_child(clip_2); + track->append_child(clip_3); + track->append_child(transition_1); + + debug_track_ranges("START", track); + + // Slice. + otio::algo::slice(track, slice_time); + + // Asserts. + assert_track_ranges(track, slice_ranges); +} + +void +test_edit_slip( + const TimeRange& media_range, + const TimeRange& clip_range, + const RationalTime& slip_time, + const TimeRange slip_range) +{ + // Create one clip with one media. + otio::SerializableObject::Retainer + media_0 = new otio::MediaReference( + "media_0", + media_range); + otio::SerializableObject::Retainer clip_0 = + new otio::Clip("clip_0", media_0, clip_range); + + // Slip. + otio::algo::slip(clip_0, slip_time); + + // Asserts. + const otio::TimeRange& range = clip_0->trimmed_range(); + assertEqual(slip_range, range); +} + + +void +test_edit_slide( + const TimeRange& media_range, + const RationalTime& slide_time, + const std::vector& slide_ranges) +{ + // Create a track with three clips. + otio::SerializableObject::Retainer + media_0 = new otio::MediaReference( + "media_0", + media_range); + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + media_0, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(30.0, 24.0))); + otio::SerializableObject::Retainer clip_2 = new otio::Clip( + "clip_2", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(40.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + track->append_child(clip_2); + + // Slide. + otio::algo::slide(clip_1, slide_time); + + // Asserts. + assert_track_ranges(track, slide_ranges); +} + +void test_edit_ripple( + const RationalTime& delta_in, + const RationalTime& delta_out, + const std::vector& track_ranges, + const std::vector& item_ranges + ) +{ + // Create a track with one gap and two clips. + otio::SerializableObject::Retainer clip_0 = new otio::Gap( + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(20.0, 24.0)), + "gap_0"); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(5.0, 24.0), + otio::RationalTime(25.0, 24.0))); + otio::SerializableObject::Retainer clip_2 = new otio::Clip( + "clip_2", + nullptr, + otio::TimeRange( + otio::RationalTime(5.0, 24.0), + otio::RationalTime(20.0, 24.0))); + otio::SerializableObject::Retainer track = new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + track->append_child(clip_2); + + otio::ErrorStatus error_status; + otio::algo::ripple( + clip_1, + delta_in, + delta_out, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + assert_track_ranges(track, track_ranges); + assert_clip_ranges(track, item_ranges); +} + +void test_edit_roll( + const RationalTime& delta_in, + const RationalTime& delta_out, + const std::vector& track_ranges, + const std::vector& item_ranges + ) +{ + // Create a track with one gap and two clips. + otio::SerializableObject::Retainer clip_0 = new otio::Gap( + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(20.0, 24.0)), + "gap_0"); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(5.0, 24.0), + otio::RationalTime(30.0, 24.0))); + otio::SerializableObject::Retainer clip_2 = new otio::Clip( + "clip_2", + nullptr, + otio::TimeRange( + otio::RationalTime(5.0, 24.0), + otio::RationalTime(20.0, 24.0))); + otio::SerializableObject::Retainer track = new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + track->append_child(clip_2); + + otio::ErrorStatus error_status; + otio::algo::roll( + clip_1, + delta_in, + delta_out, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + assert_track_ranges(track, track_ranges); + assert_clip_ranges(track, item_ranges); +} + +void test_edit_fill( + const TimeRange& clip_range, + const RationalTime& track_time, + const ReferencePoint& reference_point, + const std::vector& track_ranges, + const std::vector& item_ranges + ) +{ + // Create a track with one gap and two clips. We leave one clip for fill. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(20.0, 24.0))); + otio::SerializableObject::Retainer clip_1 = new otio::Gap( + otio::TimeRange( + otio::RationalTime(5.0, 24.0), + otio::RationalTime(30.0, 24.0)), + "gap_0"); + otio::SerializableObject::Retainer clip_2 = new otio::Clip( + "clip_2", + nullptr, + otio::TimeRange( + otio::RationalTime(5.0, 24.0), + otio::RationalTime(20.0, 24.0))); + + otio::SerializableObject::Retainer clip_3 = new otio::Clip( + "fill_0", + nullptr, + clip_range); + + otio::SerializableObject::Retainer track = new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + track->append_child(clip_2); + + auto duration = track->duration(); + + otio::ErrorStatus error_status; + otio::algo::fill( + clip_3, + track, + track_time, + reference_point, + &error_status); + + auto new_duration = track->duration(); + + // Asserts. + if (reference_point == ReferencePoint::Sequence) + { + assert_duration(new_duration, duration); + } + assert(!otio::is_error(error_status)); + assert_track_ranges(track, track_ranges); + assert_clip_ranges(track, item_ranges); +} + +} // namespace + +int +main(int argc, char** argv) +{ + Tests tests; + + tests.add_test("test_edit_slice_1", [] { + // Slice in the middle. + test_edit_slice( + TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)), + RationalTime(12.0, 24.0), + { TimeRange(RationalTime(0.0, 24.0), RationalTime(12.0, 24.0)), + TimeRange(RationalTime(12.0, 24.0), RationalTime(12.0, 24.0)) }); + + // Slice at the beginning. + test_edit_slice( + TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)), + RationalTime(0.0, 24.0), + { TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)) }); + + // Slice near the beginning. + test_edit_slice( + TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)), + RationalTime(1.0, 24.0), + { TimeRange(RationalTime(0.0, 24.0), RationalTime(1.0, 24.0)), + TimeRange(RationalTime(1.0, 24.0), RationalTime(23.0, 24.0)) }); + + // Slice near the end. + test_edit_slice( + TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)), + RationalTime(23.0, 24.0), + { TimeRange(RationalTime(0.0, 24.0), RationalTime(23.0, 24.0)), + TimeRange(RationalTime(23.0, 24.0), RationalTime(1.0, 24.0)) }); + + // Slice at the end. + test_edit_slice( + TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)), + RationalTime(24.0, 24.0), + { TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)) }); + + }); + + tests.add_test("test_edit_slice_2", [] { + // Create a track with three clips of different rates + // Slice the clips several times at different points. + // Delete an item and slice again at same point. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 23.98), + otio::RationalTime(71.94, 23.98))); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 23.98), + otio::RationalTime(71.94, 23.98))); + otio::SerializableObject::Retainer clip_2 = new otio::Clip( + "clip_2", + nullptr, + otio::TimeRange( + otio::RationalTime(90.0, 30.0), + otio::RationalTime(90.0, 30.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + track->append_child(clip_2); + + // Slice. + otio::ErrorStatus error_status; + otio::algo::slice( + track, + RationalTime(121.0, 30.0), + true, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 23.98), + RationalTime(71.94, 23.98)), + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(31.0, 30.0)), + TimeRange( + RationalTime(31.0, 30.0), + RationalTime(59, 30.0)), + TimeRange( + RationalTime(90.0, 30.0), + RationalTime(90.0, 30.0)) + }); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 23.98), + RationalTime(71.94, 23.98)), + TimeRange( + RationalTime(90.0, 30.0), + RationalTime(31.0, 30.0)), + TimeRange( + RationalTime(121.0, 30.0), + RationalTime(59, 30.0)), + TimeRange( + RationalTime(180.0, 30.0), + RationalTime(90.0, 30.0)) + }); + + otio::algo::slice( + track, + RationalTime(122.0, 30.0), + true, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 23.98), + RationalTime(71.94, 23.98)), + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(31.0, 30.0)), + TimeRange( + RationalTime(31.0, 30.0), + RationalTime(1, 30.0)), + TimeRange( + RationalTime(32.0, 30.0), + RationalTime(58, 30.0)), + TimeRange( + RationalTime(90.0, 30.0), + RationalTime(90.0, 30.0)) + }); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 23.98), + RationalTime(71.94, 23.98)), + TimeRange( + RationalTime(90.0, 30.0), + RationalTime(31.0, 30.0)), + TimeRange( + RationalTime(121.0, 30.0), + RationalTime(1.0, 30.0)), + TimeRange( + RationalTime(122.0, 30.0), + RationalTime(58, 30.0)), + TimeRange( + RationalTime(180.0, 30.0), + RationalTime(90.0, 30.0)) + }); + + track->remove_child(2); // Delete the 1 frame item + + // Asserts. + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 23.98), + RationalTime(71.94, 23.98)), + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(31.0, 30.0)), + TimeRange( + RationalTime(32.0, 30.0), + RationalTime(58, 30.0)), + TimeRange( + RationalTime(90.0, 30.0), + RationalTime(90.0, 30.0)) + }); + + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 23.98), + RationalTime(71.94, 23.98)), + TimeRange( + RationalTime(90.0, 30.0), + RationalTime(31.0, 30.0)), + TimeRange( + RationalTime(121.0, 30.0), + RationalTime(58, 30.0)), + TimeRange( + RationalTime(179.0, 30.0), + RationalTime(90.0, 30.0)) + }); + + // Slice again at the same points (this slice does nothing at it is at + // start point). + otio::algo::slice( + track, + RationalTime(121.0, 30.0), + true, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 23.98), + RationalTime(71.94, 23.98)), + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(31.0, 30.0)), + TimeRange( + RationalTime(32.0, 30.0), + RationalTime(58, 30.0)), + TimeRange( + RationalTime(90.0, 30.0), + RationalTime(90.0, 30.0)) + }); + + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 23.98), + RationalTime(71.94, 23.98)), + TimeRange( + RationalTime(90.0, 30.0), + RationalTime(31.0, 30.0)), + TimeRange( + RationalTime(121.0, 30.0), + RationalTime(58, 30.0)), + TimeRange( + RationalTime(179.0, 30.0), + RationalTime(90.0, 30.0)) + }); + + // Slice again for one frame + otio::algo::slice( + track, + RationalTime(122.0, 30.0), + true, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 23.98), + RationalTime(71.94, 23.98)), + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(31.0, 30.0)), + TimeRange( + RationalTime(32.0, 30.0), + RationalTime(1, 30.0)), + TimeRange( + RationalTime(33.0, 30.0), + RationalTime(57, 30.0)), + TimeRange( + RationalTime(90.0, 30.0), + RationalTime(90.0, 30.0)) + }); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 23.98), + RationalTime(71.94, 23.98)), + TimeRange( + RationalTime(90.0, 30.0), + RationalTime(31.0, 30.0)), + TimeRange( + RationalTime(121.0, 30.0), + RationalTime(1.0, 30.0)), + TimeRange( + RationalTime(122.0, 30.0), + RationalTime(57, 30.0)), + TimeRange( + RationalTime(179.0, 30.0), + RationalTime(90.0, 30.0)) + }); + + // Slice again for one frame + otio::algo::slice( + track, + RationalTime(179.0, 30.0), + true, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 23.98), + RationalTime(71.94, 23.98)), + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(31.0, 30.0)), + TimeRange( + RationalTime(32.0, 30.0), + RationalTime(1, 30.0)), + TimeRange( + RationalTime(33.0, 30.0), + RationalTime(57, 30.0)), + TimeRange( + RationalTime(90.0, 30.0), + RationalTime(90.0, 30.0)) + }); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 23.98), + RationalTime(71.94, 23.98)), + TimeRange( + RationalTime(90.0, 30.0), + RationalTime(31.0, 30.0)), + TimeRange( + RationalTime(121.0, 30.0), + RationalTime(1.0, 30.0)), + TimeRange( + RationalTime(122.0, 30.0), + RationalTime(57, 30.0)), + TimeRange( + RationalTime(179.0, 30.0), + RationalTime(90.0, 30.0)) + }); + }); + + + tests.add_test("test_edit_slice_transitions_1", [] { + + // Four clips with two transitions. + test_edit_slice_transitions( + RationalTime(24.0, 24.0), + { TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)), + TimeRange(RationalTime(24.0, 24.0), RationalTime(50.0, 24.0)), + TimeRange(RationalTime(74.0, 24.0), RationalTime(30.0, 24.0)), + TimeRange(RationalTime(104.0, 24.0), RationalTime(25.0, 24.0)) }); + + test_edit_slice_transitions( + RationalTime(23.0, 24.0), + { TimeRange(RationalTime(0.0, 24.0), RationalTime(23.0, 24.0)), + TimeRange(RationalTime(23.0, 24.0), RationalTime(1.0, 24.0)), + TimeRange(RationalTime(24.0, 24.0), RationalTime(50.0, 24.0)), + TimeRange(RationalTime(74.0, 24.0), RationalTime(30.0, 24.0)), + TimeRange(RationalTime(104.0, 24.0), RationalTime(25.0, 24.0)) }); + + }); + + tests.add_test("test_edit_overwrite_0", [] { + // Create a track with one clip. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + + // Overwrite past the clip. + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::ErrorStatus error_status; + otio::algo::overwrite( + clip_1, + track, + TimeRange(RationalTime(48.0, 24.0), RationalTime(24.0, 24.0)), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + assertEqual(track->children().size(), 3); + assert(otio::dynamic_retainer_cast(track->children()[1])); + const RationalTime duration = track->duration(); + assert(duration == otio::RationalTime(72.0, 24.0)); + auto range = clip_1->trimmed_range_in_parent().value(); + assertEqual( + range, + otio::TimeRange( + otio::RationalTime(48.0, 24.0), + otio::RationalTime(24.0, 24.0))); + }); + + tests.add_test("test_edit_overwrite_1", [] { + // Create a track with one clip. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + + // Overwrite past the clip. + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::ErrorStatus error_status; + otio::algo::overwrite( + clip_1, + track, + TimeRange(RationalTime(48.0, 24.0), RationalTime(24.0, 24.0)), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + assertEqual(track->children().size(), 3); + assert(otio::dynamic_retainer_cast(track->children()[1])); + const RationalTime duration = track->duration(); + assert(duration == otio::RationalTime(72.0, 24.0)); + auto range = clip_1->trimmed_range_in_parent().value(); + assertEqual( + range, + otio::TimeRange( + otio::RationalTime(48.0, 24.0), + otio::RationalTime(24.0, 24.0))); + }); + + tests.add_test("test_edit_overwrite_2", [] { + // Create a track with one clip. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(1.0, 24.0), + otio::RationalTime(100.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + + // Overwrite a single frame inside the clip. + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(1.0, 24.0), + otio::RationalTime(1.0, 24.0))); + otio::ErrorStatus error_status; + otio::algo::overwrite( + clip_1, + track, + TimeRange(RationalTime(42.0, 24.0), RationalTime(1.0, 24.0)), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + const RationalTime duration = track->duration(); + assert(duration == otio::RationalTime(100.0, 24.0)); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(1.0, 24.0), + RationalTime(42.0, 24.0)), + TimeRange( + RationalTime(1.0, 24.0), + RationalTime(1.0, 24.0)), + TimeRange( + RationalTime(44.0, 24.0), + RationalTime(57.0, 24.0)) + }); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(42.0, 24.0)), + TimeRange( + RationalTime(42.0, 24.0), + RationalTime(1.0, 24.0)), + TimeRange( + RationalTime(43.0, 24.0), + RationalTime(57.0, 24.0)) + }); + }); + + tests.add_test("test_edit_overwrite_3", [] { + // Create a track with two clips and overwrite a portion of both. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + + // Overwrite both clips. + otio::SerializableObject::Retainer clip_2 = new otio::Clip( + "clip_2", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + const RationalTime duration = track->duration(); + otio::ErrorStatus error_status; + otio::algo::overwrite( + clip_2, + track, + TimeRange(RationalTime(12.0, 24.0), RationalTime(24.0, 24.0)), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + const RationalTime new_duration = track->duration(); + assert_duration(new_duration, duration); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(12.0, 24.0)), + TimeRange( + RationalTime(12.0, 24.0), + RationalTime(24.0, 24.0)), + TimeRange( + RationalTime(36.0, 24.0), + RationalTime(12.0, 24.0)) + }); + }); + + + tests.add_test("test_edit_overwrite_4", [] { + // Create a track with one long clip. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(704.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + + // Overwrite one portion of the clip. + otio::SerializableObject::Retainer over_1 = new otio::Clip( + "over_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(1.0, 24.0))); + const RationalTime duration = track->duration(); + otio::ErrorStatus error_status; + otio::algo::overwrite( + over_1, + track, + TimeRange(RationalTime(272.0, 24.0), RationalTime(1.0, 24.0)), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + const RationalTime new_duration = track->duration(); + assert_duration(new_duration, duration); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(272.0, 24.0)), + TimeRange( + RationalTime(272.0, 24.0), + RationalTime(1.0, 24.0)), + TimeRange( + RationalTime(273.0, 24.0), + RationalTime(431.0, 24.0)) + }); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(272.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(1.0, 24.0)), + TimeRange( + RationalTime(273.0, 24.0), + RationalTime(431.0, 24.0)) + }); + }); + + tests.add_test("test_edit_overwrite_5", [] { + // Create a track with one long clip. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 30.0), + otio::RationalTime(704.0, 30.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + + // Overwrite one portion of the clip. + otio::SerializableObject::Retainer over_1 = new otio::Clip( + "over_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 30.0), + otio::RationalTime(1.0, 30.0))); + const RationalTime duration = track->duration(); + otio::ErrorStatus error_status; + otio::algo::overwrite( + over_1, + track, + TimeRange(RationalTime(272.0, 30.0), RationalTime(1.0, 30.0)), + true, + nullptr, + &error_status); + assert(!otio::is_error(error_status)); + { + const RationalTime new_duration = track->duration(); + assert_duration(new_duration, duration); + } + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(272.0, 30.0)), + TimeRange( + RationalTime(272.0, 30.0), + RationalTime(1.0, 30.0)), + TimeRange( + RationalTime(273.0, 30.0), + RationalTime(431.0, 30.0)) + }); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(272.0, 30.0)), + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(1.0, 30.0)), + TimeRange( + RationalTime(273.0, 30.0), + RationalTime(431.0, 30.0)) + }); + + // Overwrite another portion of the clip. + otio::SerializableObject::Retainer over_2 = new otio::Clip( + "over_2", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 30.0), + otio::RationalTime(1.0, 30.0))); + + otio::algo::overwrite( + over_2, + track, + TimeRange(RationalTime(360.0, 30.0), RationalTime(1.0, 30.0)), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + { + const RationalTime new_duration = track->duration(); + assert_duration(new_duration, duration); + } + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(272.0, 30.0)), + TimeRange( + RationalTime(272.0, 30.0), + RationalTime(1.0, 30.0)), + TimeRange( + RationalTime(273.0, 30.0), + RationalTime(87.0, 30.0)), + TimeRange( + RationalTime(360.0, 30.0), + RationalTime(1.0, 30.0)), + TimeRange( + RationalTime(361.0, 30.0), + RationalTime(343.0, 30.0)) + }); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(272.0, 30.0)), + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(1.0, 30.0)), + TimeRange( + RationalTime(273.0, 30.0), + RationalTime(87.0, 30.0)), + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(1.0, 30.0)), + TimeRange( + RationalTime(361.0, 30.0), + RationalTime(343.0, 30.0)) + }); + + // Overwrite the same portion of the clip. + otio::SerializableObject::Retainer over_3 = new otio::Clip( + "over_3", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 30.0), + otio::RationalTime(1.0, 30.0))); + + otio::algo::overwrite( + over_3, + track, + TimeRange(RationalTime(360.0, 30.0), RationalTime(1.0, 30.0)), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + { + const RationalTime new_duration = track->duration(); + assert_duration(new_duration, duration); + } + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(272.0, 30.0)), + TimeRange( + RationalTime(272.0, 30.0), + RationalTime(1.0, 30.0)), + TimeRange( + RationalTime(273.0, 30.0), + RationalTime(87.0, 30.0)), + TimeRange( + RationalTime(360.0, 30.0), + RationalTime(1.0, 30.0)), + TimeRange( + RationalTime(361.0, 30.0), + RationalTime(343.0, 30.0)) + }); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(272.0, 30.0)), + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(1.0, 30.0)), + TimeRange( + RationalTime(273.0, 30.0), + RationalTime(87.0, 30.0)), + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(1.0, 30.0)), + TimeRange( + RationalTime(361.0, 30.0), + RationalTime(343.0, 30.0)) + }); + }); + + tests.add_test("test_edit_overwrite_6", [] { + // Create a track with three clips of different rates. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 23.98), + otio::RationalTime(71.94, 23.98))); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 23.98), + otio::RationalTime(71.94, 23.98))); + otio::SerializableObject::Retainer clip_2 = new otio::Clip( + "clip_2", + nullptr, + otio::TimeRange( + otio::RationalTime(90.0, 30), + otio::RationalTime(90.0, 30))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + track->append_child(clip_2); + + // Overwrite one portion of the clip. + otio::SerializableObject::Retainer over_1 = new otio::Clip( + "over_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 30.0), + otio::RationalTime(1.0, 30.0))); + + const RationalTime duration = track->duration(); + otio::ErrorStatus error_status; + otio::algo::overwrite( + over_1, + track, + TimeRange(RationalTime(137.0, 30.0), RationalTime(1.0, 30.0)), + true, + nullptr, + &error_status); + + assert(!otio::is_error(error_status)); + const RationalTime new_duration = track->duration(); + assert_duration(new_duration, duration); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 23.98), + RationalTime(71.94, 23.98)), + TimeRange( + RationalTime(90, 30.0), + RationalTime(47.0, 30.0)), + TimeRange( + RationalTime(137.0, 30.0), + RationalTime(1.0, 30.0)), + TimeRange( + RationalTime(138.0, 30.0), + RationalTime(42.0, 30.0)), + TimeRange( + RationalTime(180.0, 30.0), + RationalTime(90.0, 30.0)) + }); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 23.98), + RationalTime(71.94, 23.98)), + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(47.0, 30.0)), + TimeRange( + RationalTime(0.0, 30.0), + RationalTime(1.0, 30.0)), + TimeRange( + RationalTime(48.0, 30.0), + RationalTime(42.0, 30.0)), + TimeRange( + RationalTime(90.0, 30.0), + RationalTime(90.0, 30.0)) + }); + }); + + + tests.add_test("test_edit_overwrite_7", [] { + // Create a track with one long clip. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(704.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + + // Overwrite past the clip, creating a gap. + otio::SerializableObject::Retainer over_1 = new otio::Clip( + "over_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(1.0, 24.0))); + const RationalTime duration = track->duration(); + otio::ErrorStatus error_status; + otio::algo::overwrite( + over_1, + track, + TimeRange(RationalTime(800.0, 24.0), RationalTime(1.0, 24.0)), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + const RationalTime new_duration = track->duration(); + assert_duration(new_duration, RationalTime(801.0, 24.0)); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(704.0, 24.0)), + TimeRange( + RationalTime(704.0, 24.0), + RationalTime(96.0, 24.0)), + TimeRange( + RationalTime(800.0, 24.0), + RationalTime(1.0, 24.0)) + }); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(704.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(96.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(1.0, 24.0)) + }); + }); + + + tests.add_test("test_edit_overwrite_8", [] { + // Create a track with one long clip. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(704.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + + // Overwrite before the clip, creating a gap. + otio::SerializableObject::Retainer over_1 = new otio::Clip( + "over_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(1.0, 24.0))); + const RationalTime duration = track->duration(); + otio::ErrorStatus error_status; + otio::algo::overwrite( + over_1, + track, + TimeRange(RationalTime(-30.0, 24.0), RationalTime(1.0, 24.0)), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + const RationalTime new_duration = track->duration(); + assert_duration(new_duration, RationalTime(734.0, 24.0)); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(1.0, 24.0)), + TimeRange( + RationalTime(1.0, 24.0), + RationalTime(29.0, 24.0)), + TimeRange( + RationalTime(30.0, 24.0), + RationalTime(704.0, 24.0)) + }); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(1.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(29.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(704.0, 24.0)) + }); + }); + + + + tests.add_test("test_edit_overwrite_9", [] { + // Create a track with one long clip. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(704.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + + // Overwrite before the clip, creating a gap. + otio::SerializableObject::Retainer over_1 = new otio::Clip( + "over_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(100.0, 24.0))); + const RationalTime duration = track->duration(); + otio::ErrorStatus error_status; + otio::algo::overwrite( + over_1, + track, + TimeRange(RationalTime(-30.0, 24.0), RationalTime(70.0, 24.0)), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + const RationalTime new_duration = track->duration(); + assert_duration(new_duration, duration); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(70.0, 24.0)), + TimeRange( + RationalTime(70.0, 24.0), + RationalTime(634.0, 24.0)) + }); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(70.0, 24.0)), + TimeRange( + RationalTime(70.0, 24.0), + RationalTime(634.0, 24.0)) + }); + }); + + tests.add_test("test_edit_overwrite_10", [] { + // Create a track with one long clip. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(704.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + + // Overwrite before the clip, creating a gap. + otio::SerializableObject::Retainer over_1 = new otio::Clip( + "over_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(100.0, 24.0))); + const RationalTime duration = track->duration(); + otio::ErrorStatus error_status; + otio::algo::overwrite( + over_1, + track, + TimeRange(RationalTime(20.0, 24.0), RationalTime(70.0, 24.0)), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + const RationalTime new_duration = track->duration(); + //assert_duration(new_duration, duration); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(70.0, 24.0)), + TimeRange( + RationalTime(90.0, 24.0), + RationalTime(614.0, 24.0)) + }); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(70.0, 24.0)), + TimeRange( + RationalTime(90.0, 24.0), + RationalTime(614.0, 24.0)) + }); + }); + + + // Insert at middle of clip_0 + tests.add_test("test_edit_insert_1", [] { + // Create a track with two clips. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + + // Insert in clip 0. + otio::SerializableObject::Retainer insert_1 = new otio::Clip( + "insert_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(12.0, 24.0))); + otio::ErrorStatus error_status; + otio::algo::insert( + insert_1, + track, + RationalTime(12.0, 24.0), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + assertEqual(track->children().size(), 4); + const RationalTime duration = track->duration(); + assert_duration(duration, otime::RationalTime(60.0,24.0)); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(12.0, 24.0)), + TimeRange( + RationalTime(12.0, 24.0), + RationalTime(12.0, 24.0)), + TimeRange( + RationalTime(24.0, 24.0), + RationalTime(12.0, 24.0)), + TimeRange( + RationalTime(36.0, 24.0), + RationalTime(24.0, 24.0)) + }); + }); + + // Insert at start of clip_0. + tests.add_test("test_edit_insert_2", [] { + // Create a track with two clips. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + + // Insert in clip 0. + otio::SerializableObject::Retainer insert_1 = new otio::Clip( + "insert_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(12.0, 24.0))); + otio::ErrorStatus error_status; + otio::algo::insert( + insert_1, + track, + RationalTime(0.0, 24.0), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + assertEqual(track->children().size(), 3); + + const RationalTime duration = track->duration(); + assert_duration(duration, otime::RationalTime(60.0,24.0)); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(12.0, 24.0)), + TimeRange( + RationalTime(12.0, 24.0), + RationalTime(24.0, 24.0)), + TimeRange( + RationalTime(36.0, 24.0), + RationalTime(24.0, 24.0)) + }); + }); + + // Insert at start of clip_1 (insert at 0 index). + tests.add_test("test_edit_insert_3", [] { + // Create a track with two clips. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + + // Insert in clip 0. + otio::SerializableObject::Retainer insert_1 = new otio::Clip( + "insert_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(12.0, 24.0))); + otio::ErrorStatus error_status; + otio::algo::insert( + insert_1, + track, + RationalTime(-1.0, 24.0), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + assertEqual(track->children().size(), 3); + + const RationalTime duration = track->duration(); + assertEqual(duration, otime::RationalTime(60.0,24.0)); + auto range = clip_0->trimmed_range_in_parent().value(); + assertEqual( + range, + otio::TimeRange( + otio::RationalTime(12.0, 24.0), + otio::RationalTime(24.0, 24.0))); + range = clip_1->trimmed_range_in_parent().value(); + assertEqual( + range, + otio::TimeRange( + otio::RationalTime(36.0, 24.0), + otio::RationalTime(24.0, 24.0))); + range = insert_1->trimmed_range_in_parent().value(); + assertEqual( + range, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(12.0, 24.0))); + }); + + // Insert at start of clip_1. + tests.add_test("test_edit_insert_4", [] { + // Create a track with two clips. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + + // Insert in clip 0. + otio::SerializableObject::Retainer insert_1 = new otio::Clip( + "insert_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(12.0, 24.0))); + otio::ErrorStatus error_status; + otio::algo::insert( + insert_1, + track, + RationalTime(24.0, 24.0), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + const RationalTime duration = track->duration(); + assert_duration(duration, otime::RationalTime(60.0,24.0)); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(24.0, 24.0)), + TimeRange( + RationalTime(24.0, 24.0), + RationalTime(12.0, 24.0)), + TimeRange( + RationalTime(36.0, 24.0), + RationalTime(24.0, 24.0)) + }); + }); + + // Insert at end of clip_1 (append at end). + tests.add_test("test_edit_insert_4", [] { + // Create a track with two clips. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + + // Insert in clip 0. + otio::SerializableObject::Retainer insert_1 = new otio::Clip( + "insert_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(12.0, 24.0))); + otio::ErrorStatus error_status; + otio::algo::insert( + insert_1, + track, + RationalTime(48.0, 24.0), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + const RationalTime duration = track->duration(); + assert_duration(duration, otime::RationalTime(60.0,24.0)); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(24.0, 24.0)), + TimeRange( + RationalTime(24.0, 24.0), + RationalTime(24.0, 24.0)), + TimeRange( + RationalTime(48.0, 24.0), + RationalTime(12.0, 24.0)) + }); + }); + + // Insert near the beginning of clip_0. + tests.add_test("test_edit_insert_5", [] { + // Create a track with two clips. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + + // Insert in clip 0. + otio::SerializableObject::Retainer insert_1 = new otio::Clip( + "insert_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(12.0, 24.0))); + otio::ErrorStatus error_status; + otio::algo::insert( + insert_1, + track, + RationalTime(1.0, 24.0), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + const RationalTime duration = track->duration(); + assert_duration(duration, otime::RationalTime(60.0,24.0)); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(1.0, 24.0)), + TimeRange( + RationalTime(1.0, 24.0), + RationalTime(12.0, 24.0)), + TimeRange( + RationalTime(13.0, 24.0), + RationalTime(23.0, 24.0)), + TimeRange( + RationalTime(36.0, 24.0), + RationalTime(24.0, 24.0)) + }); + }); + + // Insert near the end of clip_1. + tests.add_test("test_edit_insert_6", [] { + // Create a track with two clips. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + + // Insert in clip 0. + otio::SerializableObject::Retainer insert_1 = new otio::Clip( + "insert_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(12.0, 24.0))); + otio::ErrorStatus error_status; + otio::algo::insert( + insert_1, + track, + RationalTime(47.0, 24.0), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + const RationalTime duration = track->duration(); + assert_duration(duration, otime::RationalTime(60.0,24.0)); + assert_track_ranges(track, + { + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(24.0, 24.0)), + otio::TimeRange( + otio::RationalTime(24.0, 24.0), + otio::RationalTime(23.0, 24.0)), + otio::TimeRange( + otio::RationalTime(47.0, 24.0), + otio::RationalTime(12.0, 24.0)), + otio::TimeRange( + otio::RationalTime(59.0, 24.0), + otio::RationalTime(1.0, 24.0)), + }); + }); + + // Insert at the end of clip_1. + tests.add_test("test_edit_insert_7", [] { + otio::ErrorStatus error_status; + + // Create a track with three clips. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "PSR63_2012-06-02", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 23.98), + otio::RationalTime(71.94, 23.98))); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "Dinky_2015-06-11", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 23.98), + otio::RationalTime(71.94, 23.98))); + otio::SerializableObject::Retainer clip_2 = new otio::Clip( + "BART_2021-02-07", + nullptr, + otio::TimeRange( + otio::RationalTime(90.0, 30.0), + otio::RationalTime(90.0, 30.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + track->append_child(clip_2); + const RationalTime duration = track->duration(); + + track->remove_child(1); + + const RationalTime new_duration = track->duration(); + assert_duration(new_duration, RationalTime(180.0, 30.0)); + + assert_clip_ranges(track, + { + otio::TimeRange( + otio::RationalTime(0.0, 23.98), + otio::RationalTime(71.94, 23.98)), + otio::TimeRange( + otio::RationalTime(90.0, 30.0), + otio::RationalTime(90.0, 30.0)), + }); + + + + // Insert at end of clip 2. + otio::algo::insert( + clip_1, + track, + RationalTime(180.0, 30.0), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + { + const RationalTime new_duration = track->duration(); + assert_duration(new_duration, duration); + } + assert_clip_ranges(track, + { + otio::TimeRange( + otio::RationalTime(0.0, 23.98), + otio::RationalTime(71.94, 23.98)), + otio::TimeRange( + otio::RationalTime(90.0, 30.0), + otio::RationalTime(90.0, 30.0)), + otio::TimeRange( + otio::RationalTime(0.0, 23.98), + otio::RationalTime(71.94, 23.98)), + }); + + track->remove_child(2); + + { + const RationalTime new_duration = track->duration(); + assert_duration(new_duration, RationalTime(180.0, 30.0)); + } + + assert_clip_ranges(track, + { + otio::TimeRange( + otio::RationalTime(0.0, 23.98), + otio::RationalTime(71.94, 23.98)), + otio::TimeRange( + otio::RationalTime(90.0, 30.0), + otio::RationalTime(90.0, 30.0)), + }); + + // Insert at end of clip 1. + otio::algo::insert( + clip_1, + track, + RationalTime(90.0, 30.0), + true, + nullptr, + &error_status); + + { + const RationalTime new_duration = track->duration(); + assert_duration(new_duration, duration); + } + assert_clip_ranges(track, + { + otio::TimeRange( + otio::RationalTime(0.0, 23.98), + otio::RationalTime(71.94, 23.98)), + otio::TimeRange( + otio::RationalTime(0.0, 23.98), + otio::RationalTime(71.94, 23.98)), + otio::TimeRange( + otio::RationalTime(90.0, 30.0), + otio::RationalTime(90.0, 30.0)), + }); + + }); + + // Insert at the middle of clip 0 + tests.add_test("test_edit_insert_8", [] { + otio::ErrorStatus error_status; + + // Create a track with three clips. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "spiderman", + nullptr, + otio::TimeRange( + otio::RationalTime(1575360, 23.976024), + otio::RationalTime(3809.0, 23.976024))); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "spider insert", + nullptr, + otio::TimeRange( + otio::RationalTime(1575360, 23.976024), + otio::RationalTime(1.0, 23.976024))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + + debug_track_ranges("START", track); + + // Insert at end of clip 2. + otio::algo::insert( + clip_1, + track, + RationalTime(141.0, 23.976024), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + // { + // const RationalTime new_duration = track->duration(); + // assert_duration(new_duration, duration); + // } + assert_clip_ranges(track, + { + otio::TimeRange( + otio::RationalTime(1575360.0, 23.976024), + otio::RationalTime(141.0, 23.976024)), + otio::TimeRange( + otio::RationalTime(1575360, 23.976024), + otio::RationalTime(1.0, 23.976024)), + otio::TimeRange( + otio::RationalTime(1575502.0, 23.976024), + otio::RationalTime(3668.0, 23.976024)), + }); + + assert_track_ranges(track, + { + otio::TimeRange( + otio::RationalTime(0.0, 23.976024), + otio::RationalTime(141.0, 23.976024)), + otio::TimeRange( + otio::RationalTime(141.0, 23.976024), + otio::RationalTime(1, 23.976024)), + otio::TimeRange( + otio::RationalTime(142.0, 23.976024), + otio::RationalTime(3668.0, 23.976024)), + }); + + }); + + + tests.add_test("test_edit_slip", [] { + const TimeRange media_range( + RationalTime(-15.0, 24.0), + RationalTime(63.0, 24.0)); + const TimeRange clip_range( + RationalTime(0.0, 24.0), + RationalTime(36.0, 24.0)); + + // Slip +5 frames. + test_edit_slip( + media_range, + clip_range, + RationalTime(5.0, 24.0), + TimeRange(RationalTime(5.0, 24.0), RationalTime(36.0, 24.0))); + + // Slip +12 frames. + test_edit_slip( + media_range, + clip_range, + RationalTime(12.0, 24.0), + TimeRange(RationalTime(12.0, 24.0), RationalTime(36.0, 24.0))); + + // Slip +20 frames. + test_edit_slip( + media_range, + clip_range, + RationalTime(20.0, 24.0), + TimeRange(RationalTime(12.0, 24.0), RationalTime(36.0, 24.0))); + + // Slip -5 frames + test_edit_slip( + media_range, + clip_range, + RationalTime(-5.0, 24.0), + TimeRange(RationalTime(-5.0, 24.0), RationalTime(36.0, 24.0))); + + // Slip -15 frames + test_edit_slip( + media_range, + clip_range, + RationalTime(-15.0, 24.0), + TimeRange(RationalTime(-15.0, 24.0), RationalTime(36.0, 24.0))); + + // Slip -30 frames + test_edit_slip( + media_range, + clip_range, + RationalTime(-30.0, 24.0), + TimeRange(RationalTime(-15.0, 24.0), RationalTime(36.0, 24.0))); + }); + + tests.add_test("test_edit_slide", [] { + TimeRange media_range( + RationalTime(0.0, 24.0), + RationalTime(48.0, 24.0)); + + // Slide 0. No change. + test_edit_slide( + media_range, + RationalTime(0.0, 24.0), + { TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)), + TimeRange(RationalTime(24.0, 24.0), RationalTime(30.0, 24.0)), + TimeRange(RationalTime(54.0, 24.0), RationalTime(40.0, 24.0)) }); + + // Slide right +12. + test_edit_slide( + media_range, + RationalTime(12.0, 24.0), + { TimeRange(RationalTime(0.0, 24.0), RationalTime(36.0, 24.0)), + TimeRange(RationalTime(36.0, 24.0), RationalTime(30.0, 24.0)), + TimeRange(RationalTime(66.0, 24.0), RationalTime(40.0, 24.0)) }); + + // Slide right +48, which will clamp. + test_edit_slide( + media_range, + RationalTime(48.0, 24.0), + { TimeRange(RationalTime(0.0, 24.0), RationalTime(48.0, 24.0)), + TimeRange(RationalTime(48.0, 24.0), RationalTime(30.0, 24.0)), + TimeRange(RationalTime(78.0, 24.0), RationalTime(40.0, 24.0)) }); + + // Slide left -10. + test_edit_slide( + media_range, + RationalTime(-10.0, 24.0), + { TimeRange(RationalTime(0.0, 24.0), RationalTime(14.0, 24.0)), + TimeRange(RationalTime(14.0, 24.0), RationalTime(30.0, 24.0)), + TimeRange(RationalTime(44.0, 24.0), RationalTime(40.0, 24.0)) }); + + // Slide left -24, which is invalid. No change. + test_edit_slide( + media_range, + RationalTime(-24.0, 24.0), + { TimeRange(RationalTime(0.0, 24.0), RationalTime(24.0, 24.0)), + TimeRange(RationalTime(24.0, 24.0), RationalTime(30.0, 24.0)), + TimeRange(RationalTime(54.0, 24.0), RationalTime(40.0, 24.0)) }); + + }); + + tests.add_test("test_edit_trim_1", [] { + + // Create a track with one gap and two clips. + otio::SerializableObject::Retainer clip_0 = new otio::Gap( + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(20.0, 24.0)), + "gap_0"); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(5.0, 24.0), + otio::RationalTime(50.0, 24.0))); + otio::SerializableObject::Retainer clip_2 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(10.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + track->append_child(clip_2); + const RationalTime duration = track->duration(); + + otio::ErrorStatus error_status; + otio::algo::trim( + clip_1, + RationalTime(5.0, 24.0), + RationalTime(0.0, 24.0), + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + const RationalTime new_duration = track->duration(); + assert_duration(new_duration, duration); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(25.0, 24.0)), + TimeRange( + RationalTime(25.0, 24.0), + RationalTime(45.0, 24.0)), + TimeRange( + RationalTime(70.0, 24.0), + RationalTime(10.0, 24.0)) + }); + }); + + // Test trim delta_out right (no change due to clip). + tests.add_test("test_edit_trim_2", [] { + // Create a track with one gap and two clips. + otio::SerializableObject::Retainer clip_0 = new otio::Gap( + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(20.0, 24.0)), + "gap_0"); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(5.0, 24.0), + otio::RationalTime(50.0, 24.0))); + otio::SerializableObject::Retainer clip_2 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(10.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + track->append_child(clip_2); + + const RationalTime duration = track->duration(); + + otio::ErrorStatus error_status; + otio::algo::trim( + clip_1, + RationalTime(0.0, 24.0), + RationalTime(5.0, 24.0), + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + const RationalTime new_duration = track->duration(); + assert_duration(new_duration, duration); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(50.0, 24.0)), + TimeRange( + RationalTime(70.0, 24.0), + RationalTime(10.0, 24.0)) + }); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(50.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(10.0, 24.0)) + }); + }); + + // Test trim delta_out left (create a gap) + tests.add_test("test_edit_trim_3", [] { + // Create a track with one gap and two clips. + otio::SerializableObject::Retainer clip_0 = new otio::Gap( + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(20.0, 24.0)), + "gap_0"); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(5.0, 24.0), + otio::RationalTime(50.0, 24.0))); + otio::SerializableObject::Retainer clip_2 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(10.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + track->append_child(clip_2); + + const RationalTime duration = track->duration(); + + otio::ErrorStatus error_status; + otio::algo::trim( + clip_1, + RationalTime(0.0, 24.0), + RationalTime(-5.0, 24.0), + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + const RationalTime new_duration = track->duration(); + assert_duration(new_duration, duration); + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(45.0, 24.0)), + TimeRange( + RationalTime(65.0, 24.0), + RationalTime(5.0, 24.0)), + TimeRange( + RationalTime(70.0, 24.0), + RationalTime(10.0, 24.0)) + }); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(45.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(5.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(10.0, 24.0)) + }); + }); + + // Test ripple + tests.add_test("test_edit_ripple_1", [] { + test_edit_ripple( + RationalTime(10.0, 24.0), + RationalTime(0.0, 24.0), + // Clip in Track Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(15.0, 24.0)), + TimeRange( + RationalTime(35.0, 24.0), + RationalTime(20.0, 24.0)) + }, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(15.0, 24.0), + RationalTime(15.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(20.0, 24.0)) + }); + }); + + tests.add_test("test_edit_ripple_2", [] { + test_edit_ripple( + RationalTime(-10.0, 24.0), + RationalTime(0.0, 24.0), + // Clip in Track Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(30.0, 24.0)), + TimeRange( + RationalTime(50.0, 24.0), + RationalTime(20.0, 24.0)) + }, + // Clip Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(30.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(20.0, 24.0)) + }); + }); + + + tests.add_test("test_edit_ripple_3", [] { + test_edit_ripple( + RationalTime(0.0, 24.0), + RationalTime(10.0, 24.0), + // Clip in Track Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(35.0, 24.0)), + TimeRange( + RationalTime(55.0, 24.0), + RationalTime(20.0, 24.0)) + }, + // Clip Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(35.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(20.0, 24.0)) + }); + }); + + + tests.add_test("test_edit_ripple_4", [] { + test_edit_ripple( + RationalTime(0.0, 24.0), + RationalTime(-10.0, 24.0), + // Clip in Track Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(15.0, 24.0)), + TimeRange( + RationalTime(35.0, 24.0), + RationalTime(20.0, 24.0)) + }, + // Clip Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(15.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(20.0, 24.0)) + }); + }); + + tests.add_test("test_edit_roll_1", [] { + test_edit_roll( + RationalTime(10.0, 24.0), + RationalTime(0.0, 24.0), + // Clip in Track Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(30.0, 24.0)), + TimeRange( + RationalTime(30.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(50.0, 24.0), + RationalTime(20.0, 24.0)) + }, + // Clip Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(30.0, 24.0)), + TimeRange( + RationalTime(15.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(20.0, 24.0)) + }); + }); + + tests.add_test("test_edit_roll_2", [] { + test_edit_roll( + RationalTime(-10.0, 24.0), + RationalTime(0.0, 24.0), + // Clip in Track Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(15.0, 24.0)), + TimeRange( + RationalTime(15.0, 24.0), + RationalTime(35.0, 24.0)), + TimeRange( + RationalTime(50.0, 24.0), + RationalTime(20.0, 24.0)) + }, + // Clip Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(15.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(35.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(20.0, 24.0)) + }); + }); + + tests.add_test("test_edit_roll_3", [] { + test_edit_roll( + RationalTime(0.0, 24.0), + RationalTime(10.0, 24.0), + // Clip in Track Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(40.0, 24.0)), + TimeRange( + RationalTime(60.0, 24.0), + RationalTime(20.0, 24.0)) + }, + // Clip Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(40.0, 24.0)), + TimeRange( + RationalTime(15.0, 24.0), + RationalTime(20.0, 24.0)) + }); + }); + + tests.add_test("test_edit_roll_4", [] { + test_edit_roll( + RationalTime(0.0, 24.0), + RationalTime(-10.0, 24.0), + // Clip in Track Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(25.0, 24.0)), + TimeRange( + RationalTime(45.0, 24.0), + RationalTime(20.0, 24.0)) + }, + // Clip Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(25.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)) + }); + }); + + // Add longer clip in gap as Fit reference point + // (creates linearTimeWarp effect). + tests.add_test("test_edit_fill_1", [] { + test_edit_fill( + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(35.0, 24.0)), + RationalTime(20.0, 24.0), + ReferencePoint::Fit, + // Clip in Track Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(35.0, 24.0)), + TimeRange( + RationalTime(55.0, 24.0), + RationalTime(20.0, 24.0)) + }, + // Clip Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(35.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(20.0, 24.0)) + }); + }); + + // Add longer clip at gap as Source reference point. + // Stretches timeline. + tests.add_test("test_edit_fill_2", [] { + test_edit_fill( + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(35.0, 24.0)), + RationalTime(20.0, 24.0), + ReferencePoint::Source, + // Clip in Track Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(35.0, 24.0)), + TimeRange( + RationalTime(55.0, 24.0), + RationalTime(5.0, 24.0)), + }, + // Clip Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(35.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(5.0, 24.0)), + }); + }); + + + // Add equal clip in gap as Source reference point + tests.add_test("test_edit_fill_3", [] { + test_edit_fill( + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(30.0, 24.0)), + RationalTime(20.0, 24.0), + ReferencePoint::Source, + // Clip in Track Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(30.0, 24.0)), + TimeRange( + RationalTime(50.0, 24.0), + RationalTime(20.0, 24.0)) + }, + // Clip Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(30.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(20.0, 24.0)) + }); + }); + + // Add shorter clip in gap as Source reference point + tests.add_test("test_edit_fill_4", [] { + test_edit_fill( + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(5.0, 24.0)), + RationalTime(20.0, 24.0), + ReferencePoint::Source, + // Clip in Track Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(5.0, 24.0)), + TimeRange( + RationalTime(25.0, 24.0), + RationalTime(25.0, 24.0)), + TimeRange( + RationalTime(50.0, 24.0), + RationalTime(20.0, 24.0)) + }, + // Clip Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(5.0, 24.0)), + TimeRange( + RationalTime(10.0, 24.0), + RationalTime(25.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(20.0, 24.0)) + }); + }); + + // Add an equal clip (after trim) in gap as + // Sequence reference point. + tests.add_test("test_edit_fill_5", [] { + + test_edit_fill( + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(35.0, 24.0)), + RationalTime(20.0, 24.0), + ReferencePoint::Sequence, + // Clip in Track Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(30.0, 24.0)), + TimeRange( + RationalTime(50.0, 24.0), + RationalTime(20.0, 24.0)) + }, + // Clip Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(30.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(20.0, 24.0)) + }); + }); + + // Add a longer clip in gap as Sequence reference point + tests.add_test("test_edit_fill_6", [] { + + test_edit_fill( + TimeRange( + RationalTime(-10.0, 24.0), + RationalTime(30.0, 24.0)), + RationalTime(20.0, 24.0), + ReferencePoint::Sequence, + // Clip in Track Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(15.0, 24.0)), + TimeRange( + RationalTime(35.0, 24.0), + RationalTime(15.0, 24.0)), + TimeRange( + RationalTime(50.0, 24.0), + RationalTime(20.0, 24.0)) + }, + // Clip Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(15.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(15.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(20.0, 24.0)) + }); + }); + + + // Add a shorter clip in gap as Sequence reference point + tests.add_test("test_edit_fill_7", [] { + + test_edit_fill( + TimeRange( + RationalTime(10.0, 24.0), + RationalTime(5.0, 24.0)), + RationalTime(20.0, 24.0), + ReferencePoint::Sequence, + // Clip in Track Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(5.0, 24.0)), + TimeRange( + RationalTime(25.0, 24.0), + RationalTime(25.0, 24.0)), + TimeRange( + RationalTime(50.0, 24.0), + RationalTime(20.0, 24.0)) + }, + // Clip Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(10.0, 24.0), + RationalTime(5.0, 24.0)), + TimeRange( + RationalTime(10.0, 24.0), + RationalTime(25.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(20.0, 24.0)) + }); + }); + + // Add a shorter clip in gap as Sequence reference point + tests.add_test("test_edit_fill_8", [] { + + test_edit_fill( + TimeRange( + RationalTime(10.0, 24.0), + RationalTime(5.0, 24.0)), + RationalTime(20.0, 24.0), + ReferencePoint::Sequence, + // Clip in Track Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(20.0, 24.0), + RationalTime(5.0, 24.0)), + TimeRange( + RationalTime(25.0, 24.0), + RationalTime(25.0, 24.0)), + TimeRange( + RationalTime(50.0, 24.0), + RationalTime(20.0, 24.0)) + }, + // Clip Ranges + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(20.0, 24.0)), + TimeRange( + RationalTime(10.0, 24.0), + RationalTime(5.0, 24.0)), + TimeRange( + RationalTime(10.0, 24.0), + RationalTime(25.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(20.0, 24.0)) + }); + }); + + + // Test remove middle clip + tests.add_test("test_edit_remove_0", [] { + // Create a track with three clips. + otio::SerializableObject::Retainer clip_0 = new otio::Clip( + "clip_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(50.0, 24.0))); + otio::SerializableObject::Retainer clip_1 = new otio::Clip( + "clip_1", + nullptr, + otio::TimeRange( + otio::RationalTime(5.0, 24.0), + otio::RationalTime(50.0, 24.0))); + otio::SerializableObject::Retainer clip_2 = new otio::Clip( + "clip_2", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(10.0, 24.0))); + + otio::SerializableObject::Retainer fill_0 = new otio::Clip( + "fill_0", + nullptr, + otio::TimeRange( + otio::RationalTime(0.0, 24.0), + otio::RationalTime(10.0, 24.0))); + otio::SerializableObject::Retainer track = + new otio::Track(); + track->append_child(clip_0); + track->append_child(clip_1); + track->append_child(clip_2); + + const RationalTime duration = track->duration(); + + otio::ErrorStatus error_status; + + // Remove second clip (creates a gap) + otio::algo::remove( + track, + RationalTime(55.0, 24.0), + true, + nullptr, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + { + const RationalTime new_duration = track->duration(); + assert_duration(new_duration, duration); + } + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(50.0, 24.0)), + TimeRange( + RationalTime(50.0, 24.0), + RationalTime(50.0, 24.0)), + TimeRange( + RationalTime(100.0, 24.0), + RationalTime(10.0, 24.0)) + }); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(50.0, 24.0)), + TimeRange( + RationalTime(5.0, 24.0), + RationalTime(50.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(10.0, 24.0)) + }); + + // Remove second clip (which is now a gap, replacing it with fill_0) + otio::algo::remove( + track, + RationalTime(55.0, 24.0), + true, + fill_0, + &error_status); + + // Asserts. + assert(!otio::is_error(error_status)); + { + const RationalTime new_duration = track->duration(); + assert_duration(new_duration, RationalTime(70.0, 24.0)); + } + assert_track_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(50.0, 24.0)), + TimeRange( + RationalTime(50.0, 24.0), + RationalTime(10.0, 24.0)), + TimeRange( + RationalTime(60.0, 24.0), + RationalTime(10.0, 24.0)) + }); + assert_clip_ranges(track, + { + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(50.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(10.0, 24.0)), + TimeRange( + RationalTime(0.0, 24.0), + RationalTime(10.0, 24.0)) + }); + }); + + tests.run(argc, argv); + return 0; +}