From aa34e9a28acd43ad6e33391c68af8b59fdc5bd17 Mon Sep 17 00:00:00 2001 From: smagnusson Date: Thu, 14 May 2026 14:46:15 -0700 Subject: [PATCH 1/2] Color equality check in Python Signed-off-by: Spencer Magnusson --- src/opentimelineio/color.h | 5 +++++ .../opentimelineio-bindings/otio_serializableObjects.cpp | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/opentimelineio/color.h b/src/opentimelineio/color.h index bb47b776c..d20bf8707 100644 --- a/src/opentimelineio/color.h +++ b/src/opentimelineio/color.h @@ -63,6 +63,11 @@ class OTIO_API_TYPE Color && lhs.to_agbr_integer() == rhs.to_agbr_integer(); } + friend bool operator!=(Color lhs, Color rhs) noexcept + { + return !(lhs == rhs); + } + OTIO_API std::string to_hex(); OTIO_API std::vector to_rgba_int_list(int base); OTIO_API unsigned int to_agbr_integer(); diff --git a/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp b/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp index 4268a4cf6..130b68e83 100644 --- a/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp +++ b/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp @@ -355,6 +355,8 @@ define_bases2(py::module m) "b"_a = 1.0, "a"_a = 1.0, py::arg_v("name"_a = std::string())) + .def(py::self == py::self) + .def(py::self != py::self) .def_property("r", &Color::r, &Color::set_r) .def_property("g", &Color::g, &Color::set_g) .def_property("b", &Color::b, &Color::set_b) From 19736ec7ea3969b0d80937e555bc45e7d7c0dd0c Mon Sep 17 00:00:00 2001 From: smagnusson Date: Thu, 14 May 2026 10:00:47 -0700 Subject: [PATCH 2/2] Marker RGB colors Signed-off-by: Spencer Magnusson --- .../otio-serialized-schema-only-fields.md | 20 +-- docs/tutorials/otio-serialized-schema.md | 46 +++---- src/opentimelineio/CORE_VERSION_MAP.cpp | 2 +- src/opentimelineio/marker.cpp | 10 +- src/opentimelineio/marker.h | 26 +--- src/opentimelineio/typeRegistry.cpp | 100 +++++++++++++++ .../otio_serializableObjects.cpp | 49 +------ .../opentimelineio/schema/__init__.py | 3 +- .../opentimelineio/schema/marker.py | 5 +- tests/baselines/empty_marker.json | 11 +- tests/sample_data/premiere_example.otio | 44 +++++-- tests/sample_data/screening_example.otio | 33 ++++- tests/test_clip.cpp | 5 +- tests/test_clip.py | 2 +- tests/test_console.py | 16 +-- tests/test_marker.py | 121 ++++++++++++++++-- 16 files changed, 354 insertions(+), 139 deletions(-) diff --git a/docs/tutorials/otio-serialized-schema-only-fields.md b/docs/tutorials/otio-serialized-schema-only-fields.md index 09a4c2b25..0d1a656df 100644 --- a/docs/tutorials/otio-serialized-schema-only-fields.md +++ b/docs/tutorials/otio-serialized-schema-only-fields.md @@ -31,15 +31,6 @@ parameters: ## Module: opentimelineio.core -### Color.1 - -parameters: -- *a* -- *b* -- *g* -- *name* -- *r* - ### Composable.1 parameters: @@ -152,6 +143,15 @@ parameters: - *name* - *source_range* +### Color.1 + +parameters: +- *a* +- *b* +- *g* +- *name* +- *r* + ### Effect.1 parameters: @@ -224,7 +224,7 @@ parameters: - *name* - *time_scalar* -### Marker.2 +### Marker.3 parameters: - *color* diff --git a/docs/tutorials/otio-serialized-schema.md b/docs/tutorials/otio-serialized-schema.md index 73919a094..e6c0d00db 100644 --- a/docs/tutorials/otio-serialized-schema.md +++ b/docs/tutorials/otio-serialized-schema.md @@ -55,27 +55,6 @@ parameters: ## Module: opentimelineio.core -### Color.1 - -*full module path*: `opentimelineio.core.Color` - -*documentation*: - -``` -:class:`Color` is a definition of red, green, blue, and alpha double floating point values, allowing - conversion between different formats. To be considered interoperable, the sRGB transfer function -encoded values, ranging between zero and one, are expected to be accurate to within 1/255 of the -intended value. Round-trip conversions may not be guaranteed outside that. This Color class is meant - for use in user interface elements, like marker or clip coloring, NOT for image pixel content. -``` - -parameters: -- *a*: -- *b*: -- *g*: -- *name*: -- *r*: - ### Composable.1 *full module path*: `opentimelineio.core.Composable` @@ -309,6 +288,27 @@ parameters: - *name*: - *source_range*: +### Color.1 + +*full module path*: `opentimelineio.schema.Color` + +*documentation*: + +``` +:class:`Color` is a definition of red, green, blue, and alpha double floating point values, allowing + conversion between different formats. To be considered interoperable, the sRGB transfer function +encoded values, ranging between zero and one, are expected to be accurate to within 1/255 of the +intended value. Round-trip conversions may not be guaranteed outside that. This Color class is meant + for use in user interface elements, like marker or clip coloring, NOT for image pixel content. +``` + +parameters: +- *a*: +- *b*: +- *g*: +- *name*: +- *r*: + ### Effect.1 *full module path*: `opentimelineio.schema.Effect` @@ -519,7 +519,7 @@ parameters: Note that adjusting the time_scalar of a :class:`~LinearTimeWarp` does not affect the duration of the item this effect is attached to. Instead it affects the speed of the media displayed within that item. -### Marker.2 +### Marker.3 *full module path*: `opentimelineio.schema.Marker` @@ -534,7 +534,7 @@ system. ``` parameters: -- *color*: Color string for this marker (for example: 'RED'), based on the :class:`~Color` enum. +- *color*: Color object assigned to this marker - *comment*: Optional comment for this marker. - *marked_range*: Range this marker applies to, relative to the :class:`.Item` this marker is attached to (e.g. the :class:`.Clip` or :class:`.Track` that owns this marker). - *metadata*: diff --git a/src/opentimelineio/CORE_VERSION_MAP.cpp b/src/opentimelineio/CORE_VERSION_MAP.cpp index 8d6e58da6..442dab527 100644 --- a/src/opentimelineio/CORE_VERSION_MAP.cpp +++ b/src/opentimelineio/CORE_VERSION_MAP.cpp @@ -221,7 +221,7 @@ const label_to_schema_version_map CORE_VERSION_MAP{ { "ImageSequenceReference", 1 }, { "Item", 1 }, { "LinearTimeWarp", 1 }, - { "Marker", 2 }, + { "Marker", 3 }, { "MediaLinker", 1 }, { "MediaReference", 1 }, { "MissingReference", 1 }, diff --git a/src/opentimelineio/marker.cpp b/src/opentimelineio/marker.cpp index 558a0fc90..28c0ff1f1 100644 --- a/src/opentimelineio/marker.cpp +++ b/src/opentimelineio/marker.cpp @@ -7,11 +7,11 @@ namespace opentimelineio { namespace OPENTIMELINEIO_VERSION_NS { Marker::Marker( - std::string const& name, - TimeRange const& marked_range, - std::string const& color, - AnyDictionary const& metadata, - std::string const& comment) + std::string const& name, + TimeRange const& marked_range, + std::optional const& color, + AnyDictionary const& metadata, + std::string const& comment) : Parent(name, metadata) , _color(color) , _marked_range(marked_range) diff --git a/src/opentimelineio/marker.h b/src/opentimelineio/marker.h index 6ec1a775f..c465bb559 100644 --- a/src/opentimelineio/marker.h +++ b/src/opentimelineio/marker.h @@ -5,6 +5,7 @@ #include "opentimelineio/serializableObjectWithMetadata.h" #include "opentimelineio/version.h" +#include "opentimelineio/color.h" namespace opentimelineio { namespace OPENTIMELINEIO_VERSION_NS { @@ -16,27 +17,12 @@ namespace opentimelineio { namespace OPENTIMELINEIO_VERSION_NS { class OTIO_API_TYPE Marker : public SerializableObjectWithMetadata { public: - /// @brief This struct provides the base set of colors. - struct Color - { - static auto constexpr pink = "PINK"; - static auto constexpr red = "RED"; - static auto constexpr orange = "ORANGE"; - static auto constexpr yellow = "YELLOW"; - static auto constexpr green = "GREEN"; - static auto constexpr cyan = "CYAN"; - static auto constexpr blue = "BLUE"; - static auto constexpr purple = "PURPLE"; - static auto constexpr magenta = "MAGENTA"; - static auto constexpr black = "BLACK"; - static auto constexpr white = "WHITE"; - }; /// @brief This struct provides the Marker schema. struct Schema { static auto constexpr name = "Marker"; - static int constexpr version = 2; + static int constexpr version = 3; }; using Parent = SerializableObjectWithMetadata; @@ -51,15 +37,15 @@ class OTIO_API_TYPE Marker : public SerializableObjectWithMetadata OTIO_API Marker( std::string const& name = std::string(), TimeRange const& marked_range = TimeRange(), - std::string const& color = Color::green, + std::optional const& color = Color::green, AnyDictionary const& metadata = AnyDictionary(), std::string const& comment = std::string()); /// @brief Return the marker color. - std::string color() const noexcept { return _color; } + std::optional color() const noexcept { return _color; } /// @brief Set the marker color. - void set_color(std::string const& color) { _color = color; } + void set_color(std::optional const& color) { _color = color; } /// @brief Return the marker time range. TimeRange marked_range() const noexcept { return _marked_range; } @@ -83,7 +69,7 @@ class OTIO_API_TYPE Marker : public SerializableObjectWithMetadata void write_to(Writer&) const override; private: - std::string _color; + std::optional _color; TimeRange _marked_range; std::string _comment; }; diff --git a/src/opentimelineio/typeRegistry.cpp b/src/opentimelineio/typeRegistry.cpp index 8e4f012f2..395079629 100644 --- a/src/opentimelineio/typeRegistry.cpp +++ b/src/opentimelineio/typeRegistry.cpp @@ -29,7 +29,11 @@ #include "opentimelineio/unknownSchema.h" #include "stringUtils.h" +#include #include +#include +#include +#include #include namespace opentimelineio { namespace OPENTIMELINEIO_VERSION_NS { @@ -96,6 +100,62 @@ TypeRegistry::TypeRegistry() d->erase("range"); }); + // 2 - 3 + register_upgrade_function(Marker::Schema::name, 3, [](AnyDictionary* d) { + // get color name + std::string color_name_v2; + if (d->get_if_set("color", &color_name_v2)) + { + static const std::map color_map = { + { "PINK", Color::pink }, + { "RED", Color::red }, + { "ORANGE", Color::orange }, + { "YELLOW", Color::yellow }, + { "GREEN", Color::green }, + { "CYAN", Color::cyan }, + { "BLUE", Color::blue }, + { "MAGENTA", Color::magenta }, + { "PURPLE", Color::purple }, + { "BLACK", Color::black }, + { "WHITE", Color::white }, + { "TRANSPARENT", Color::transparent } + }; + + // make copy of color_name_v2, for uppercase change to be separate + std::string color_name_v2_upper = color_name_v2; + + // force color name to uppercase for lookup + // since v2 color names were case-insensitive + std::transform( + color_name_v2_upper.begin(), + color_name_v2_upper.end(), + color_name_v2_upper.begin(), + [](unsigned char c) { return std::toupper(c); } + ); + + // if all-caps name matches a known color, convert to color with r,g,b,a, and name fields + auto it = color_map.find(color_name_v2_upper); + Color color_match = Color::white; + std::string color_match_name = ""; + if (it != color_map.end()) { // match found + color_match = it->second; + color_match_name = color_match.name(); + } + else { // no match, default to white but keep original name + color_match = Color::white; + color_match_name = color_name_v2; + } + + (*d)["color"] = Color( + color_match.r(), + color_match.g(), + color_match.b(), + color_match.a(), + color_match_name + ); + } + }); + register_upgrade_function(Clip::Schema::name, 2, [](AnyDictionary* d) { auto media_ref = (*d)["media_reference"]; @@ -116,6 +176,46 @@ TypeRegistry::TypeRegistry() d->erase("media_reference"); }); + // 3->2 + register_downgrade_function(Marker::Schema::name, 3, [](AnyDictionary* d) { + AnyDictionary color_dict; + + if (d->get_if_set("color", &color_dict)) + { + std::string color_name = ""; + color_dict.get_if_set("name", &color_name); + + // if the name matches case-insensitive to a known color, + // set the color an all-caps version of that name + if (!color_name.empty()) + { + // Convert to uppercase for comparison and storage + std::string upper_name = color_name; + std::transform( + upper_name.begin(), + upper_name.end(), + upper_name.begin(), + [](unsigned char c) { return std::toupper(c); } + ); + + // Known color names - these are the valid color enum values + static const std::set known_colors = { + "RED", "GREEN", "BLUE", "YELLOW", "CYAN", "MAGENTA", + "PINK", "ORANGE", "PURPLE", "BLACK", "WHITE", "TRANSPARENT" + }; + + if (known_colors.find(upper_name) != known_colors.end()) + { + // remove color object and replace with color name string + (*d)["color"] = upper_name; + } + else { // otherwise, keep name as-is + (*d)["color"] = color_name; + } + } + } + }); + // 2->1 register_downgrade_function(Clip::Schema::name, 2, [](AnyDictionary* d) { AnyDictionary mrefs; diff --git a/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp b/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp index 130b68e83..dfe8f59fd 100644 --- a/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp +++ b/src/py-opentimelineio/opentimelineio-bindings/otio_serializableObjects.cpp @@ -433,11 +433,11 @@ A marker indicates a marked range of time on an item in a timeline, usually with The marked range may have a zero duration. The marked range is in the owning item's time coordinate system. )docstring") .def( - py::init([](std::string name, - TimeRange marked_range, - std::string const& color, - py::object metadata, - std::string const& comment) { + py::init([](std::string name, + TimeRange marked_range, + std::optional color, + py::object metadata, + std::string const& comment) { return new Marker( name, marked_range, @@ -447,14 +447,14 @@ The marked range may have a zero duration. The marked range is in the owning ite }), py::arg_v("name"_a = std::string()), "marked_range"_a = TimeRange(), - "color"_a = std::string(Marker::Color::red), + "color"_a = Color::red, py::arg_v("metadata"_a = py::none()), "comment"_a = std::string()) .def_property( "color", &Marker::color, &Marker::set_color, - "Color string for this marker (for example: 'RED'), based on the :class:`~Color` enum.") + "Color object assigned to this marker") .def_property( "marked_range", &Marker::marked_range, @@ -466,41 +466,6 @@ The marked range may have a zero duration. The marked range is in the owning ite &Marker::set_comment, "Optional comment for this marker."); - py::class_(marker_class, "Color") - .def_property_readonly_static( - "PINK", - [](py::object /* self */) { return Marker::Color::pink; }) - .def_property_readonly_static( - "RED", - [](py::object /* self */) { return Marker::Color::red; }) - .def_property_readonly_static( - "ORANGE", - [](py::object /* self */) { return Marker::Color::orange; }) - .def_property_readonly_static( - "YELLOW", - [](py::object /* self */) { return Marker::Color::yellow; }) - .def_property_readonly_static( - "GREEN", - [](py::object /* self */) { return Marker::Color::green; }) - .def_property_readonly_static( - "CYAN", - [](py::object /* self */) { return Marker::Color::cyan; }) - .def_property_readonly_static( - "BLUE", - [](py::object /* self */) { return Marker::Color::blue; }) - .def_property_readonly_static( - "PURPLE", - [](py::object /* self */) { return Marker::Color::purple; }) - .def_property_readonly_static( - "MAGENTA", - [](py::object /* self */) { return Marker::Color::magenta; }) - .def_property_readonly_static( - "BLACK", - [](py::object /* self */) { return Marker::Color::black; }) - .def_property_readonly_static("WHITE", [](py::object /* self */) { - return Marker::Color::white; - }); - using SerializableCollectionIterator = ContainerIterator; py::class_( diff --git a/src/py-opentimelineio/opentimelineio/schema/__init__.py b/src/py-opentimelineio/opentimelineio/schema/__init__.py index c7fc31bfc..626304df1 100644 --- a/src/py-opentimelineio/opentimelineio/schema/__init__.py +++ b/src/py-opentimelineio/opentimelineio/schema/__init__.py @@ -8,6 +8,7 @@ from .. _otio import ( Box2d, Clip, + Color, Effect, TimeEffect, LinearTimeWarp, @@ -26,7 +27,7 @@ V2d, ) -MarkerColor = Marker.Color +MarkerColor = Color # for backwards compatibility TrackKind = Track.Kind TransitionTypes = Transition.Type NeighborGapPolicy = Track.NeighborGapPolicy diff --git a/src/py-opentimelineio/opentimelineio/schema/marker.py b/src/py-opentimelineio/opentimelineio/schema/marker.py index af1809018..2f3d66d16 100644 --- a/src/py-opentimelineio/opentimelineio/schema/marker.py +++ b/src/py-opentimelineio/opentimelineio/schema/marker.py @@ -7,9 +7,10 @@ @add_method(_otio.Marker) def __str__(self): - return "Marker({}, {}, {})".format( + return "Marker({}, {}, {}, {})".format( str(self.name), str(self.marked_range), + str(self.color), str(self.metadata), ) @@ -20,10 +21,12 @@ def __repr__(self): "otio.schema.Marker(" "name={}, " "marked_range={}, " + "color={}, " "metadata={}" ")".format( repr(self.name), repr(self.marked_range), + repr(self.color), repr(self.metadata), ) ) diff --git a/tests/baselines/empty_marker.json b/tests/baselines/empty_marker.json index cfee65e75..4e6222e01 100644 --- a/tests/baselines/empty_marker.json +++ b/tests/baselines/empty_marker.json @@ -1,8 +1,15 @@ { - "OTIO_SCHEMA" : "Marker.2", + "OTIO_SCHEMA" : "Marker.3", "metadata" : {}, "name" : "", - "color" : "RED", + "color" : { + "OTIO_SCHEMA" : "Color.1", + "r" : 1.0, + "g" : 0.0, + "b" : 0.0, + "a" : 1.0, + "name" : "Red" + }, "comment" : "", "marked_range" : { "FROM_TEST_FILE" : "empty_timerange.json" diff --git a/tests/sample_data/premiere_example.otio b/tests/sample_data/premiere_example.otio index 17f96ee78..beb99b80f 100644 --- a/tests/sample_data/premiere_example.otio +++ b/tests/sample_data/premiere_example.otio @@ -124,14 +124,21 @@ "effects": [], "markers": [ { - "OTIO_SCHEMA": "Marker.2", + "OTIO_SCHEMA": "Marker.3", "metadata": { "fcp_xml": { "comment": "so, this happened" } }, "name": "My MArker 1", - "color": "RED", + "color": { + "OTIO_SCHEMA" : "Color.1", + "r" : 1.0, + "g" : 0.0, + "b" : 0.0, + "a" : 1.0, + "name" : "RED" + }, "marked_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { @@ -147,14 +154,21 @@ } }, { - "OTIO_SCHEMA": "Marker.2", + "OTIO_SCHEMA": "Marker.3", "metadata": { "fcp_xml": { "comment": "fsfsfs" } }, "name": "dsf", - "color": "RED", + "color": { + "OTIO_SCHEMA" : "Color.1", + "r" : 1.0, + "g" : 0.0, + "b" : 0.0, + "a" : 1.0, + "name" : "RED" + }, "marked_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { @@ -170,14 +184,21 @@ } }, { - "OTIO_SCHEMA": "Marker.2", + "OTIO_SCHEMA": "Marker.3", "metadata": { "fcp_xml": { "comment": null } }, "name": "", - "color": "RED", + "color": { + "OTIO_SCHEMA" : "Color.1", + "r" : 1.0, + "g" : 0.0, + "b" : 0.0, + "a" : 1.0, + "name" : "RED" + }, "marked_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { @@ -699,14 +720,21 @@ "effects": [], "markers": [ { - "OTIO_SCHEMA": "Marker.2", + "OTIO_SCHEMA": "Marker.3", "metadata": { "fcp_xml": { "comment": null } }, "name": "", - "color": "RED", + "color": { + "OTIO_SCHEMA" : "Color.1", + "r" : 1.0, + "g" : 0.0, + "b" : 0.0, + "a" : 1.0, + "name" : "RED" + }, "marked_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { diff --git a/tests/sample_data/screening_example.otio b/tests/sample_data/screening_example.otio index dff5649e5..19d97b9ce 100644 --- a/tests/sample_data/screening_example.otio +++ b/tests/sample_data/screening_example.otio @@ -174,14 +174,21 @@ "effects": [], "markers": [ { - "OTIO_SCHEMA": "Marker.2", + "OTIO_SCHEMA": "Marker.3", "metadata": { "cmx_3600": { "color": "RED" } }, "name": "ANIM FIX NEEDED", - "color": "RED", + "color": { + "OTIO_SCHEMA" : "Color.1", + "r" : 1.0, + "g" : 0.0, + "b" : 0.0, + "a" : 1.0, + "name" : "RED" + }, "marked_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { @@ -197,14 +204,21 @@ } }, { - "OTIO_SCHEMA": "Marker.2", + "OTIO_SCHEMA": "Marker.3", "metadata": { "cmx_3600": { "color": "PINK" } }, "name": "ANIM FIX NEEDED", - "color": "PINK", + "color": { + "OTIO_SCHEMA" : "Color.1", + "r" : 1.0, + "g" : 0.0, + "b" : 1.0, + "a" : 1.0, + "name" : "PINK" + }, "marked_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { @@ -335,14 +349,21 @@ "effects": [], "markers": [ { - "OTIO_SCHEMA": "Marker.2", + "OTIO_SCHEMA": "Marker.3", "metadata": { "cmx_3600": { "color": "GREEN" } }, "name": "", - "color": "GREEN", + "color": { + "OTIO_SCHEMA" : "Color.1", + "r" : 0.0, + "g" : 1.0, + "b" : 0.0, + "a" : 1.0, + "name" : "GREEN" + }, "marked_range": { "OTIO_SCHEMA": "TimeRange.1", "duration": { diff --git a/tests/test_clip.cpp b/tests/test_clip.cpp index e7c10ed07..029b97eb9 100644 --- a/tests/test_clip.cpp +++ b/tests/test_clip.cpp @@ -3,6 +3,7 @@ #include "utils.h" +#include #include #include #include @@ -152,7 +153,7 @@ main(int argc, char** argv) time_scalar)); std::vector effects = { ltw }; - static constexpr auto red = Marker::Color::red; + static const auto red = Color::red; SerializableObject::Retainer m( new Marker(LinearTimeWarp::Schema::name, TimeRange(), red)); @@ -248,7 +249,7 @@ main(int argc, char** argv) assertEqual(clip->markers().size(), markers.size()); auto marker = dynamic_cast(clip->markers().front().value); - assertEqual(marker->color().c_str(), red); + assertEqual(marker->color()->name(), red.name()); }); // test to ensure null error_status pointers are correctly handled diff --git a/tests/test_clip.py b/tests/test_clip.py index 83ada7b4e..e90508bdb 100644 --- a/tests/test_clip.py +++ b/tests/test_clip.py @@ -27,7 +27,7 @@ def test_cons(self): effects = [] effects.append(ltw) - red = otio.schema.MarkerColor.RED + red = otio.core.Color.RED m = otio.schema.Marker( name="red_marker", color=red) markers = [] diff --git a/tests/test_console.py b/tests/test_console.py index 8188c51e3..332e7fe0e 100755 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -351,10 +351,10 @@ def test_list_markers(self): out, err = self.run_test() self.assertEqual( ("TIMELINE: sc01_sh010_layerA\n" - " MARKER: global: 00:00:03:23 local: 00:00:03:23 duration: 0.0 color: RED name: My MArker 1\n" # noqa: E501 line too long - " MARKER: global: 00:00:16:12 local: 00:00:16:12 duration: 0.0 color: RED name: dsf\n" # noqa: E501 line too long - " MARKER: global: 00:00:09:28 local: 00:00:09:28 duration: 0.0 color: RED name: \n" # noqa: E501 line too long - " MARKER: global: 00:00:13:05 local: 00:00:02:13 duration: 0.0 color: RED name: \n"), # noqa: E501 line too long + " MARKER: global: 00:00:03:23 local: 00:00:03:23 duration: 0.0 color: Color('RED', (1.0, 0.0, 0.0, 1.0)) name: My MArker 1\n" # noqa: E501 line too long + " MARKER: global: 00:00:16:12 local: 00:00:16:12 duration: 0.0 color: Color('RED', (1.0, 0.0, 0.0, 1.0)) name: dsf\n" # noqa: E501 line too long + " MARKER: global: 00:00:09:28 local: 00:00:09:28 duration: 0.0 color: Color('RED', (1.0, 0.0, 0.0, 1.0)) name: \n" # noqa: E501 line too long + " MARKER: global: 00:00:13:05 local: 00:00:02:13 duration: 0.0 color: Color('RED', (1.0, 0.0, 0.0, 1.0)) name: \n"), # noqa: E501 line too long out) def test_list_tracks_and_clips(self): @@ -413,9 +413,9 @@ def test_list_tracks_and_clips_and_media_and_markers(self): out, err = self.run_test() self.assertEqual( ("TIMELINE: sc01_sh010_layerA\n" - " MARKER: global: 00:00:03:23 local: 00:00:03:23 duration: 0.0 color: RED name: My MArker 1\n" # noqa E501 line too long - " MARKER: global: 00:00:16:12 local: 00:00:16:12 duration: 0.0 color: RED name: dsf\n" # noqa E501 line too long - " MARKER: global: 00:00:09:28 local: 00:00:09:28 duration: 0.0 color: RED name: \n" # noqa E501 line too long + " MARKER: global: 00:00:03:23 local: 00:00:03:23 duration: 0.0 color: Color('RED', (1.0, 0.0, 0.0, 1.0)) name: My MArker 1\n" # noqa E501 line too long + " MARKER: global: 00:00:16:12 local: 00:00:16:12 duration: 0.0 color: Color('RED', (1.0, 0.0, 0.0, 1.0)) name: dsf\n" # noqa E501 line too long + " MARKER: global: 00:00:09:28 local: 00:00:09:28 duration: 0.0 color: Color('RED', (1.0, 0.0, 0.0, 1.0)) name: \n" # noqa E501 line too long "TRACK: (Video)\n" " CLIP: sc01_sh010_anim.mov\n" " MEDIA: file://localhost/D%3a/media/sc01_sh010_anim.mov\n" @@ -426,7 +426,7 @@ def test_list_tracks_and_clips_and_media_and_markers(self): " MEDIA: file://localhost/D%3a/media/sc01_sh020_anim.mov\n" " CLIP: sc01_sh030_anim.mov\n" " MEDIA: file://localhost/D%3a/media/sc01_sh030_anim.mov\n" - " MARKER: global: 00:00:13:05 local: 00:00:02:13 duration: 0.0 color: RED name: \n" # noqa E501 line too long + " MARKER: global: 00:00:13:05 local: 00:00:02:13 duration: 0.0 color: Color('RED', (1.0, 0.0, 0.0, 1.0)) name: \n" # noqa E501 line too long "TRACK: (Video)\n" " CLIP: test_title\n" " MEDIA: None\n" diff --git a/tests/test_marker.py b/tests/test_marker.py index c5c256dfa..6153d70a3 100755 --- a/tests/test_marker.py +++ b/tests/test_marker.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project +import json import unittest import opentimelineio as otio @@ -17,19 +18,27 @@ def test_cons(self): m = otio.schema.Marker( name="marker_1", marked_range=tr, - color=otio.schema.MarkerColor.GREEN, + color=otio.core.Color.GREEN, metadata={'foo': 'bar'} ) self.assertEqual(m.name, 'marker_1') self.assertEqual(m.metadata['foo'], 'bar') self.assertEqual(m.marked_range, tr) - self.assertEqual(m.color, otio.schema.MarkerColor.GREEN) + self.assertEqual(m.color, otio.core.Color.GREEN) encoded = otio.adapters.otio_json.write_to_string(m) decoded = otio.adapters.otio_json.read_from_string(encoded) self.assertIsOTIOEquivalentTo(m, decoded) - def test_upgrade(self): + def test_set_color(self): + m1 = otio.schema.Marker(name='marker_1') + m1.color = otio.core.Color.RED + m2 = otio.schema.Marker(name='marker_2') + m2.color = otio.core.Color.GREEN + + self.assertNotEqual(m1.color, m2.color) + + def test_upgrade_from_1(self): src = """ { "OTIO_SCHEMA" : "Marker.1", @@ -60,6 +69,100 @@ def test_upgrade(self): ) ) + def test_upgrade_from_2(self): + """Test color conversion from a name to Color object. + + If a known color constant, it should convert to it. + Otherwise, preserve the color name in the Color object. + """ + for color in (otio.core.Color.RED, otio.core.Color.GREEN, otio.core.Color.BLUE): + src = """ + { + "OTIO_SCHEMA" : "Marker.1", + "metadata" : {}, + "name" : null, + "range" : { + "OTIO_SCHEMA" : "TimeRange.1", + "start_time" : { + "OTIO_SCHEMA" : "RationalTime.1", + "rate" : 5, + "value" : 0 + }, + "duration" : { + "OTIO_SCHEMA" : "RationalTime.1", + "rate" : 5, + "value" : 0 + } + }, + "color": "{REPLACE_COLOR}" + } + """.replace("{REPLACE_COLOR}", color.name) + marker = otio.adapters.read_from_string(src, "otio_json") + self.assertEqual( + marker.color, + color + ) + + src = """ + { + "OTIO_SCHEMA" : "Marker.1", + "metadata" : {}, + "name" : null, + "range" : { + "OTIO_SCHEMA" : "TimeRange.1", + "start_time" : { + "OTIO_SCHEMA" : "RationalTime.1", + "rate" : 5, + "value" : 0 + }, + "duration" : { + "OTIO_SCHEMA" : "RationalTime.1", + "rate" : 5, + "value" : 0 + } + }, + "color": "something unknown" + } + """ + marker = otio.adapters.read_from_string(src, "otio_json") + self.assertEqual( + marker.color.name, + "something unknown" + ) + + def test_downgrade_to_2(self): + """Test color conversion from Color object to a name. + + If a known color constant, use its uppercase name. + Otherwise, preserve the name. + """ + for color in (otio.core.Color.RED, otio.core.Color.GREEN, otio.core.Color.BLUE): + marker = otio.schema.Marker(color=color) + marker_str = otio.adapters.write_to_string( + marker, + "otio_json", + target_schema_versions={"Marker": 2} + ) + marker_in_json = json.loads(marker_str) + self.assertEqual( + marker_in_json["color"], + color.name.upper() + ) + + marker = otio.schema.Marker( + color=otio.core.Color(0.5, 0.5, 0.5, 1.0, "something unknown") + ) + marker_str = otio.adapters.write_to_string( + marker, + "otio_json", + target_schema_versions={"Marker": 2} + ) + marker_in_json = json.loads(marker_str) + self.assertEqual( + marker_in_json["color"], + "something unknown" + ) + def test_equality(self): m = otio.schema.Marker() bo = otio.core.Item() @@ -75,14 +178,14 @@ def test_repr(self): m = otio.schema.Marker( name="marker_1", marked_range=tr, - color=otio.schema.MarkerColor.GREEN, + color=otio.core.Color.GREEN, metadata={'foo': 'bar'} ) expected = ( "otio.schema.Marker(name='marker_1', " - "marked_range={}, metadata={})".format( - repr(tr), repr(m.metadata) + "marked_range={}, color={}, metadata={})".format( + repr(tr), repr(m.color), repr(m.metadata) ) ) @@ -137,12 +240,12 @@ def test_str(self): m = otio.schema.Marker( name="marker_1", marked_range=tr, - color=otio.schema.MarkerColor.GREEN, + color=otio.core.Color.GREEN, metadata={'foo': 'bar'} ) - expected = 'Marker(marker_1, {}, {})'.format( - str(tr), str(m.metadata) + expected = 'Marker(marker_1, {}, {}, {})'.format( + str(tr), str(m.color), str(m.metadata) ) self.assertEqual(str(m), expected)