diff --git a/src/opentimelineio/imageSequenceReference.cpp b/src/opentimelineio/imageSequenceReference.cpp index c5f1d2055..10ac8f465 100644 --- a/src/opentimelineio/imageSequenceReference.cpp +++ b/src/opentimelineio/imageSequenceReference.cpp @@ -69,7 +69,15 @@ ImageSequenceReference::ImageSequenceReference(std::string const& target_url_bas std::string ImageSequenceReference::target_url_for_image_number(int const image_number, ErrorStatus* error_status) const { - if (image_number >= this->number_of_images_in_sequence()) { + if (_rate == 0) { + *error_status = ErrorStatus(ErrorStatus::ILLEGAL_INDEX, "Zero rate sequence has no frames."); + return std::string(); + } + else if (!this->available_range().has_value() || this->available_range().value().duration().value() == 0) { + *error_status = ErrorStatus(ErrorStatus::ILLEGAL_INDEX, "Zero duration sequences has no frames."); + return std::string(); + } + else if (image_number >= this->number_of_images_in_sequence()) { *error_status = ErrorStatus(ErrorStatus::ILLEGAL_INDEX); return std::string(); } diff --git a/src/py-opentimelineio/opentimelineio-bindings/otio_errorStatusHandler.cpp b/src/py-opentimelineio/opentimelineio-bindings/otio_errorStatusHandler.cpp index 0475b9903..0912c5ae5 100644 --- a/src/py-opentimelineio/opentimelineio-bindings/otio_errorStatusHandler.cpp +++ b/src/py-opentimelineio/opentimelineio-bindings/otio_errorStatusHandler.cpp @@ -28,12 +28,12 @@ ErrorStatusHandler::~ErrorStatusHandler() noexcept(false) { if (!error_status) { return; } - + switch(error_status.outcome) { case ErrorStatus::NOT_IMPLEMENTED: - throw py::not_implemented_error(); + throw py::not_implemented_error(error_status.details); case ErrorStatus::ILLEGAL_INDEX: - throw py::index_error(); + throw py::index_error(error_status.details); case ErrorStatus::KEY_NOT_FOUND: throw py::key_error(error_status.details); case ErrorStatus::INTERNAL_ERROR: diff --git a/tests/test_image_sequence_reference.py b/tests/test_image_sequence_reference.py index 9d682ecf6..a70400ffb 100644 --- a/tests/test_image_sequence_reference.py +++ b/tests/test_image_sequence_reference.py @@ -611,3 +611,53 @@ def test_negative_frame_numbers(self): ref.target_url_for_image_number(i), "file:///show/seq/shot/rndr/show_shot.{:04}.exr".format(i - 1), ) + + def test_target_url_for_image_number_with_missing_timing_info(self): + ref = otio.schema.ImageSequenceReference( + "file:///show/seq/shot/rndr/", + "show_shot.", + ".exr", + frame_zero_padding=4, + start_frame=1, + frame_step=1, + rate=24, + ) + + # Make sure the right error and a useful message raised when + # source_range is either un-set or zero duration. + with self.assertRaises(IndexError) as exception_manager: + ref.target_url_for_image_number(0) + + self.assertEqual( + str(exception_manager.exception), + "Zero duration sequences has no frames.", + ) + + ref.available_range = otio.opentime.TimeRange( + otio.opentime.RationalTime(12, 24), + otio.opentime.RationalTime(0, 1), + ) + + with self.assertRaises(IndexError) as exception_manager: + ref.target_url_for_image_number(0) + + self.assertEqual( + str(exception_manager.exception), + "Zero duration sequences has no frames.", + ) + + # Set the duration and make sure a similarly useful message comes + # when rate is un-set. + ref.available_range = otio.opentime.TimeRange( + otio.opentime.RationalTime(12, 24), + otio.opentime.RationalTime(48, 24), + ) + ref.rate = 0 + + with self.assertRaises(IndexError) as exception_manager: + ref.target_url_for_image_number(0) + + self.assertEqual( + str(exception_manager.exception), + "Zero rate sequence has no frames.", + )