From a2bfebfbcc0e0c06d42cee3aa66eac54e1af2481 Mon Sep 17 00:00:00 2001 From: Jonathan Hearn Date: Thu, 10 Dec 2020 15:21:11 +0000 Subject: [PATCH 1/7] #841 Track range_of_all_children applies TimeEffect effect to duration of clip. --- src/opentime/rationalTime.h | 8 ++++++++ src/opentimelineio/linearTimeWarp.h | 5 +++++ src/opentimelineio/timeEffect.cpp | 5 +++++ src/opentimelineio/timeEffect.h | 4 ++++ src/opentimelineio/track.cpp | 9 ++++++++- .../opentime-bindings/opentime_rationalTime.cpp | 2 ++ .../opentimelineio-bindings/otio_serializableObjects.cpp | 5 ++++- 7 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/opentime/rationalTime.h b/src/opentime/rationalTime.h index 00256ac3a9..caa9656f19 100644 --- a/src/opentime/rationalTime.h +++ b/src/opentime/rationalTime.h @@ -135,6 +135,14 @@ class RationalTime { return RationalTime {-lhs._value, lhs._rate}; } + friend RationalTime operator* (RationalTime lhs, double rhs) { + return RationalTime {lhs._value * rhs, lhs._rate}; + } + + friend RationalTime operator/ (RationalTime lhs, double rhs) { + return RationalTime {lhs._value / rhs, lhs._rate}; + } + friend bool operator> (RationalTime lhs, RationalTime rhs) { return (lhs._value / lhs._rate) > (rhs._value / rhs._rate); } diff --git a/src/opentimelineio/linearTimeWarp.h b/src/opentimelineio/linearTimeWarp.h index 620504c048..ef9143bcd2 100644 --- a/src/opentimelineio/linearTimeWarp.h +++ b/src/opentimelineio/linearTimeWarp.h @@ -2,6 +2,7 @@ #include "opentimelineio/version.h" #include "opentimelineio/timeEffect.h" +#include "opentime/timeRange.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { @@ -27,6 +28,10 @@ class LinearTimeWarp : public TimeEffect { _time_scalar = time_scalar; } + virtual TimeRange output_range(TimeRange input_range, ErrorStatus* error_status) const { + return TimeRange(input_range.start_time(), input_range.duration() / _time_scalar); + } + protected: virtual ~LinearTimeWarp(); diff --git a/src/opentimelineio/timeEffect.cpp b/src/opentimelineio/timeEffect.cpp index 648c73505a..9b39f8fb80 100644 --- a/src/opentimelineio/timeEffect.cpp +++ b/src/opentimelineio/timeEffect.cpp @@ -11,4 +11,9 @@ TimeEffect::TimeEffect(std::string const& name, TimeEffect::~TimeEffect() { } +TimeRange TimeEffect::output_range(TimeRange input_range, ErrorStatus* error_status) const { + *error_status = ErrorStatus::NOT_IMPLEMENTED; + return TimeRange(); +} + } } diff --git a/src/opentimelineio/timeEffect.h b/src/opentimelineio/timeEffect.h index a0bd3df8ea..25940d7a8f 100644 --- a/src/opentimelineio/timeEffect.h +++ b/src/opentimelineio/timeEffect.h @@ -2,6 +2,7 @@ #include "opentimelineio/version.h" #include "opentimelineio/effect.h" +#include "opentime/timeRange.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { @@ -17,6 +18,9 @@ class TimeEffect : public Effect { TimeEffect(std::string const& name = std::string(), std::string const& effect_name = std::string(), AnyDictionary const& metadata = AnyDictionary()); + + virtual TimeRange output_range(TimeRange input_range, ErrorStatus* error_status) const; + protected: virtual ~TimeEffect(); diff --git a/src/opentimelineio/track.cpp b/src/opentimelineio/track.cpp index 2906eaf72e..f3b304ee27 100644 --- a/src/opentimelineio/track.cpp +++ b/src/opentimelineio/track.cpp @@ -2,6 +2,7 @@ #include "opentimelineio/transition.h" #include "opentimelineio/gap.h" #include "opentimelineio/vectorIndexing.h" +#include "opentimelineio/timeEffect.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { @@ -196,7 +197,13 @@ std::map Track::range_of_all_children(ErrorStatus* error transition->out_offset() + transition->in_offset()); } else if (auto item = dynamic_cast(child.value)) { - auto last_range = TimeRange(last_end_time, item->trimmed_range(error_status).duration()); + auto output_range = TimeRange(RationalTime(0), item->trimmed_range(error_status).duration()); + for (auto effect: item->effects()) { + if (auto time_effect = dynamic_cast(effect.value)) { + output_range = time_effect->output_range(output_range, error_status); + } + } + auto last_range = TimeRange(last_end_time, output_range.duration()); result[child] = last_range; last_end_time = last_range.end_time_exclusive(); } diff --git a/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp b/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp index 27eee0da59..7a60636404 100644 --- a/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp +++ b/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp @@ -134,6 +134,8 @@ void opentime_rationalTime_bindings(py::module m) { }) .def(py::self - py::self) .def(py::self + py::self) + .def(py::self * double()) + .def(py::self / double()) // The simple "py::self += py::self" returns the original, // which is not what we want here: we need this to return a new copy // to avoid mutating any additional references, since this class has complete value semantics. diff --git a/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp b/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp index c83f2af56f..56d2c118b1 100644 --- a/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp +++ b/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp @@ -545,7 +545,10 @@ static void define_effects(py::module m) { return new TimeEffect(name, effect_name, py_to_any_dictionary(metadata)); }), name_arg, "effect_name"_a = std::string(), - metadata_arg); + metadata_arg) + .def("output_range", [](TimeEffect* effect, TimeRange input_range) { + return effect->output_range(input_range, ErrorStatusHandler()); + }); py::class_>(m, "LinearTimeWarp", py::dynamic_attr()) .def(py::init([](std::string name, From 67046c54756cf328682a2f58d4541bdb4ebd4e1a Mon Sep 17 00:00:00 2001 From: Jonathan Hearn Date: Fri, 11 Dec 2020 14:13:27 +0000 Subject: [PATCH 2/7] #841 addressed code review comments. --- src/opentime/rationalTime.h | 8 -------- src/opentimelineio/linearTimeWarp.h | 15 ++++++++++++++- src/opentimelineio/track.cpp | 10 ++++++---- .../opentime-bindings/opentime_rationalTime.cpp | 2 -- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/opentime/rationalTime.h b/src/opentime/rationalTime.h index caa9656f19..00256ac3a9 100644 --- a/src/opentime/rationalTime.h +++ b/src/opentime/rationalTime.h @@ -135,14 +135,6 @@ class RationalTime { return RationalTime {-lhs._value, lhs._rate}; } - friend RationalTime operator* (RationalTime lhs, double rhs) { - return RationalTime {lhs._value * rhs, lhs._rate}; - } - - friend RationalTime operator/ (RationalTime lhs, double rhs) { - return RationalTime {lhs._value / rhs, lhs._rate}; - } - friend bool operator> (RationalTime lhs, RationalTime rhs) { return (lhs._value / lhs._rate) > (rhs._value / rhs._rate); } diff --git a/src/opentimelineio/linearTimeWarp.h b/src/opentimelineio/linearTimeWarp.h index ef9143bcd2..84150601bd 100644 --- a/src/opentimelineio/linearTimeWarp.h +++ b/src/opentimelineio/linearTimeWarp.h @@ -3,6 +3,8 @@ #include "opentimelineio/version.h" #include "opentimelineio/timeEffect.h" #include "opentime/timeRange.h" +#include "opentime/timeTransform.h" +#include "opentime/rationalTime.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { @@ -25,11 +27,22 @@ class LinearTimeWarp : public TimeEffect { } void set_time_scalar(double time_scalar) { + // TODO: Is there a concept of validation within set methods? + // I think the only invalid value is 0 (use a freeze frame) _time_scalar = time_scalar; } virtual TimeRange output_range(TimeRange input_range, ErrorStatus* error_status) const { - return TimeRange(input_range.start_time(), input_range.duration() / _time_scalar); + return TimeTransform( + RationalTime(), + // TODO: Does time_scalar in LinearTimeWarp mean + // "make it this much faster" + // or "make it this much slower"? + // e.g. is 2.0 twice the speed or twice the length? + 1 / _time_scalar + ).applied_to( + TimeRange(RationalTime(), input_range.duration()) + ); } protected: diff --git a/src/opentimelineio/track.cpp b/src/opentimelineio/track.cpp index f3b304ee27..7e35032279 100644 --- a/src/opentimelineio/track.cpp +++ b/src/opentimelineio/track.cpp @@ -198,19 +198,21 @@ std::map Track::range_of_all_children(ErrorStatus* error } else if (auto item = dynamic_cast(child.value)) { auto output_range = TimeRange(RationalTime(0), item->trimmed_range(error_status).duration()); + if (*error_status) { + return result; + } for (auto effect: item->effects()) { if (auto time_effect = dynamic_cast(effect.value)) { output_range = time_effect->output_range(output_range, error_status); + if (*error_status) { + return result; + } } } auto last_range = TimeRange(last_end_time, output_range.duration()); result[child] = last_range; last_end_time = last_range.end_time_exclusive(); } - - if (*error_status) { - return result; - } } return result; diff --git a/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp b/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp index 7a60636404..27eee0da59 100644 --- a/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp +++ b/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp @@ -134,8 +134,6 @@ void opentime_rationalTime_bindings(py::module m) { }) .def(py::self - py::self) .def(py::self + py::self) - .def(py::self * double()) - .def(py::self / double()) // The simple "py::self += py::self" returns the original, // which is not what we want here: we need this to return a new copy // to avoid mutating any additional references, since this class has complete value semantics. From c3bea68c3ab45f2d32f0252e634348050b13a3da Mon Sep 17 00:00:00 2001 From: Jonathan Hearn Date: Fri, 11 Dec 2020 21:22:35 +0000 Subject: [PATCH 3/7] #841 added test for LinearTimeWarp output_range --- src/opentimelineio/linearTimeWarp.h | 7 +++++-- tests/test_effect.py | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/opentimelineio/linearTimeWarp.h b/src/opentimelineio/linearTimeWarp.h index 84150601bd..d2aa725a26 100644 --- a/src/opentimelineio/linearTimeWarp.h +++ b/src/opentimelineio/linearTimeWarp.h @@ -34,14 +34,17 @@ class LinearTimeWarp : public TimeEffect { virtual TimeRange output_range(TimeRange input_range, ErrorStatus* error_status) const { return TimeTransform( - RationalTime(), + input_range.start_time(), // TODO: Does time_scalar in LinearTimeWarp mean // "make it this much faster" // or "make it this much slower"? // e.g. is 2.0 twice the speed or twice the length? 1 / _time_scalar ).applied_to( - TimeRange(RationalTime(), input_range.duration()) + TimeRange( + RationalTime(0, input_range.start_time().rate()), + input_range.duration() + ) ); } diff --git a/tests/test_effect.py b/tests/test_effect.py index ce5be5fc5f..6ef3b2fb88 100644 --- a/tests/test_effect.py +++ b/tests/test_effect.py @@ -84,6 +84,15 @@ def test_str(self): ) +class TestTimeEffect(unittest.TestCase): + def test_output_range(self): + ef = otio.schema.TimeEffect("dummy", "dummy", dict(foo='bar')) + with self.assertRaises(NotImplementedError): + ef.output_range( + otio.opentime.TimeRange() + ) + + class TestLinearTimeWarp(unittest.TestCase): def test_cons(self): ef = otio.schema.LinearTimeWarp("Foo", 2.5, {'foo': 'bar'}) @@ -92,6 +101,19 @@ def test_cons(self): self.assertEqual(ef.time_scalar, 2.5) self.assertEqual(ef.metadata, {"foo": "bar"}) + def test_output_range(self): + ef = otio.schema.LinearTimeWarp("Foo", 2.5, {'foo': 'bar'}) + output_range = ef.output_range( + otio.opentime.TimeRange( + start_time=otio.opentime.RationalTime(10, 25), + duration=otio.opentime.RationalTime(10, 25), + ) + ) + self.assertEqual(output_range.start_time.value, 10) + self.assertEqual(output_range.start_time.rate, 25) + self.assertEqual(output_range.duration.value, 4) + self.assertEqual(output_range.duration.rate, 25) + class TestFreezeFrame(unittest.TestCase): def test_cons(self): From 75678758678d17ef30159a6dd394147e5834def4 Mon Sep 17 00:00:00 2001 From: Jonathan Hearn Date: Fri, 11 Dec 2020 23:46:08 +0000 Subject: [PATCH 4/7] #841 made FreezeFrame a direct subclass of TimeEffect to differentiate it from LinearTimeWarp. Given it a duration property. --- docs/tutorials/otio-serialized-schema.md | 2 +- src/opentime/rationalTime.h | 4 + src/opentimelineio/freezeFrame.cpp | 14 ++- src/opentimelineio/freezeFrame.h | 25 +++++- src/opentimelineio/item.cpp | 18 +++- src/opentimelineio/item.h | 6 +- src/opentimelineio/linearTimeWarp.cpp | 3 +- src/opentimelineio/linearTimeWarp.h | 1 - src/opentimelineio/track.cpp | 12 +-- .../opentime_rationalTime.cpp | 1 + .../otio_serializableObjects.cpp | 13 +-- .../opentimelineio/adapters/cmx_3600.py | 39 ++++---- tests/test_cmx_3600_adapter.py | 2 +- tests/test_composition.py | 89 +++++++++++++++++++ tests/test_effect.py | 5 +- 15 files changed, 177 insertions(+), 57 deletions(-) diff --git a/docs/tutorials/otio-serialized-schema.md b/docs/tutorials/otio-serialized-schema.md index 3af377f7ca..2f4c47e2ce 100644 --- a/docs/tutorials/otio-serialized-schema.md +++ b/docs/tutorials/otio-serialized-schema.md @@ -315,10 +315,10 @@ None ``` parameters: +- *duration*: - *effect_name*: - *metadata*: - *name*: -- *time_scalar*: ### Gap.1 diff --git a/src/opentime/rationalTime.h b/src/opentime/rationalTime.h index 00256ac3a9..c6bd3bb37b 100644 --- a/src/opentime/rationalTime.h +++ b/src/opentime/rationalTime.h @@ -131,6 +131,10 @@ class RationalTime { RationalTime {lhs._value - rhs.value_rescaled_to(lhs._rate), lhs._rate}; } + friend double operator/ (RationalTime lhs, RationalTime rhs) { + return lhs.value_rescaled_to(1) / rhs.value_rescaled_to(1); + } + friend RationalTime operator- (RationalTime lhs) { return RationalTime {-lhs._value, lhs._rate}; } diff --git a/src/opentimelineio/freezeFrame.cpp b/src/opentimelineio/freezeFrame.cpp index 09880589ba..85ea6bc2fd 100644 --- a/src/opentimelineio/freezeFrame.cpp +++ b/src/opentimelineio/freezeFrame.cpp @@ -3,11 +3,23 @@ namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { FreezeFrame::FreezeFrame(std::string const& name, + RationalTime duration, AnyDictionary const& metadata) - : Parent(name, "FreezeFrame", 0.0, metadata) { + : Parent(name, "FreezeFrame", metadata), + _duration(duration) { } FreezeFrame::~FreezeFrame() { } +bool FreezeFrame::read_from(Reader& reader) { + return reader.read("duration", &_duration) && + Parent::read_from(reader); +} + +void FreezeFrame::write_to(Writer& writer) const { + Parent::write_to(writer); + writer.write("duration", _duration); +} + } } diff --git a/src/opentimelineio/freezeFrame.h b/src/opentimelineio/freezeFrame.h index 232f1b57c0..77a05f8692 100644 --- a/src/opentimelineio/freezeFrame.h +++ b/src/opentimelineio/freezeFrame.h @@ -1,27 +1,44 @@ #pragma once #include "opentimelineio/version.h" -#include "opentimelineio/linearTimeWarp.h" +#include "opentimelineio/timeEffect.h" +#include "opentime/rationalTime.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { -class FreezeFrame : public LinearTimeWarp { +class FreezeFrame : public TimeEffect { public: struct Schema { static auto constexpr name = "FreezeFrame"; static int constexpr version = 1; }; - using Parent = LinearTimeWarp; + using Parent = TimeEffect; FreezeFrame(std::string const& name = std::string(), + RationalTime duration = RationalTime(), AnyDictionary const& metadata = AnyDictionary()); + RationalTime duration() const { + return _duration; + } + + void set_duration(RationalTime duration) { + _duration = duration; + } + + virtual TimeRange output_range(TimeRange input_range, ErrorStatus* error_status) const { + return TimeRange(input_range.start_time(), _duration); + } + protected: virtual ~FreezeFrame(); -private: + virtual bool read_from(Reader&) ; + virtual void write_to(Writer&) const; +private: + RationalTime _duration; }; } } diff --git a/src/opentimelineio/item.cpp b/src/opentimelineio/item.cpp index 9e7741d13a..0eaf06409c 100644 --- a/src/opentimelineio/item.cpp +++ b/src/opentimelineio/item.cpp @@ -1,6 +1,6 @@ #include "opentimelineio/item.h" #include "opentimelineio/composition.h" -#include "opentimelineio/effect.h" +#include "opentimelineio/timeEffect.h" #include "opentimelineio/marker.h" #include @@ -39,6 +39,22 @@ TimeRange Item::available_range(ErrorStatus* error_status) const { return TimeRange(); } +TimeRange Item::trimmed_range(ErrorStatus* error_status) const { + auto result = _source_range ? *_source_range : available_range(error_status); + if (*error_status) { + return result; + } + for (auto effect: _effects) { + if (auto time_effect = dynamic_cast(effect.value)) { + result = time_effect->output_range(result, error_status); + if (*error_status) { + return result; + } + } + } + return result; +} + TimeRange Item::visible_range(ErrorStatus* error_status) const { TimeRange result = trimmed_range(error_status); diff --git a/src/opentimelineio/item.h b/src/opentimelineio/item.h index 5a722c6741..d8ffa85f27 100644 --- a/src/opentimelineio/item.h +++ b/src/opentimelineio/item.h @@ -57,10 +57,8 @@ class Item : public Composable { virtual TimeRange available_range(ErrorStatus* error_status) const; - TimeRange trimmed_range(ErrorStatus* error_status) const { - return _source_range ? *_source_range : available_range(error_status); - } - + TimeRange trimmed_range(ErrorStatus* error_status) const; + TimeRange visible_range(ErrorStatus* error_status) const; optional trimmed_range_in_parent(ErrorStatus* error_status) const; diff --git a/src/opentimelineio/linearTimeWarp.cpp b/src/opentimelineio/linearTimeWarp.cpp index 2d7beeb037..a9c4a934d7 100644 --- a/src/opentimelineio/linearTimeWarp.cpp +++ b/src/opentimelineio/linearTimeWarp.cpp @@ -3,10 +3,9 @@ namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { LinearTimeWarp::LinearTimeWarp(std::string const& name, - std::string const& effect_name, double time_scalar, AnyDictionary const& metadata) - : Parent(name, effect_name, metadata), + : Parent(name, "LinearTimeWarp", metadata), _time_scalar(time_scalar) { } diff --git a/src/opentimelineio/linearTimeWarp.h b/src/opentimelineio/linearTimeWarp.h index d2aa725a26..d7a2ddd47b 100644 --- a/src/opentimelineio/linearTimeWarp.h +++ b/src/opentimelineio/linearTimeWarp.h @@ -18,7 +18,6 @@ class LinearTimeWarp : public TimeEffect { using Parent = TimeEffect; LinearTimeWarp(std::string const& name = std::string(), - std::string const& effect_name = std::string(), double time_scalar = 1, AnyDictionary const& metadata = AnyDictionary()); diff --git a/src/opentimelineio/track.cpp b/src/opentimelineio/track.cpp index 7e35032279..0defc71c64 100644 --- a/src/opentimelineio/track.cpp +++ b/src/opentimelineio/track.cpp @@ -2,7 +2,6 @@ #include "opentimelineio/transition.h" #include "opentimelineio/gap.h" #include "opentimelineio/vectorIndexing.h" -#include "opentimelineio/timeEffect.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { @@ -197,19 +196,10 @@ std::map Track::range_of_all_children(ErrorStatus* error transition->out_offset() + transition->in_offset()); } else if (auto item = dynamic_cast(child.value)) { - auto output_range = TimeRange(RationalTime(0), item->trimmed_range(error_status).duration()); + auto last_range = TimeRange(last_end_time, item->trimmed_range(error_status).duration()); if (*error_status) { return result; } - for (auto effect: item->effects()) { - if (auto time_effect = dynamic_cast(effect.value)) { - output_range = time_effect->output_range(output_range, error_status); - if (*error_status) { - return result; - } - } - } - auto last_range = TimeRange(last_end_time, output_range.duration()); result[child] = last_range; last_end_time = last_range.end_time_exclusive(); } diff --git a/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp b/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp index 27eee0da59..40330ca61a 100644 --- a/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp +++ b/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp @@ -134,6 +134,7 @@ void opentime_rationalTime_bindings(py::module m) { }) .def(py::self - py::self) .def(py::self + py::self) + .def(py::self / py::self) // The simple "py::self += py::self" returns the original, // which is not what we want here: we need this to return a new copy // to avoid mutating any additional references, since this class has complete value semantics. diff --git a/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp b/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp index 56d2c118b1..5f54067b1c 100644 --- a/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp +++ b/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp @@ -554,18 +554,21 @@ static void define_effects(py::module m) { .def(py::init([](std::string name, double time_scalar, py::object metadata) { - return new LinearTimeWarp(name, "LinearTimeWarp", time_scalar, + return new LinearTimeWarp(name, time_scalar, py_to_any_dictionary(metadata)); }), name_arg, "time_scalar"_a = 1.0, metadata_arg) .def_property("time_scalar", &LinearTimeWarp::time_scalar, &LinearTimeWarp::set_time_scalar); - py::class_>(m, "FreezeFrame", py::dynamic_attr()) - .def(py::init([](std::string name, py::object metadata) { - return new FreezeFrame(name, py_to_any_dictionary(metadata)); }), + py::class_>(m, "FreezeFrame", py::dynamic_attr()) + .def(py::init([](std::string name, RationalTime duration, py::object metadata) { + return new FreezeFrame(name, duration, + py_to_any_dictionary(metadata)); }), name_arg, - metadata_arg); + "duration"_a = RationalTime(), + metadata_arg) + .def_property("duration", &FreezeFrame::duration, &FreezeFrame::set_duration); } static void define_media_references(py::module m) { diff --git a/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py b/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py index 4caeedcb64..4edde651ec 100644 --- a/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py +++ b/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py @@ -154,22 +154,13 @@ def add_clip(self, line, comments, rate=24): motion = comment_handler.handled.get('motion_effect') freeze = comment_handler.handled.get('freeze_frame') if motion is not None or freeze is not None: - # Adjust the clip to match the record duration - clip.source_range = opentime.TimeRange( - start_time=clip.source_range.start_time, - duration=rec_duration - ) - if freeze is not None: - clip.effects.append(schema.FreezeFrame()) + clip.effects.append(schema.FreezeFrame(duration=rec_duration)) # XXX remove 'FF' suffix (writing edl will add it back) if clip.name.endswith(' FF'): clip.name = clip.name[:-3] elif motion is not None: - fps = float( - SPEED_EFFECT_RE.match(motion).group("speed") - ) - time_scalar = fps / rate + time_scalar = src_duration / rec_duration clip.effects.append( schema.LinearTimeWarp(time_scalar=time_scalar) ) @@ -1005,7 +996,7 @@ def get_content_for_track_at_index(self, idx, title): def _supported_timing_effects(clip): return [ fx for fx in clip.effects - if isinstance(fx, schema.LinearTimeWarp) + if isinstance(fx, (schema.FreezeFrame, schema.LinearTimeWarp)) ] @@ -1017,8 +1008,8 @@ def _relevant_timing_effect(clip): for thing in clip.effects: if thing not in effects and isinstance(thing, schema.TimeEffect): raise exceptions.NotSupportedError( - "Clip contains timing effects not supported by the EDL" - " adapter.\nClip: {}".format(str(clip))) + "{} Clip contains timing effects not supported by the EDL" + " adapter.\nClip: {}".format(thing, str(clip))) timing_effect = None if effects: @@ -1048,16 +1039,16 @@ def __init__( timing_effect = _relevant_timing_effect(clip) - if timing_effect: - if timing_effect.effect_name == "FreezeFrame": - line.source_out = line.source_in + opentime.RationalTime( - 1, - line.source_in.rate - ) - elif timing_effect.effect_name == "LinearTimeWarp": - value = clip.trimmed_range().duration.value / timing_effect.time_scalar - line.source_out = ( - line.source_in + opentime.RationalTime(value, rate)) + # if timing_effect: + # if timing_effect.effect_name == "FreezeFrame": + # line.source_out = line.source_in + opentime.RationalTime( + # 1, + # line.source_in.rate + # ) + # elif timing_effect.effect_name == "LinearTimeWarp": + # value = clip.trimmed_range().duration.value / timing_effect.time_scalar + # line.source_out = ( + # line.source_in + opentime.RationalTime(value, rate)) range_in_timeline = clip.transformed_time_range( clip.trimmed_range(), diff --git a/tests/test_cmx_3600_adapter.py b/tests/test_cmx_3600_adapter.py index 12a1d1dbe1..5df259058d 100755 --- a/tests/test_cmx_3600_adapter.py +++ b/tests/test_cmx_3600_adapter.py @@ -997,7 +997,7 @@ def test_speed_effects(self): self.assertTrue( clip.effects and clip.effects[0].effect_name == "LinearTimeWarp" ) - self.assertAlmostEqual(clip.effects[0].time_scalar, 1.98333333) + self.assertAlmostEqual(clip.effects[0].time_scalar, 1.9444444444444444) self.assertIsNone( clip.metadata.get("cmx_3600", {}).get("motion") diff --git a/tests/test_composition.py b/tests/test_composition.py index 566b4b1e92..c5df01d915 100755 --- a/tests/test_composition.py +++ b/tests/test_composition.py @@ -977,6 +977,95 @@ def test_range_of_child(self): with self.assertRaises(otio.exceptions.NotAChildError): otio.schema.Clip().trimmed_range_in_parent() + def test_range_of_child_with_linear_time_warp(self): + sq = otio.schema.Track( + name="foo", + children=[ + otio.schema.Clip( + name="clip1", + source_range=otio.opentime.TimeRange( + start_time=otio.opentime.RationalTime( + value=100, + rate=24 + ), + duration=otio.opentime.RationalTime( + value=50, + rate=24 + ) + ) + ), + otio.schema.Clip( + name="clip2", + source_range=otio.opentime.TimeRange( + start_time=otio.opentime.RationalTime( + value=101, + rate=24 + ), + duration=otio.opentime.RationalTime( + value=50, + rate=24 + ) + ) + ), + otio.schema.Clip( + name="clip3", + source_range=otio.opentime.TimeRange( + start_time=otio.opentime.RationalTime( + value=102, + rate=24 + ), + duration=otio.opentime.RationalTime( + value=50, + rate=24 + ) + ) + ) + ] + ) + + sq[1].effects.append( + otio.schema.LinearTimeWarp(time_scalar=0.5) + ) + + # The Track should be as long as the children summed up + self.assertEqual( + sq.duration(), + otio.opentime.RationalTime(value=200, rate=24) + ) + + # @TODO: should include time transforms + + # Sequenced items should all land end-to-end + self.assertEqual( + sq.range_of_child_at_index(0).start_time, + otio.opentime.RationalTime() + ) + self.assertEqual( + sq.range_of_child_at_index(1).start_time, + otio.opentime.RationalTime(value=50, rate=24) + ) + self.assertEqual( + sq.range_of_child_at_index(2).start_time, + otio.opentime.RationalTime(value=150, rate=24) + ) + self.assertEqual( + sq.range_of_child(sq[2]), + sq.range_of_child_at_index(2) + ) + + self.assertEqual( + sq.range_of_child_at_index(0).duration, + otio.opentime.RationalTime(value=50, rate=24) + ) + self.assertEqual( + sq.range_of_child_at_index(1).duration, + otio.opentime.RationalTime(value=100, rate=24) + ) + self.assertEqual( + sq.range_of_child_at_index(2).duration, + otio.opentime.RationalTime(value=50, rate=24) + ) + def test_range_trimmed_out(self): track = otio.schema.Track( name="top_track", diff --git a/tests/test_effect.py b/tests/test_effect.py index 6ef3b2fb88..932fae3bfb 100644 --- a/tests/test_effect.py +++ b/tests/test_effect.py @@ -117,10 +117,11 @@ def test_output_range(self): class TestFreezeFrame(unittest.TestCase): def test_cons(self): - ef = otio.schema.FreezeFrame("Foo", {'foo': 'bar'}) + ef = otio.schema.FreezeFrame("Foo", otio.opentime.RationalTime(10, 1), {'foo': 'bar'}) self.assertEqual(ef.effect_name, "FreezeFrame") self.assertEqual(ef.name, "Foo") - self.assertEqual(ef.time_scalar, 0) + self.assertEqual(ef.duration.value, 10) + self.assertEqual(ef.duration.rate, 1) self.assertEqual(ef.metadata, {"foo": "bar"}) From 489c0716d529b408751ff3d9119c08966d7150d7 Mon Sep 17 00:00:00 2001 From: Jonathan Hearn Date: Fri, 11 Dec 2020 23:56:07 +0000 Subject: [PATCH 5/7] addressed lint issues. still 1 test failing I can't get my head around atm. --- src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py | 7 +++++-- tests/test_effect.py | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py b/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py index 4edde651ec..a7b5c4c3aa 100644 --- a/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py +++ b/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py @@ -1037,7 +1037,7 @@ def __init__( line.source_in = clip.source_range.start_time line.source_out = clip.source_range.end_time_exclusive() - timing_effect = _relevant_timing_effect(clip) + # timing_effect = _relevant_timing_effect(clip) # if timing_effect: # if timing_effect.effect_name == "FreezeFrame": @@ -1046,7 +1046,10 @@ def __init__( # line.source_in.rate # ) # elif timing_effect.effect_name == "LinearTimeWarp": - # value = clip.trimmed_range().duration.value / timing_effect.time_scalar + # value = ( + # clip.trimmed_range().duration.value + # / timing_effect.time_scalar + # ) # line.source_out = ( # line.source_in + opentime.RationalTime(value, rate)) diff --git a/tests/test_effect.py b/tests/test_effect.py index 932fae3bfb..1943f038bf 100644 --- a/tests/test_effect.py +++ b/tests/test_effect.py @@ -117,7 +117,9 @@ def test_output_range(self): class TestFreezeFrame(unittest.TestCase): def test_cons(self): - ef = otio.schema.FreezeFrame("Foo", otio.opentime.RationalTime(10, 1), {'foo': 'bar'}) + ef = otio.schema.FreezeFrame( + "Foo", otio.opentime.RationalTime(10, 1), {'foo': 'bar'} + ) self.assertEqual(ef.effect_name, "FreezeFrame") self.assertEqual(ef.name, "Foo") self.assertEqual(ef.duration.value, 10) From 553e112be065281b67b863130d642023afbbe9db Mon Sep 17 00:00:00 2001 From: Jonathan Hearn Date: Tue, 15 Dec 2020 15:02:50 +0000 Subject: [PATCH 6/7] Further down the rabbit hole of TimeEffect giving the answer to effective item duration. Commiting to save WIP. --- .../adapters/tests/test_aaf_adapter.py | 2 +- .../adapters/tests/tests_xges_adapter.py | 4 +- .../opentimelineio_contrib/adapters/xges.py | 2 +- src/opentime/rationalTime.h | 12 ++- src/opentime/timeRange.h | 2 +- src/opentimelineio/item.cpp | 16 +++- .../opentime_rationalTime.cpp | 2 + .../opentimelineio/adapters/cmx_3600.py | 76 ++++++++++--------- tests/sample_data/speed_effects.edl | 20 ++--- tests/sample_data/speed_effects_small.edl | 4 +- tests/test_cmx_3600_adapter.py | 8 +- 11 files changed, 86 insertions(+), 62 deletions(-) diff --git a/contrib/opentimelineio_contrib/adapters/tests/test_aaf_adapter.py b/contrib/opentimelineio_contrib/adapters/tests/test_aaf_adapter.py index e4b0c503d5..d354779c59 100644 --- a/contrib/opentimelineio_contrib/adapters/tests/test_aaf_adapter.py +++ b/contrib/opentimelineio_contrib/adapters/tests/test_aaf_adapter.py @@ -743,7 +743,7 @@ def test_read_misc_speed_effects(self): self.assertEqual(1, len(clip.effects)) effect = clip.effects[0] self.assertEqual(otio.schema.FreezeFrame, type(effect)) - self.assertEqual(0, effect.time_scalar) + self.assertEqual(8, effect.duration.value) self.assertEqual(8, clip.duration().value) clip = track[2] diff --git a/contrib/opentimelineio_contrib/adapters/tests/tests_xges_adapter.py b/contrib/opentimelineio_contrib/adapters/tests/tests_xges_adapter.py index a2b0e47ac8..60b313965a 100644 --- a/contrib/opentimelineio_contrib/adapters/tests/tests_xges_adapter.py +++ b/contrib/opentimelineio_contrib/adapters/tests/tests_xges_adapter.py @@ -65,12 +65,12 @@ GST_SECOND = 1000000000 -def _rat_tm_from_secs(val, rate=25.0): +def _rat_tm_from_secs(val, rate=1.0): """Return a RationalTime for the given timestamp (in seconds).""" return otio.opentime.from_seconds(val).rescaled_to(rate) -def _tm_range_from_secs(start, dur, rate=25.0): +def _tm_range_from_secs(start, dur, rate=1.0): """ Return a TimeRange for the given timestamp and duration (in seconds). diff --git a/contrib/opentimelineio_contrib/adapters/xges.py b/contrib/opentimelineio_contrib/adapters/xges.py index 96147d4d1b..96e9507596 100644 --- a/contrib/opentimelineio_contrib/adapters/xges.py +++ b/contrib/opentimelineio_contrib/adapters/xges.py @@ -256,7 +256,7 @@ def __init__(self, ges_obj): "{} rather than the expected 'ges' for xges".format( ges_obj.tag)) self.ges_xml = ges_obj - self.rate = 25.0 + self.rate = 1.0 @staticmethod def _findall(xmlelement, path): diff --git a/src/opentime/rationalTime.h b/src/opentime/rationalTime.h index c6bd3bb37b..e8c297a741 100644 --- a/src/opentime/rationalTime.h +++ b/src/opentime/rationalTime.h @@ -99,6 +99,14 @@ class RationalTime { std::string to_time_string() const; + RationalTime floor() const { + return RationalTime {std::floor(_value), _rate}; + } + + RationalTime round() const { + return RationalTime {std::round(_value), _rate}; + } + RationalTime const& operator+= (RationalTime other) { if (_rate < other._rate) { _value = other._value + value_rescaled_to(other._rate); @@ -167,10 +175,6 @@ class RationalTime { static RationalTime _invalid_time; static constexpr double _invalid_rate = -1; - RationalTime _floor() const { - return RationalTime {floor(_value), _rate}; - } - friend class TimeTransform; friend class TimeRange; diff --git a/src/opentime/timeRange.h b/src/opentime/timeRange.h index 691b4317a8..2e4caf5382 100644 --- a/src/opentime/timeRange.h +++ b/src/opentime/timeRange.h @@ -51,7 +51,7 @@ class TimeRange { RationalTime et = end_time_exclusive(); if ((et - _start_time.rescaled_to(_duration))._value > 1) { - return _duration._value != floor(_duration._value) ? et._floor() : + return _duration._value != floor(_duration._value) ? et.floor() : et - RationalTime(1, _duration._rate); } else { return _start_time; diff --git a/src/opentimelineio/item.cpp b/src/opentimelineio/item.cpp index 0eaf06409c..1edde940b0 100644 --- a/src/opentimelineio/item.cpp +++ b/src/opentimelineio/item.cpp @@ -52,7 +52,21 @@ TimeRange Item::trimmed_range(ErrorStatus* error_status) const { } } } - return result; + // Treat rate of 1 as special case where + // value isn't snapped to a whole number + if (result.duration().rate() == 1) { + return result; + } + else { + // after concatenating all the time effects + // performed in a conceptual space with subframes, + // we now snap the result to the nearest whole frame + // (according to rate) + return TimeRange( + result.start_time().round(), + result.duration().round() + ); + } } TimeRange Item::visible_range(ErrorStatus* error_status) const { diff --git a/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp b/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp index 40330ca61a..ea89a6c7c7 100644 --- a/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp +++ b/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp @@ -111,6 +111,8 @@ void opentime_rationalTime_bindings(py::module m) { .def_static("from_time_string", [](std::string s, double rate) { return RationalTime::from_time_string(s, rate, ErrorStatusConverter()); }, "time_string"_a, "rate"_a) + .def("floor", &RationalTime::floor) + .def("round", &RationalTime::round) .def("__str__", &opentime_python_str) .def("__repr__", &opentime_python_repr) .def(- py::self) diff --git a/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py b/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py index a7b5c4c3aa..f40427fa8e 100644 --- a/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py +++ b/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py @@ -150,35 +150,44 @@ def add_clip(self, line, comments, rate=24): src_duration = clip.duration() rec_duration = record_out - record_in - if rec_duration != src_duration: - motion = comment_handler.handled.get('motion_effect') - freeze = comment_handler.handled.get('freeze_frame') - if motion is not None or freeze is not None: - if freeze is not None: - clip.effects.append(schema.FreezeFrame(duration=rec_duration)) - # XXX remove 'FF' suffix (writing edl will add it back) - if clip.name.endswith(' FF'): - clip.name = clip.name[:-3] - elif motion is not None: - time_scalar = src_duration / rec_duration - clip.effects.append( - schema.LinearTimeWarp(time_scalar=time_scalar) - ) - elif self.ignore_timecode_mismatch: + motion = comment_handler.handled.get('motion_effect') + if motion is not None: + fps = float( + SPEED_EFFECT_RE.match(motion).group("speed") + ) + # linear time warp + if fps: + time_scalar = fps / rate + clip.effects.append( + schema.LinearTimeWarp(time_scalar=time_scalar) + ) + # freeze frame + else: + clip.effects.append(schema.FreezeFrame(duration=rec_duration)) + # XXX remove 'FF' suffix (writing edl will add it back) + if clip.name.endswith(' FF'): + clip.name = clip.name[:-3] + + effective_duration = clip.trimmed_range().duration + else: + effective_duration = src_duration + + if rec_duration != effective_duration: + if self.ignore_timecode_mismatch: # Pretend there was no problem by adjusting the record_out. # Note that we don't actually use record_out after this # point in the code, since all of the subsequent math uses # the clip's source_range. Adjusting the record_out is # just to document what the implications of ignoring the # mismatch here entails. - record_out = record_in + src_duration + record_out = record_in + effective_duration else: raise EDLParseError( - "Source and record duration don't match: {} != {}" + "Effective and record duration don't match: {} != {}" " for clip {}".format( - src_duration, + effective_duration, rec_duration, clip.name )) @@ -1037,25 +1046,11 @@ def __init__( line.source_in = clip.source_range.start_time line.source_out = clip.source_range.end_time_exclusive() - # timing_effect = _relevant_timing_effect(clip) - - # if timing_effect: - # if timing_effect.effect_name == "FreezeFrame": - # line.source_out = line.source_in + opentime.RationalTime( - # 1, - # line.source_in.rate - # ) - # elif timing_effect.effect_name == "LinearTimeWarp": - # value = ( - # clip.trimmed_range().duration.value - # / timing_effect.time_scalar - # ) - # line.source_out = ( - # line.source_in + opentime.RationalTime(value, rate)) + timing_effect = _relevant_timing_effect(clip) range_in_timeline = clip.transformed_time_range( clip.trimmed_range(), - tracks + tracks, ) line.record_in = range_in_timeline.start_time line.record_out = range_in_timeline.end_time_exclusive() @@ -1283,14 +1278,21 @@ def _generate_comment_lines( ) ) - if timing_effect and isinstance(timing_effect, schema.LinearTimeWarp): + if ( + timing_effect + and isinstance(timing_effect, (schema.FreezeFrame, schema.LinearTimeWarp)) + ): + if isinstance(timing_effect, schema.FreezeFrame): + new_rate = 0 + elif isinstance(timing_effect, schema.LinearTimeWarp): + new_rate = timing_effect.time_scalar * edl_rate lines.append( 'M2 {}\t\t{}\t\t\t{}'.format( clip.name, - timing_effect.time_scalar * edl_rate, + new_rate, opentime.to_timecode( clip.trimmed_range().start_time, - edl_rate + edl_rate, ) ) ) diff --git a/tests/sample_data/speed_effects.edl b/tests/sample_data/speed_effects.edl index c33580dbeb..45a0731e03 100644 --- a/tests/sample_data/speed_effects.edl +++ b/tests/sample_data/speed_effects.edl @@ -566,7 +566,7 @@ M2 Z682_157 000.0 01:00:10:20 000281 Z686_5A. V C 01:00:04:04 01:00:06:00 01:11:29:20 01:11:31:16 * FROM CLIP NAME: Z686_5A (LAY2) 000282 Z686_5A. V C 01:00:06:00 01:00:08:22 01:11:31:16 01:11:33:04 -M2 Z686_5A. 047.6 01:00:06:00 +M2 Z686_5A. 046.7 01:00:06:00 * FROM CLIP NAME: Z686_5A (LAY2) (47.56 FPS) 000283 Z686_7.R V C 01:00:09:15 01:00:10:07 01:11:33:04 01:11:33:20 * FROM CLIP NAME: Z686_7 (RENDER-ANIM20) @@ -951,8 +951,8 @@ M2 Z686_5A. 047.6 01:00:06:00 000473 Z694_51B V C 01:00:14:21 01:00:15:13 01:17:19:12 01:17:20:04 * FROM CLIP NAME: Z694_51B (LAY2) 000474 Z694_51B V C 01:00:15:13 01:00:16:04 01:17:20:04 01:17:20:10 -M2 Z694_51B 068.0 01:00:15:13 -* FROM CLIP NAME: Z694_51B (LAY2) (68.00 FPS) +M2 Z694_51B 060.0 01:00:15:13 +* FROM CLIP NAME: Z694_51B (LAY2) (60.00 FPS) 000475 Z694_51B V C 01:00:16:05 01:00:17:18 01:17:20:10 01:17:21:05 M2 Z694_51B 047.8 01:00:16:05 * FROM CLIP NAME: Z694_51B (LAY2) (47.75 FPS) @@ -968,15 +968,15 @@ M2 Z694_MLE 029.9 01:00:17:16 M2 Z694_MLE 035.1 01:00:18:15 * FROM CLIP NAME: Z694_56C (LAY2) (35.08 FPS) 000480 Z694_MLE V C 01:00:18:19 01:00:21:11 01:17:23:16 01:17:25:11 -M2 Z694_MLE 036.2 01:00:18:19 -* FROM CLIP NAME: Z694_56B (LAY5) (36.21 FPS) +M2 Z694_MLE 035.7 01:00:18:19 +* FROM CLIP NAME: Z694_56B (LAY5) (35.721 FPS) 000481 Z694_56D V C 01:00:21:09 01:00:23:00 01:17:25:11 01:17:27:02 * FROM CLIP NAME: Z694_56D (LAY4) 000482 Z694_MLE V C 01:00:17:07 01:00:18:09 01:17:27:02 01:17:28:04 * FROM CLIP NAME: Z694_151 (LAY3) 000483 Z694_MLE V C 01:00:18:09 01:00:19:01 01:17:28:04 01:17:28:13 -M2 Z694_MLE 045.3 01:00:18:09 -* FROM CLIP NAME: Z694_151 (LAY3) (45.33 FPS) +M2 Z694_MLE 042.7 01:00:18:09 +* FROM CLIP NAME: Z694_151 (LAY3) (42.667 FPS) 000484 Z694_MLE V C 01:00:19:01 01:00:20:01 01:17:28:13 01:17:29:13 * FROM CLIP NAME: Z694_151 (LAY3) 000485 Z694_154 V C 01:00:21:12 01:00:23:21 01:17:29:13 01:17:31:22 @@ -986,8 +986,8 @@ M2 Z694_MLE 045.3 01:00:18:09 000487 Z694_MLE V C 01:00:22:22 01:00:25:09 01:17:34:10 01:17:36:21 * FROM CLIP NAME: Z694_153 (LAY2) 000488 Z694_SHI V C 01:00:25:01 01:00:33:09 01:17:36:21 01:17:41:02 -M2 Z694_SHI 047.9 01:00:25:01 -* FROM CLIP NAME: Z694_ZZZ_1T (FX7) (47.88 FPS) +M2 Z694_SHI 047.5 01:00:25:01 +* FROM CLIP NAME: Z694_ZZZ_1T (FX7) (47.525 FPS) 000489 DEVFX_HY V C 01:00:10:04 01:00:12:13 01:17:41:02 01:17:43:11 * FROM CLIP NAME: DEVFX_ZZZ_3 (FX2) 000490 Z694_201 V C 01:00:04:11 01:00:06:10 01:17:43:11 01:17:45:10 @@ -1073,7 +1073,7 @@ M2 Z694_307 000.0 01:00:38:21 000525 Z700_303 V C 01:00:06:04 01:00:07:18 01:19:25:12 01:19:27:02 * FROM CLIP NAME: Z700_303B (LAY1) 000526 Z700_303 V C 01:00:07:18 01:00:10:03 01:19:27:02 01:19:28:05 -M2 Z700_303 052.6 01:00:07:18 +M2 Z700_303 050.7 01:00:07:18 * FROM CLIP NAME: Z700_303B (LAY1) 000527 Z700_304 V C 01:00:04:18 01:00:06:23 01:19:28:05 01:19:30:10 * FROM CLIP NAME: Z700_304 (LAY6) diff --git a/tests/sample_data/speed_effects_small.edl b/tests/sample_data/speed_effects_small.edl index 1e15298c85..1f701c3b7b 100644 --- a/tests/sample_data/speed_effects_small.edl +++ b/tests/sample_data/speed_effects_small.edl @@ -13,5 +13,5 @@ M2 Z682_157 000.0 01:00:10:20 004 Z682_157 V C 01:00:10:20 01:00:11:14 01:08:30:18 01:08:31:12 * FROM CLIP NAME: Z682_157 (LAY2) 005 Z686_5A. V C 01:00:06:00 01:00:08:22 01:11:31:16 01:11:33:04 -M2 Z686_5A. 047.6 01:00:06:00 -* FROM CLIP NAME: Z686_5A (LAY2) (47.56 FPS) +M2 Z686_5A. 046.7 01:00:06:00 +* FROM CLIP NAME: Z686_5A (LAY2) (46.66 FPS) diff --git a/tests/test_cmx_3600_adapter.py b/tests/test_cmx_3600_adapter.py index 5df259058d..feb1e30c99 100755 --- a/tests/test_cmx_3600_adapter.py +++ b/tests/test_cmx_3600_adapter.py @@ -260,7 +260,7 @@ def test_edl_round_trip_mem2disk2mem(self): metadata=md ) - cl4.effects[:] = [otio.schema.FreezeFrame()] + cl4.effects[:] = [otio.schema.FreezeFrame(duration=rt)] cl5 = otio.schema.Clip( name="test clip5 (speed)", media_reference=mr.clone(), @@ -303,7 +303,7 @@ def test_edl_round_trip_mem2disk2mem(self): # but a timing effect should raise an exception cl5.effects[:] = [otio.schema.TimeEffect()] - with self.assertRaises(otio.exceptions.NotSupportedError): + with self.assertRaises(NotImplementedError): otio.adapters.write_to_string(tl, "cmx_3600") def test_edl_round_trip_disk2mem2disk_speed_effects(self): @@ -314,6 +314,8 @@ def test_edl_round_trip_disk2mem2disk_speed_effects(self): otio.adapters.write_to_file(timeline, tmp_path) + otio.adapters.write_to_file(timeline, "/home/jonathan/blah.edl") + result = otio.adapters.read_from_file(tmp_path) # When debugging, you can use this to see the difference in the OTIO @@ -997,7 +999,7 @@ def test_speed_effects(self): self.assertTrue( clip.effects and clip.effects[0].effect_name == "LinearTimeWarp" ) - self.assertAlmostEqual(clip.effects[0].time_scalar, 1.9444444444444444) + self.assertAlmostEqual(clip.effects[0].time_scalar, 1.94583333) self.assertIsNone( clip.metadata.get("cmx_3600", {}).get("motion") From 1a8b2aa6b516934e9db46e606d64c991ffbeee0f Mon Sep 17 00:00:00 2001 From: Jonathan Hearn Date: Tue, 15 Dec 2020 16:21:04 +0000 Subject: [PATCH 7/7] Back to the previous model of an item's source_range indicating its duration in a track. So a TimeEffect actually has no effect on changing an item's duration. I still have FreezeFrame as a direct subclass of TimeEffect, rather than LinearTimeWarp - it doesn't need a time_scalar property of 0, we know that already about it. --- .../adapters/tests/test_aaf_adapter.py | 1 - .../adapters/tests/tests_xges_adapter.py | 4 +- .../opentimelineio_contrib/adapters/xges.py | 2 +- docs/tutorials/otio-serialized-schema.md | 1 - src/opentime/rationalTime.h | 16 +--- src/opentime/timeRange.h | 2 +- src/opentimelineio/freezeFrame.cpp | 14 +-- src/opentimelineio/freezeFrame.h | 18 ---- src/opentimelineio/item.cpp | 32 +------ src/opentimelineio/item.h | 6 +- src/opentimelineio/linearTimeWarp.h | 19 ---- src/opentimelineio/timeEffect.cpp | 5 -- src/opentimelineio/timeEffect.h | 3 - .../opentime_rationalTime.cpp | 3 - .../otio_serializableObjects.cpp | 14 +-- .../opentimelineio/adapters/cmx_3600.py | 37 +++++--- tests/sample_data/speed_effects.edl | 12 +-- tests/sample_data/speed_effects_small.edl | 2 +- tests/test_cmx_3600_adapter.py | 6 +- tests/test_composition.py | 89 ------------------- tests/test_effect.py | 28 +----- 21 files changed, 53 insertions(+), 261 deletions(-) diff --git a/contrib/opentimelineio_contrib/adapters/tests/test_aaf_adapter.py b/contrib/opentimelineio_contrib/adapters/tests/test_aaf_adapter.py index d354779c59..883289578e 100644 --- a/contrib/opentimelineio_contrib/adapters/tests/test_aaf_adapter.py +++ b/contrib/opentimelineio_contrib/adapters/tests/test_aaf_adapter.py @@ -743,7 +743,6 @@ def test_read_misc_speed_effects(self): self.assertEqual(1, len(clip.effects)) effect = clip.effects[0] self.assertEqual(otio.schema.FreezeFrame, type(effect)) - self.assertEqual(8, effect.duration.value) self.assertEqual(8, clip.duration().value) clip = track[2] diff --git a/contrib/opentimelineio_contrib/adapters/tests/tests_xges_adapter.py b/contrib/opentimelineio_contrib/adapters/tests/tests_xges_adapter.py index 60b313965a..a2b0e47ac8 100644 --- a/contrib/opentimelineio_contrib/adapters/tests/tests_xges_adapter.py +++ b/contrib/opentimelineio_contrib/adapters/tests/tests_xges_adapter.py @@ -65,12 +65,12 @@ GST_SECOND = 1000000000 -def _rat_tm_from_secs(val, rate=1.0): +def _rat_tm_from_secs(val, rate=25.0): """Return a RationalTime for the given timestamp (in seconds).""" return otio.opentime.from_seconds(val).rescaled_to(rate) -def _tm_range_from_secs(start, dur, rate=1.0): +def _tm_range_from_secs(start, dur, rate=25.0): """ Return a TimeRange for the given timestamp and duration (in seconds). diff --git a/contrib/opentimelineio_contrib/adapters/xges.py b/contrib/opentimelineio_contrib/adapters/xges.py index 96e9507596..96147d4d1b 100644 --- a/contrib/opentimelineio_contrib/adapters/xges.py +++ b/contrib/opentimelineio_contrib/adapters/xges.py @@ -256,7 +256,7 @@ def __init__(self, ges_obj): "{} rather than the expected 'ges' for xges".format( ges_obj.tag)) self.ges_xml = ges_obj - self.rate = 1.0 + self.rate = 25.0 @staticmethod def _findall(xmlelement, path): diff --git a/docs/tutorials/otio-serialized-schema.md b/docs/tutorials/otio-serialized-schema.md index 2f4c47e2ce..e5b6afda60 100644 --- a/docs/tutorials/otio-serialized-schema.md +++ b/docs/tutorials/otio-serialized-schema.md @@ -315,7 +315,6 @@ None ``` parameters: -- *duration*: - *effect_name*: - *metadata*: - *name*: diff --git a/src/opentime/rationalTime.h b/src/opentime/rationalTime.h index e8c297a741..00256ac3a9 100644 --- a/src/opentime/rationalTime.h +++ b/src/opentime/rationalTime.h @@ -99,14 +99,6 @@ class RationalTime { std::string to_time_string() const; - RationalTime floor() const { - return RationalTime {std::floor(_value), _rate}; - } - - RationalTime round() const { - return RationalTime {std::round(_value), _rate}; - } - RationalTime const& operator+= (RationalTime other) { if (_rate < other._rate) { _value = other._value + value_rescaled_to(other._rate); @@ -139,10 +131,6 @@ class RationalTime { RationalTime {lhs._value - rhs.value_rescaled_to(lhs._rate), lhs._rate}; } - friend double operator/ (RationalTime lhs, RationalTime rhs) { - return lhs.value_rescaled_to(1) / rhs.value_rescaled_to(1); - } - friend RationalTime operator- (RationalTime lhs) { return RationalTime {-lhs._value, lhs._rate}; } @@ -175,6 +163,10 @@ class RationalTime { static RationalTime _invalid_time; static constexpr double _invalid_rate = -1; + RationalTime _floor() const { + return RationalTime {floor(_value), _rate}; + } + friend class TimeTransform; friend class TimeRange; diff --git a/src/opentime/timeRange.h b/src/opentime/timeRange.h index 2e4caf5382..691b4317a8 100644 --- a/src/opentime/timeRange.h +++ b/src/opentime/timeRange.h @@ -51,7 +51,7 @@ class TimeRange { RationalTime et = end_time_exclusive(); if ((et - _start_time.rescaled_to(_duration))._value > 1) { - return _duration._value != floor(_duration._value) ? et.floor() : + return _duration._value != floor(_duration._value) ? et._floor() : et - RationalTime(1, _duration._rate); } else { return _start_time; diff --git a/src/opentimelineio/freezeFrame.cpp b/src/opentimelineio/freezeFrame.cpp index 85ea6bc2fd..ae709e04c6 100644 --- a/src/opentimelineio/freezeFrame.cpp +++ b/src/opentimelineio/freezeFrame.cpp @@ -3,23 +3,11 @@ namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { FreezeFrame::FreezeFrame(std::string const& name, - RationalTime duration, AnyDictionary const& metadata) - : Parent(name, "FreezeFrame", metadata), - _duration(duration) { + : Parent(name, "FreezeFrame", metadata) { } FreezeFrame::~FreezeFrame() { } -bool FreezeFrame::read_from(Reader& reader) { - return reader.read("duration", &_duration) && - Parent::read_from(reader); -} - -void FreezeFrame::write_to(Writer& writer) const { - Parent::write_to(writer); - writer.write("duration", _duration); -} - } } diff --git a/src/opentimelineio/freezeFrame.h b/src/opentimelineio/freezeFrame.h index 77a05f8692..7ef9292aab 100644 --- a/src/opentimelineio/freezeFrame.h +++ b/src/opentimelineio/freezeFrame.h @@ -2,7 +2,6 @@ #include "opentimelineio/version.h" #include "opentimelineio/timeEffect.h" -#include "opentime/rationalTime.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { @@ -16,29 +15,12 @@ class FreezeFrame : public TimeEffect { using Parent = TimeEffect; FreezeFrame(std::string const& name = std::string(), - RationalTime duration = RationalTime(), AnyDictionary const& metadata = AnyDictionary()); - RationalTime duration() const { - return _duration; - } - - void set_duration(RationalTime duration) { - _duration = duration; - } - - virtual TimeRange output_range(TimeRange input_range, ErrorStatus* error_status) const { - return TimeRange(input_range.start_time(), _duration); - } - protected: virtual ~FreezeFrame(); - virtual bool read_from(Reader&) ; - virtual void write_to(Writer&) const; - private: - RationalTime _duration; }; } } diff --git a/src/opentimelineio/item.cpp b/src/opentimelineio/item.cpp index 1edde940b0..9e7741d13a 100644 --- a/src/opentimelineio/item.cpp +++ b/src/opentimelineio/item.cpp @@ -1,6 +1,6 @@ #include "opentimelineio/item.h" #include "opentimelineio/composition.h" -#include "opentimelineio/timeEffect.h" +#include "opentimelineio/effect.h" #include "opentimelineio/marker.h" #include @@ -39,36 +39,6 @@ TimeRange Item::available_range(ErrorStatus* error_status) const { return TimeRange(); } -TimeRange Item::trimmed_range(ErrorStatus* error_status) const { - auto result = _source_range ? *_source_range : available_range(error_status); - if (*error_status) { - return result; - } - for (auto effect: _effects) { - if (auto time_effect = dynamic_cast(effect.value)) { - result = time_effect->output_range(result, error_status); - if (*error_status) { - return result; - } - } - } - // Treat rate of 1 as special case where - // value isn't snapped to a whole number - if (result.duration().rate() == 1) { - return result; - } - else { - // after concatenating all the time effects - // performed in a conceptual space with subframes, - // we now snap the result to the nearest whole frame - // (according to rate) - return TimeRange( - result.start_time().round(), - result.duration().round() - ); - } -} - TimeRange Item::visible_range(ErrorStatus* error_status) const { TimeRange result = trimmed_range(error_status); diff --git a/src/opentimelineio/item.h b/src/opentimelineio/item.h index d8ffa85f27..5a722c6741 100644 --- a/src/opentimelineio/item.h +++ b/src/opentimelineio/item.h @@ -57,8 +57,10 @@ class Item : public Composable { virtual TimeRange available_range(ErrorStatus* error_status) const; - TimeRange trimmed_range(ErrorStatus* error_status) const; - + TimeRange trimmed_range(ErrorStatus* error_status) const { + return _source_range ? *_source_range : available_range(error_status); + } + TimeRange visible_range(ErrorStatus* error_status) const; optional trimmed_range_in_parent(ErrorStatus* error_status) const; diff --git a/src/opentimelineio/linearTimeWarp.h b/src/opentimelineio/linearTimeWarp.h index d7a2ddd47b..d4335c9116 100644 --- a/src/opentimelineio/linearTimeWarp.h +++ b/src/opentimelineio/linearTimeWarp.h @@ -2,9 +2,6 @@ #include "opentimelineio/version.h" #include "opentimelineio/timeEffect.h" -#include "opentime/timeRange.h" -#include "opentime/timeTransform.h" -#include "opentime/rationalTime.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { @@ -31,22 +28,6 @@ class LinearTimeWarp : public TimeEffect { _time_scalar = time_scalar; } - virtual TimeRange output_range(TimeRange input_range, ErrorStatus* error_status) const { - return TimeTransform( - input_range.start_time(), - // TODO: Does time_scalar in LinearTimeWarp mean - // "make it this much faster" - // or "make it this much slower"? - // e.g. is 2.0 twice the speed or twice the length? - 1 / _time_scalar - ).applied_to( - TimeRange( - RationalTime(0, input_range.start_time().rate()), - input_range.duration() - ) - ); - } - protected: virtual ~LinearTimeWarp(); diff --git a/src/opentimelineio/timeEffect.cpp b/src/opentimelineio/timeEffect.cpp index 9b39f8fb80..648c73505a 100644 --- a/src/opentimelineio/timeEffect.cpp +++ b/src/opentimelineio/timeEffect.cpp @@ -11,9 +11,4 @@ TimeEffect::TimeEffect(std::string const& name, TimeEffect::~TimeEffect() { } -TimeRange TimeEffect::output_range(TimeRange input_range, ErrorStatus* error_status) const { - *error_status = ErrorStatus::NOT_IMPLEMENTED; - return TimeRange(); -} - } } diff --git a/src/opentimelineio/timeEffect.h b/src/opentimelineio/timeEffect.h index 25940d7a8f..1bd9c3a5ef 100644 --- a/src/opentimelineio/timeEffect.h +++ b/src/opentimelineio/timeEffect.h @@ -2,7 +2,6 @@ #include "opentimelineio/version.h" #include "opentimelineio/effect.h" -#include "opentime/timeRange.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION { @@ -19,8 +18,6 @@ class TimeEffect : public Effect { std::string const& effect_name = std::string(), AnyDictionary const& metadata = AnyDictionary()); - virtual TimeRange output_range(TimeRange input_range, ErrorStatus* error_status) const; - protected: virtual ~TimeEffect(); diff --git a/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp b/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp index ea89a6c7c7..27eee0da59 100644 --- a/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp +++ b/src/py-opentimelineio/opentime-bindings/opentime_rationalTime.cpp @@ -111,8 +111,6 @@ void opentime_rationalTime_bindings(py::module m) { .def_static("from_time_string", [](std::string s, double rate) { return RationalTime::from_time_string(s, rate, ErrorStatusConverter()); }, "time_string"_a, "rate"_a) - .def("floor", &RationalTime::floor) - .def("round", &RationalTime::round) .def("__str__", &opentime_python_str) .def("__repr__", &opentime_python_repr) .def(- py::self) @@ -136,7 +134,6 @@ void opentime_rationalTime_bindings(py::module m) { }) .def(py::self - py::self) .def(py::self + py::self) - .def(py::self / py::self) // The simple "py::self += py::self" returns the original, // which is not what we want here: we need this to return a new copy // to avoid mutating any additional references, since this class has complete value semantics. diff --git a/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp b/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp index 5f54067b1c..825b5469bd 100644 --- a/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp +++ b/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp @@ -545,10 +545,7 @@ static void define_effects(py::module m) { return new TimeEffect(name, effect_name, py_to_any_dictionary(metadata)); }), name_arg, "effect_name"_a = std::string(), - metadata_arg) - .def("output_range", [](TimeEffect* effect, TimeRange input_range) { - return effect->output_range(input_range, ErrorStatusHandler()); - }); + metadata_arg); py::class_>(m, "LinearTimeWarp", py::dynamic_attr()) .def(py::init([](std::string name, @@ -562,13 +559,10 @@ static void define_effects(py::module m) { .def_property("time_scalar", &LinearTimeWarp::time_scalar, &LinearTimeWarp::set_time_scalar); py::class_>(m, "FreezeFrame", py::dynamic_attr()) - .def(py::init([](std::string name, RationalTime duration, py::object metadata) { - return new FreezeFrame(name, duration, - py_to_any_dictionary(metadata)); }), + .def(py::init([](std::string name, py::object metadata) { + return new FreezeFrame(name, py_to_any_dictionary(metadata)); }), name_arg, - "duration"_a = RationalTime(), - metadata_arg) - .def_property("duration", &FreezeFrame::duration, &FreezeFrame::set_duration); + metadata_arg); } static void define_media_references(py::module m) { diff --git a/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py b/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py index f40427fa8e..9f50658cf2 100644 --- a/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py +++ b/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py @@ -148,7 +148,6 @@ def add_clip(self, line, comments, rate=24): edl_rate ) - src_duration = clip.duration() rec_duration = record_out - record_in motion = comment_handler.handled.get('motion_effect') @@ -164,16 +163,19 @@ def add_clip(self, line, comments, rate=24): ) # freeze frame else: - clip.effects.append(schema.FreezeFrame(duration=rec_duration)) + clip.effects.append(schema.FreezeFrame()) # XXX remove 'FF' suffix (writing edl will add it back) if clip.name.endswith(' FF'): clip.name = clip.name[:-3] - - effective_duration = clip.trimmed_range().duration - else: - effective_duration = src_duration - if rec_duration != effective_duration: + clip.source_range = opentime.TimeRange( + start_time=clip.source_range.start_time, + duration=rec_duration, + ) + + src_duration = clip.duration() + + if rec_duration != src_duration: if self.ignore_timecode_mismatch: # Pretend there was no problem by adjusting the record_out. # Note that we don't actually use record_out after this @@ -181,13 +183,13 @@ def add_clip(self, line, comments, rate=24): # the clip's source_range. Adjusting the record_out is # just to document what the implications of ignoring the # mismatch here entails. - record_out = record_in + effective_duration + record_out = record_in + src_duration else: raise EDLParseError( - "Effective and record duration don't match: {} != {}" + "Source and record duration don't match: {} != {}" " for clip {}".format( - effective_duration, + src_duration, rec_duration, clip.name )) @@ -1017,8 +1019,8 @@ def _relevant_timing_effect(clip): for thing in clip.effects: if thing not in effects and isinstance(thing, schema.TimeEffect): raise exceptions.NotSupportedError( - "{} Clip contains timing effects not supported by the EDL" - " adapter.\nClip: {}".format(thing, str(clip))) + "Clip contains timing effects not supported by the EDL" + " adapter.\nClip: {}".format(str(clip))) timing_effect = None if effects: @@ -1048,6 +1050,17 @@ def __init__( timing_effect = _relevant_timing_effect(clip) + if timing_effect: + if timing_effect.effect_name == "FreezeFrame": + line.source_out = line.source_in + opentime.RationalTime( + 1, + line.source_in.rate + ) + elif timing_effect.effect_name == "LinearTimeWarp": + value = clip.trimmed_range().duration.value / timing_effect.time_scalar + line.source_out = ( + line.source_in + opentime.RationalTime(value, rate)) + range_in_timeline = clip.transformed_time_range( clip.trimmed_range(), tracks, diff --git a/tests/sample_data/speed_effects.edl b/tests/sample_data/speed_effects.edl index 45a0731e03..b9d096dc11 100644 --- a/tests/sample_data/speed_effects.edl +++ b/tests/sample_data/speed_effects.edl @@ -566,7 +566,7 @@ M2 Z682_157 000.0 01:00:10:20 000281 Z686_5A. V C 01:00:04:04 01:00:06:00 01:11:29:20 01:11:31:16 * FROM CLIP NAME: Z686_5A (LAY2) 000282 Z686_5A. V C 01:00:06:00 01:00:08:22 01:11:31:16 01:11:33:04 -M2 Z686_5A. 046.7 01:00:06:00 +M2 Z686_5A. 046.7 01:00:06:00 * FROM CLIP NAME: Z686_5A (LAY2) (47.56 FPS) 000283 Z686_7.R V C 01:00:09:15 01:00:10:07 01:11:33:04 01:11:33:20 * FROM CLIP NAME: Z686_7 (RENDER-ANIM20) @@ -951,7 +951,7 @@ M2 Z686_5A. 046.7 01:00:06:00 000473 Z694_51B V C 01:00:14:21 01:00:15:13 01:17:19:12 01:17:20:04 * FROM CLIP NAME: Z694_51B (LAY2) 000474 Z694_51B V C 01:00:15:13 01:00:16:04 01:17:20:04 01:17:20:10 -M2 Z694_51B 060.0 01:00:15:13 +M2 Z694_51B 060.0 01:00:15:13 * FROM CLIP NAME: Z694_51B (LAY2) (60.00 FPS) 000475 Z694_51B V C 01:00:16:05 01:00:17:18 01:17:20:10 01:17:21:05 M2 Z694_51B 047.8 01:00:16:05 @@ -968,14 +968,14 @@ M2 Z694_MLE 029.9 01:00:17:16 M2 Z694_MLE 035.1 01:00:18:15 * FROM CLIP NAME: Z694_56C (LAY2) (35.08 FPS) 000480 Z694_MLE V C 01:00:18:19 01:00:21:11 01:17:23:16 01:17:25:11 -M2 Z694_MLE 035.7 01:00:18:19 +M2 Z694_MLE 035.7 01:00:18:19 * FROM CLIP NAME: Z694_56B (LAY5) (35.721 FPS) 000481 Z694_56D V C 01:00:21:09 01:00:23:00 01:17:25:11 01:17:27:02 * FROM CLIP NAME: Z694_56D (LAY4) 000482 Z694_MLE V C 01:00:17:07 01:00:18:09 01:17:27:02 01:17:28:04 * FROM CLIP NAME: Z694_151 (LAY3) 000483 Z694_MLE V C 01:00:18:09 01:00:19:01 01:17:28:04 01:17:28:13 -M2 Z694_MLE 042.7 01:00:18:09 +M2 Z694_MLE 042.7 01:00:18:09 * FROM CLIP NAME: Z694_151 (LAY3) (42.667 FPS) 000484 Z694_MLE V C 01:00:19:01 01:00:20:01 01:17:28:13 01:17:29:13 * FROM CLIP NAME: Z694_151 (LAY3) @@ -986,7 +986,7 @@ M2 Z694_MLE 042.7 01:00:18:09 000487 Z694_MLE V C 01:00:22:22 01:00:25:09 01:17:34:10 01:17:36:21 * FROM CLIP NAME: Z694_153 (LAY2) 000488 Z694_SHI V C 01:00:25:01 01:00:33:09 01:17:36:21 01:17:41:02 -M2 Z694_SHI 047.5 01:00:25:01 +M2 Z694_SHI 047.5 01:00:25:01 * FROM CLIP NAME: Z694_ZZZ_1T (FX7) (47.525 FPS) 000489 DEVFX_HY V C 01:00:10:04 01:00:12:13 01:17:41:02 01:17:43:11 * FROM CLIP NAME: DEVFX_ZZZ_3 (FX2) @@ -1073,7 +1073,7 @@ M2 Z694_307 000.0 01:00:38:21 000525 Z700_303 V C 01:00:06:04 01:00:07:18 01:19:25:12 01:19:27:02 * FROM CLIP NAME: Z700_303B (LAY1) 000526 Z700_303 V C 01:00:07:18 01:00:10:03 01:19:27:02 01:19:28:05 -M2 Z700_303 050.7 01:00:07:18 +M2 Z700_303 050.7 01:00:07:18 * FROM CLIP NAME: Z700_303B (LAY1) 000527 Z700_304 V C 01:00:04:18 01:00:06:23 01:19:28:05 01:19:30:10 * FROM CLIP NAME: Z700_304 (LAY6) diff --git a/tests/sample_data/speed_effects_small.edl b/tests/sample_data/speed_effects_small.edl index 1f701c3b7b..438b23cdfe 100644 --- a/tests/sample_data/speed_effects_small.edl +++ b/tests/sample_data/speed_effects_small.edl @@ -13,5 +13,5 @@ M2 Z682_157 000.0 01:00:10:20 004 Z682_157 V C 01:00:10:20 01:00:11:14 01:08:30:18 01:08:31:12 * FROM CLIP NAME: Z682_157 (LAY2) 005 Z686_5A. V C 01:00:06:00 01:00:08:22 01:11:31:16 01:11:33:04 -M2 Z686_5A. 046.7 01:00:06:00 +M2 Z686_5A. 046.7 01:00:06:00 * FROM CLIP NAME: Z686_5A (LAY2) (46.66 FPS) diff --git a/tests/test_cmx_3600_adapter.py b/tests/test_cmx_3600_adapter.py index feb1e30c99..d482d92996 100755 --- a/tests/test_cmx_3600_adapter.py +++ b/tests/test_cmx_3600_adapter.py @@ -260,7 +260,7 @@ def test_edl_round_trip_mem2disk2mem(self): metadata=md ) - cl4.effects[:] = [otio.schema.FreezeFrame(duration=rt)] + cl4.effects[:] = [otio.schema.FreezeFrame()] cl5 = otio.schema.Clip( name="test clip5 (speed)", media_reference=mr.clone(), @@ -303,7 +303,7 @@ def test_edl_round_trip_mem2disk2mem(self): # but a timing effect should raise an exception cl5.effects[:] = [otio.schema.TimeEffect()] - with self.assertRaises(NotImplementedError): + with self.assertRaises(otio.exceptions.NotSupportedError): otio.adapters.write_to_string(tl, "cmx_3600") def test_edl_round_trip_disk2mem2disk_speed_effects(self): @@ -314,8 +314,6 @@ def test_edl_round_trip_disk2mem2disk_speed_effects(self): otio.adapters.write_to_file(timeline, tmp_path) - otio.adapters.write_to_file(timeline, "/home/jonathan/blah.edl") - result = otio.adapters.read_from_file(tmp_path) # When debugging, you can use this to see the difference in the OTIO diff --git a/tests/test_composition.py b/tests/test_composition.py index c5df01d915..566b4b1e92 100755 --- a/tests/test_composition.py +++ b/tests/test_composition.py @@ -977,95 +977,6 @@ def test_range_of_child(self): with self.assertRaises(otio.exceptions.NotAChildError): otio.schema.Clip().trimmed_range_in_parent() - def test_range_of_child_with_linear_time_warp(self): - sq = otio.schema.Track( - name="foo", - children=[ - otio.schema.Clip( - name="clip1", - source_range=otio.opentime.TimeRange( - start_time=otio.opentime.RationalTime( - value=100, - rate=24 - ), - duration=otio.opentime.RationalTime( - value=50, - rate=24 - ) - ) - ), - otio.schema.Clip( - name="clip2", - source_range=otio.opentime.TimeRange( - start_time=otio.opentime.RationalTime( - value=101, - rate=24 - ), - duration=otio.opentime.RationalTime( - value=50, - rate=24 - ) - ) - ), - otio.schema.Clip( - name="clip3", - source_range=otio.opentime.TimeRange( - start_time=otio.opentime.RationalTime( - value=102, - rate=24 - ), - duration=otio.opentime.RationalTime( - value=50, - rate=24 - ) - ) - ) - ] - ) - - sq[1].effects.append( - otio.schema.LinearTimeWarp(time_scalar=0.5) - ) - - # The Track should be as long as the children summed up - self.assertEqual( - sq.duration(), - otio.opentime.RationalTime(value=200, rate=24) - ) - - # @TODO: should include time transforms - - # Sequenced items should all land end-to-end - self.assertEqual( - sq.range_of_child_at_index(0).start_time, - otio.opentime.RationalTime() - ) - self.assertEqual( - sq.range_of_child_at_index(1).start_time, - otio.opentime.RationalTime(value=50, rate=24) - ) - self.assertEqual( - sq.range_of_child_at_index(2).start_time, - otio.opentime.RationalTime(value=150, rate=24) - ) - self.assertEqual( - sq.range_of_child(sq[2]), - sq.range_of_child_at_index(2) - ) - - self.assertEqual( - sq.range_of_child_at_index(0).duration, - otio.opentime.RationalTime(value=50, rate=24) - ) - self.assertEqual( - sq.range_of_child_at_index(1).duration, - otio.opentime.RationalTime(value=100, rate=24) - ) - self.assertEqual( - sq.range_of_child_at_index(2).duration, - otio.opentime.RationalTime(value=50, rate=24) - ) - def test_range_trimmed_out(self): track = otio.schema.Track( name="top_track", diff --git a/tests/test_effect.py b/tests/test_effect.py index 1943f038bf..32311bb85c 100644 --- a/tests/test_effect.py +++ b/tests/test_effect.py @@ -84,15 +84,6 @@ def test_str(self): ) -class TestTimeEffect(unittest.TestCase): - def test_output_range(self): - ef = otio.schema.TimeEffect("dummy", "dummy", dict(foo='bar')) - with self.assertRaises(NotImplementedError): - ef.output_range( - otio.opentime.TimeRange() - ) - - class TestLinearTimeWarp(unittest.TestCase): def test_cons(self): ef = otio.schema.LinearTimeWarp("Foo", 2.5, {'foo': 'bar'}) @@ -101,29 +92,12 @@ def test_cons(self): self.assertEqual(ef.time_scalar, 2.5) self.assertEqual(ef.metadata, {"foo": "bar"}) - def test_output_range(self): - ef = otio.schema.LinearTimeWarp("Foo", 2.5, {'foo': 'bar'}) - output_range = ef.output_range( - otio.opentime.TimeRange( - start_time=otio.opentime.RationalTime(10, 25), - duration=otio.opentime.RationalTime(10, 25), - ) - ) - self.assertEqual(output_range.start_time.value, 10) - self.assertEqual(output_range.start_time.rate, 25) - self.assertEqual(output_range.duration.value, 4) - self.assertEqual(output_range.duration.rate, 25) - class TestFreezeFrame(unittest.TestCase): def test_cons(self): - ef = otio.schema.FreezeFrame( - "Foo", otio.opentime.RationalTime(10, 1), {'foo': 'bar'} - ) + ef = otio.schema.FreezeFrame("Foo", {'foo': 'bar'}) self.assertEqual(ef.effect_name, "FreezeFrame") self.assertEqual(ef.name, "Foo") - self.assertEqual(ef.duration.value, 10) - self.assertEqual(ef.duration.rate, 1) self.assertEqual(ef.metadata, {"foo": "bar"})