diff --git a/bin/toucan-render/App.cpp b/bin/toucan-render/App.cpp index b5bc6e8..d45f63f 100644 --- a/bin/toucan-render/App.cpp +++ b/bin/toucan-render/App.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -147,22 +148,11 @@ namespace toucan const size_t outputNumberPadding = getNumberPadding(outputSplit.second); // Open the timeline. - OTIO_NS::ErrorStatus errorStatus; - auto timeline = OTIO_NS::SerializableObject::Retainer( - dynamic_cast(OTIO_NS::Timeline::from_json_file(inputPath.string(), &errorStatus))); - if (!timeline) - { - std::stringstream ss; - ss << inputPath.string() << ": " << errorStatus.full_description << std::endl; - throw std::runtime_error(ss.str()); - } + auto timeline = std::make_shared(inputPath); - // Compute time values. - const OTIO_NS::RationalTime startTime = timeline->global_start_time().has_value() ? - timeline->global_start_time().value() : - OTIO_NS::RationalTime(0.0, timeline->duration().rate()); - const OTIO_NS::TimeRange timeRange(startTime, timeline->duration()); - const OTIO_NS::RationalTime timeInc(1.0, timeline->duration().rate()); + // Get time values. + const OTIO_NS::TimeRange& timeRange = timeline->getTimeRange(); + const OTIO_NS::RationalTime timeInc(1.0, timeRange.duration().rate()); const int frames = timeRange.duration().value(); // Create the image graph. @@ -235,7 +225,7 @@ namespace toucan // Render the timeline frames. int filmstripX = 0; - for (OTIO_NS::RationalTime time = startTime; + for (OTIO_NS::RationalTime time = timeRange.start_time(); time <= timeRange.end_time_inclusive(); time += timeInc) { diff --git a/cmake/SuperBuild/Builddtk-deps.cmake b/cmake/SuperBuild/Builddtk-deps.cmake index 9bca16d..5f7dc03 100644 --- a/cmake/SuperBuild/Builddtk-deps.cmake +++ b/cmake/SuperBuild/Builddtk-deps.cmake @@ -1,7 +1,7 @@ include(ExternalProject) set(dtk_GIT_REPOSITORY "https://github.com/darbyjohnston/dtk.git") -set(dtk_GIT_TAG "fe708a58fda5dc65141e492ae5fa24b199ff3edc") +set(dtk_GIT_TAG "ff2b7dcbbaf6eb8dc3568922f7c84b7c34c2c8dd") set(dtk-deps_ARGS ${toucan_EXTERNAL_PROJECT_ARGS} diff --git a/cmake/SuperBuild/Builddtk.cmake b/cmake/SuperBuild/Builddtk.cmake index 9e6ae52..b16ce14 100644 --- a/cmake/SuperBuild/Builddtk.cmake +++ b/cmake/SuperBuild/Builddtk.cmake @@ -1,7 +1,7 @@ include(ExternalProject) set(dtk_GIT_REPOSITORY "https://github.com/darbyjohnston/dtk.git") -set(dtk_GIT_TAG "fe708a58fda5dc65141e492ae5fa24b199ff3edc") +set(dtk_GIT_TAG "ff2b7dcbbaf6eb8dc3568922f7c84b7c34c2c8dd") set(dtk_DEPS dtk-deps) set(dtk_ARGS diff --git a/lib/toucan/CMakeLists.txt b/lib/toucan/CMakeLists.txt index d467496..1df1244 100644 --- a/lib/toucan/CMakeLists.txt +++ b/lib/toucan/CMakeLists.txt @@ -10,6 +10,7 @@ set(HEADERS PropertySet.h Read.h TimeWarp.h + Timeline.h Util.h) set(HEADERS_PRIVATE ImageEffect_p.h) @@ -25,6 +26,7 @@ set(SOURCE PropertySet.cpp Read.cpp TimeWarp.cpp + Timeline.cpp Util.cpp) if(WIN32) list(APPEND SOURCE @@ -32,7 +34,8 @@ if(WIN32) UtilWin32.cpp) else() list(APPEND SOURCE - PluginUnix.cpp) + PluginUnix.cpp + UtilUnix.cpp) endif() add_library(toucan ${HEADERS} ${HEADERS_PRIVATE} ${SOURCE}) diff --git a/lib/toucan/ImageGraph.cpp b/lib/toucan/ImageGraph.cpp index da8f328..ee38eaa 100644 --- a/lib/toucan/ImageGraph.cpp +++ b/lib/toucan/ImageGraph.cpp @@ -24,57 +24,60 @@ namespace toucan ImageGraph::ImageGraph( const std::filesystem::path& path, - const OTIO_NS::SerializableObject::Retainer& timeline, + const std::shared_ptr& timeline, const ImageGraphOptions& options) : _path(path), _timeline(timeline), + _timeRange(timeline->getTimeRange()), _options(options) { - const auto globalStartTime = timeline->global_start_time(); - _globalStartTime = globalStartTime.has_value() ? globalStartTime.value() : - OTIO_NS::RationalTime(0.0, timeline->duration().rate()); - _loadCache.setMax(10); - // Get the image size from the first clip. - for (auto clip : _timeline->find_clips()) + // Get the image size from the first video clip. + for (auto track : _timeline->otio()->find_children()) { - if (auto externalRef = dynamic_cast(clip->media_reference())) + if (OTIO_NS::Track::Kind::video == track->kind()) { - const std::filesystem::path path = _getMediaPath(externalRef->target_url()); - const OIIO::ImageBuf buf(path.string()); - const auto& spec = buf.spec(); - if (spec.width > 0) + for (auto clip : track->find_clips()) { - _imageSize.x = spec.width; - _imageSize.y = spec.height; - break; - } - } - else if (auto sequenceRef = dynamic_cast(clip->media_reference())) - { - const std::filesystem::path path = getSequenceFrame( - _getMediaPath(sequenceRef->target_url_base()), - sequenceRef->name_prefix(), - sequenceRef->start_frame(), - sequenceRef->frame_zero_padding(), - sequenceRef->name_suffix()); - const OIIO::ImageBuf buf(path.string()); - const auto& spec = buf.spec(); - if (spec.width > 0) - { - _imageSize.x = spec.width; - _imageSize.y = spec.height; - break; - } - } - else if (auto generatorRef = dynamic_cast(clip->media_reference())) - { - auto parameters = generatorRef->parameters(); - auto i = parameters.find("size"); - if (i != parameters.end() && i->second.has_value()) - { - anyToVec(std::any_cast(i->second), _imageSize); + if (auto externalRef = dynamic_cast(clip->media_reference())) + { + const std::filesystem::path path = _timeline->getMediaPath(externalRef->target_url()); + const OIIO::ImageBuf buf(path.string()); + const auto& spec = buf.spec(); + if (spec.width > 0) + { + _imageSize.x = spec.width; + _imageSize.y = spec.height; + break; + } + } + else if (auto sequenceRef = dynamic_cast(clip->media_reference())) + { + const std::filesystem::path path = getSequenceFrame( + _timeline->getMediaPath(sequenceRef->target_url_base()), + sequenceRef->name_prefix(), + sequenceRef->start_frame(), + sequenceRef->frame_zero_padding(), + sequenceRef->name_suffix()); + const OIIO::ImageBuf buf(path.string()); + const auto& spec = buf.spec(); + if (spec.width > 0) + { + _imageSize.x = spec.width; + _imageSize.y = spec.height; + break; + } + } + else if (auto generatorRef = dynamic_cast(clip->media_reference())) + { + auto parameters = generatorRef->parameters(); + auto i = parameters.find("size"); + if (i != parameters.end() && i->second.has_value()) + { + anyToVec(std::any_cast(i->second), _imageSize); + } + } } } } @@ -99,7 +102,7 @@ namespace toucan std::shared_ptr node = host->createNode("toucan:Fill", metaData); // Loop over the tracks. - auto stack = _timeline->tracks(); + auto stack = _timeline->otio()->tracks(); for (const auto& i : stack->children()) { if (auto track = OTIO_NS::dynamic_retainer_cast(i)) @@ -107,7 +110,7 @@ namespace toucan if (track->kind() == OTIO_NS::Track::Kind::video && !track->find_clips().empty()) { // Process this track. - auto trackNode = _track(host, time - _globalStartTime, track); + auto trackNode = _track(host, time - _timeRange.start_time(), track); // Get the track effects. const auto& effects = track->effects(); @@ -141,7 +144,7 @@ namespace toucan } // Set the time. - node->setTime(time - _globalStartTime); + node->setTime(time - _timeRange.start_time()); return node; } @@ -291,7 +294,7 @@ namespace toucan std::shared_ptr read; if (!_loadCache.get(externalRef, read)) { - const std::filesystem::path path = _getMediaPath(externalRef->target_url()); + const std::filesystem::path path = _timeline->getMediaPath(externalRef->target_url()); read = std::make_shared(path); _loadCache.add(externalRef, read); } @@ -309,7 +312,7 @@ namespace toucan } else if (auto sequenceRef = dynamic_cast(clip->media_reference())) { - const std::filesystem::path path = _getMediaPath(sequenceRef->target_url_base()); + const std::filesystem::path path = _timeline->getMediaPath(sequenceRef->target_url_base()); auto read = std::make_shared( path, sequenceRef->name_prefix(), @@ -374,14 +377,4 @@ namespace toucan } return out; } - - std::filesystem::path ImageGraph::_getMediaPath(const std::string& url) const - { - std::filesystem::path path = splitURLProtocol(url).second; - if (!path.is_absolute()) - { - path = _path / path; - } - return path; - } } diff --git a/lib/toucan/ImageGraph.h b/lib/toucan/ImageGraph.h index 483fb1e..c246861 100644 --- a/lib/toucan/ImageGraph.h +++ b/lib/toucan/ImageGraph.h @@ -5,11 +5,10 @@ #include #include +#include #include -#include -#include #include #include @@ -32,7 +31,7 @@ namespace toucan public: ImageGraph( const std::filesystem::path&, - const OTIO_NS::SerializableObject::Retainer&, + const std::shared_ptr&, const ImageGraphOptions& = ImageGraphOptions()); ~ImageGraph(); @@ -62,11 +61,9 @@ namespace toucan const std::vector >&, const std::shared_ptr&); - std::filesystem::path _getMediaPath(const std::string&) const; - std::filesystem::path _path; - OTIO_NS::SerializableObject::Retainer _timeline; - OTIO_NS::RationalTime _globalStartTime; + std::shared_ptr _timeline; + OTIO_NS::TimeRange _timeRange; ImageGraphOptions _options; IMATH_NAMESPACE::V2i _imageSize = IMATH_NAMESPACE::V2i(0, 0); dtk::LRUCache > _loadCache; diff --git a/lib/toucan/Timeline.cpp b/lib/toucan/Timeline.cpp new file mode 100644 index 0000000..79385b1 --- /dev/null +++ b/lib/toucan/Timeline.cpp @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#include "Timeline.h" + +#include "Util.h" + +#include + +#include +#include +#include + +namespace toucan +{ + namespace + { + class ZipFile + { + public: + ZipFile(const std::filesystem::path& path) + { + mz_zip_reader_create(&handle); + if (!handle) + { + throw std::runtime_error("Cannot create zip handle"); + } + int32_t r = mz_zip_reader_open_file(handle, path.string().c_str()); + if (r != 0) + { + throw std::runtime_error("Cannot open zip file: " + path.string()); + } + } + + ~ZipFile() + { + mz_zip_reader_close(handle); + mz_zip_reader_delete(&handle); + } + + void* handle = nullptr; + }; + } + + Timeline::Timeline(const std::filesystem::path& path) : + _path(path) + { + const std::string extension = toLower(_path.extension().string()); + if (".otio" == extension) + { + OTIO_NS::ErrorStatus errorStatus; + _timeline = OTIO_NS::SerializableObject::Retainer( + dynamic_cast(OTIO_NS::Timeline::from_json_file(_path.string(), &errorStatus))); + if (!_timeline) + { + throw std::runtime_error(errorStatus.full_description); + } + } + else if (".otiod" == extension) + { + _path = _path / "content.otio"; + OTIO_NS::ErrorStatus errorStatus; + _timeline = OTIO_NS::SerializableObject::Retainer( + dynamic_cast(OTIO_NS::Timeline::from_json_file(_path.string(), &errorStatus))); + if (!_timeline) + { + throw std::runtime_error(errorStatus.full_description); + } + } + else if (".otioz" == extension) + { + // Create a temp directory. + _tmpPath = makeUniqueTemp(); + + // Open the ZIP and extract the .otio file. + ZipFile zip(path); + int32_t r = mz_zip_reader_locate_entry(zip.handle, "content.otio", 0); + if (r != 0) + { + throw std::runtime_error("Cannot locate: content.otio"); + } + std::filesystem::path otioPath = _tmpPath / "content.otio"; + r = mz_zip_reader_entry_save_file(zip.handle, otioPath.string().c_str()); + if (r != 0) + { + throw std::runtime_error("Cannot read: content.otio"); + } + + // Read the .otio file. + OTIO_NS::ErrorStatus errorStatus; + _timeline = OTIO_NS::SerializableObject::Retainer( + dynamic_cast(OTIO_NS::Timeline::from_json_file(otioPath.string(), &errorStatus))); + if (!_timeline) + { + throw std::runtime_error(errorStatus.full_description); + } + + // Extract the media files from the ZIP. + std::filesystem::create_directory(_tmpPath / "media"); + for (const auto& clip : _timeline->find_clips()) + { + if (auto externalRef = dynamic_cast(clip->media_reference())) + { + auto split = splitURLProtocol(externalRef->target_url()); + r = mz_zip_reader_locate_entry(zip.handle, split.second.c_str(), 0); + if (r != 0) + { + throw std::runtime_error("Cannot locate: " + split.second); + } + std::filesystem::path externalRefPath = _tmpPath / split.second; + r = mz_zip_reader_entry_save_file(zip.handle, externalRefPath.string().c_str()); + if (r != 0) + { + throw std::runtime_error("Cannot read: " + split.second); + } + } + } + } + else + { + throw std::runtime_error("Unrecognized file: " + _path.string()); + } + + const auto globalStartTime = _timeline->global_start_time(); + _timeRange = OTIO_NS::TimeRange( + globalStartTime.has_value() ? + globalStartTime.value() : + OTIO_NS::RationalTime(0.0, _timeline->duration().rate()), + _timeline->duration()); + } + + Timeline::~Timeline() + { + if (!_tmpPath.empty()) + { + std::filesystem::remove_all(_tmpPath); + } + } + + const OTIO_NS::SerializableObject::Retainer& Timeline::otio() const + { + return _timeline; + } + + const OTIO_NS::TimeRange& Timeline::getTimeRange() const + { + return _timeRange; + } + + std::filesystem::path Timeline::getMediaPath(const std::string& url) const + { + std::filesystem::path path = splitURLProtocol(url).second; + if (!path.is_absolute()) + { + if (!_tmpPath.empty()) + { + path = _tmpPath / path; + } + else + { + path = _path.parent_path() / path; + } + } + return path; + } +} diff --git a/lib/toucan/Timeline.h b/lib/toucan/Timeline.h new file mode 100644 index 0000000..5be2022 --- /dev/null +++ b/lib/toucan/Timeline.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#pragma once + +#include +#include + +#include +#include + +namespace toucan +{ + class Timeline : public std::enable_shared_from_this + { + public: + Timeline(const std::filesystem::path&); + + ~Timeline(); + + const OTIO_NS::SerializableObject::Retainer& otio() const; + + const OTIO_NS::TimeRange& getTimeRange() const; + + std::filesystem::path getMediaPath(const std::string& url) const; + + private: + std::filesystem::path _path; + std::filesystem::path _tmpPath; + OTIO_NS::SerializableObject::Retainer _timeline; + OTIO_NS::TimeRange _timeRange; + }; +} diff --git a/lib/toucan/Util.h b/lib/toucan/Util.h index d3a8417..ad0aa82 100644 --- a/lib/toucan/Util.h +++ b/lib/toucan/Util.h @@ -48,6 +48,9 @@ namespace toucan //! Return the zero padding for the given number. size_t getNumberPadding(const std::string&); + //! Make a unique temp directory. + std::filesystem::path makeUniqueTemp(); + #if defined(_WINDOWS) //! Get an error string from a Windows system call. std::string getLastError(); diff --git a/lib/toucan/UtilUnix.cpp b/lib/toucan/UtilUnix.cpp new file mode 100644 index 0000000..09b37c7 --- /dev/null +++ b/lib/toucan/UtilUnix.cpp @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#include "Util.h" + +#include + +namespace toucan +{ + std::filesystem::path makeUniqueTemp() + { + std::filesystem::path path = std::filesystem::temp_directory_path() / "XXXXXX"; + return mkdtemp(path.string().c_str()); + } +} diff --git a/lib/toucan/UtilWin32.cpp b/lib/toucan/UtilWin32.cpp index a250475..9c15b94 100644 --- a/lib/toucan/UtilWin32.cpp +++ b/lib/toucan/UtilWin32.cpp @@ -10,9 +10,27 @@ #define NOMINMAX #endif // NOMINMAX #include +#include namespace toucan { + std::filesystem::path makeUniqueTemp() + { + std::string unique; + GUID guid; + CoCreateGuid(&guid); + const uint8_t* guidP = reinterpret_cast(&guid); + for (int i = 0; i < 16; ++i) + { + char buf[3] = ""; + sprintf_s(buf, 3, "%02x", guidP[i]); + unique += buf; + } + std::filesystem::path path = std::filesystem::temp_directory_path() / unique; + std::filesystem::create_directory(path); + return path; + } + std::string getLastError() { std::string out; diff --git a/lib/toucanView/Document.cpp b/lib/toucanView/Document.cpp index c4881f5..97abdf0 100644 --- a/lib/toucanView/Document.cpp +++ b/lib/toucanView/Document.cpp @@ -8,6 +8,8 @@ #include "ThumbnailGenerator.h" #include "ViewModel.h" +#include + #include namespace toucan @@ -19,23 +21,12 @@ namespace toucan _host(host), _path(path) { - OTIO_NS::ErrorStatus errorStatus; - _timeline = OTIO_NS::SerializableObject::Retainer( - dynamic_cast(OTIO_NS::Timeline::from_json_file(path.string(), &errorStatus))); - if (!_timeline) - { - throw std::runtime_error(errorStatus.full_description); - } - - const auto& globalStartTime = _timeline->global_start_time(); - const OTIO_NS::RationalTime& duration = _timeline->duration(); - const OTIO_NS::RationalTime startTime = globalStartTime.has_value() ? - globalStartTime.value() : - OTIO_NS::RationalTime(0.0, duration.rate()); + _timeline = std::make_shared(path.string()); _playbackModel = std::make_shared(context); - _playbackModel->setTimeRange(OTIO_NS::TimeRange(startTime, duration)); - _playbackModel->setCurrentTime(startTime); + const OTIO_NS::TimeRange& timeRange = _timeline->getTimeRange(); + _playbackModel->setTimeRange(timeRange); + _playbackModel->setCurrentTime(timeRange.start_time()); _viewModel = std::make_shared(); @@ -77,7 +68,7 @@ namespace toucan return _path; } - const OTIO_NS::SerializableObject::Retainer& Document::getTimeline() const + const std::shared_ptr& Document::getTimeline() const { return _timeline; } diff --git a/lib/toucanView/Document.h b/lib/toucanView/Document.h index f8e3c2e..dedc150 100644 --- a/lib/toucanView/Document.h +++ b/lib/toucanView/Document.h @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -33,7 +34,7 @@ namespace toucan const std::filesystem::path& getPath() const; - const OTIO_NS::SerializableObject::Retainer& getTimeline() const; + const std::shared_ptr& getTimeline() const; const std::shared_ptr& getPlaybackModel() const; const std::shared_ptr& getViewModel() const; @@ -53,7 +54,7 @@ namespace toucan std::shared_ptr _host; std::filesystem::path _path; - OTIO_NS::SerializableObject::Retainer _timeline; + std::shared_ptr _timeline; std::shared_ptr _playbackModel; std::shared_ptr _viewModel; std::shared_ptr _selectionModel; diff --git a/lib/toucanView/InfoBar.cpp b/lib/toucanView/InfoBar.cpp index 8312a0e..c08d944 100644 --- a/lib/toucanView/InfoBar.cpp +++ b/lib/toucanView/InfoBar.cpp @@ -33,7 +33,7 @@ namespace toucan if (document) { const IMATH_NAMESPACE::V2i& imageSize = document->getImageSize(); - const size_t trackCount = document->getTimeline()->find_children().size(); + const size_t trackCount = document->getTimeline()->otio()->find_children().size(); text = dtk::Format("{0}: {1}x{2}, {3} tracks"). arg(dtk::elide(document->getPath().filename().string())). diff --git a/lib/toucanView/MenuBar.cpp b/lib/toucanView/MenuBar.cpp index 3dc1c21..304a26c 100644 --- a/lib/toucanView/MenuBar.cpp +++ b/lib/toucanView/MenuBar.cpp @@ -214,7 +214,8 @@ namespace toucan { if (_document) { - _document->getSelectionModel()->selectAll(_document->getTimeline()); + _document->getSelectionModel()->selectAll( + _document->getTimeline()->otio()); } }); _menus["Select"]->addItem(_actions["Select/All"]); @@ -241,7 +242,8 @@ namespace toucan { if (_document) { - _document->getSelectionModel()->invertSelection(_document->getTimeline()); + _document->getSelectionModel()->invertSelection( + _document->getTimeline()->otio()); } }); _menus["Select"]->addItem(_actions["Select/Invert"]); diff --git a/lib/toucanView/ThumbnailGenerator.cpp b/lib/toucanView/ThumbnailGenerator.cpp index 6ba8aca..c1fb038 100644 --- a/lib/toucanView/ThumbnailGenerator.cpp +++ b/lib/toucanView/ThumbnailGenerator.cpp @@ -13,7 +13,7 @@ namespace toucan { ThumbnailGenerator::ThumbnailGenerator( const std::filesystem::path& path, - const OTIO_NS::SerializableObject::Retainer& timeline, + const std::shared_ptr& timeline, const std::shared_ptr& host) : _path(path), _timeline(timeline), diff --git a/lib/toucanView/ThumbnailGenerator.h b/lib/toucanView/ThumbnailGenerator.h index 2b92e1f..823b425 100644 --- a/lib/toucanView/ThumbnailGenerator.h +++ b/lib/toucanView/ThumbnailGenerator.h @@ -5,11 +5,10 @@ #include #include +#include #include -#include - #include #include #include @@ -32,7 +31,7 @@ namespace toucan public: ThumbnailGenerator( const std::filesystem::path&, - const OTIO_NS::SerializableObject::Retainer&, + const std::shared_ptr&, const std::shared_ptr&); ~ThumbnailGenerator(); @@ -50,7 +49,7 @@ namespace toucan void _cancel(); std::filesystem::path _path; - OTIO_NS::SerializableObject::Retainer _timeline; + std::shared_ptr _timeline; std::shared_ptr _host; std::shared_ptr _graph; float _aspect = 1.F; diff --git a/lib/toucanView/TimelineItem.cpp b/lib/toucanView/TimelineItem.cpp index 13b99d4..54ed70a 100644 --- a/lib/toucanView/TimelineItem.cpp +++ b/lib/toucanView/TimelineItem.cpp @@ -18,19 +18,11 @@ namespace toucan const std::shared_ptr& document, const std::shared_ptr& parent) { - const auto& timeline = document->getTimeline(); - const OTIO_NS::RationalTime& duration = timeline->duration(); - OTIO_NS::RationalTime startTime(0.0, duration.rate()); - auto opt = timeline->global_start_time(); - if (opt.has_value()) - { - startTime = opt.value(); - } IItem::_init( context, app, nullptr, - OTIO_NS::TimeRange(startTime, duration), + document->getTimeline()->getTimeRange(), "toucan::TimelineItem", parent); @@ -40,7 +32,7 @@ namespace toucan 0, 0 | static_cast(dtk::KeyModifier::Shift) | static_cast(dtk::KeyModifier::Control)); - _timeline = timeline; + _timeline = document->getTimeline()->otio(); _timeUnitsModel = app->getTimeUnitsModel(); _selectionModel = document->getSelectionModel(); _thumbnails.setMax(100); @@ -183,7 +175,7 @@ namespace toucan const dtk::Box2I& g = getGeometry(); const int y = g.min.y + _size.fontMetrics.lineHeight + _size.margin * 2; - for (int x = g.min.x; x < g.max.x; x += _size.thumbnailSize.w) + for (int x = g.min.x; x < g.max.x && _size.thumbnailSize.w > 0; x += _size.thumbnailSize.w) { const dtk::Box2I g2(x, y, _size.thumbnailSize.w, _size.thumbnailSize.h); if (dtk::intersects(g2, drawRect)) diff --git a/tests/toucanTest/ImageGraphTest.cpp b/tests/toucanTest/ImageGraphTest.cpp index 0bbbc69..4bf6165 100644 --- a/tests/toucanTest/ImageGraphTest.cpp +++ b/tests/toucanTest/ImageGraphTest.cpp @@ -4,6 +4,7 @@ #include "ImageGraphTest.h" #include +#include #include #include @@ -34,25 +35,24 @@ namespace toucan { // Open the timeline. const std::filesystem::path timelinePath = path / (otioFile + ".otio"); - OTIO_NS::ErrorStatus errorStatus; - auto timeline = OTIO_NS::SerializableObject::Retainer( - dynamic_cast(OTIO_NS::Timeline::from_json_file(timelinePath.string(), &errorStatus))); - if (!timeline) + std::shared_ptr timeline; + try { - std::cout << "ERROR: " << timelinePath.string() << ": " << errorStatus.full_description << std::endl; + timeline = std::make_shared(timelinePath); + } + catch (const std::exception& e) + { + std::cout << "ERROR: " << e.what() << std::endl; continue; } - // Compute time values. - const OTIO_NS::RationalTime startTime = timeline->global_start_time().has_value() ? - timeline->global_start_time().value() : - OTIO_NS::RationalTime(0.0, timeline->duration().rate()); - const OTIO_NS::TimeRange timeRange(startTime, timeline->duration()); - const OTIO_NS::RationalTime timeInc(1.0, timeline->duration().rate()); + // Get time values. + const OTIO_NS::TimeRange& timeRange = timeline->getTimeRange(); + const OTIO_NS::RationalTime timeInc(1.0, timeRange.duration().rate()); // Render the timeline frames. const auto graph = std::make_shared(path, timeline); - for (OTIO_NS::RationalTime time = startTime; + for (OTIO_NS::RationalTime time = timeRange.start_time(); time <= timeRange.end_time_inclusive(); time += timeInc) {