diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 9031a31..9772e2e 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -25,6 +25,7 @@ jobs: echo "LD_LIBRARY_PATH=$PWD/install-Debug/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV sudo apt-get update sudo apt-get install xorg-dev libglu1-mesa-dev mesa-common-dev mesa-utils xvfb + cmake --version - name: Build run: sh toucan/sbuild-linux.sh Debug @@ -53,9 +54,10 @@ jobs: echo "LD_LIBRARY_PATH=$PWD/install-Release/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV sudo apt-get update sudo apt-get install xorg-dev libglu1-mesa-dev mesa-common-dev mesa-utils xvfb + cmake --version - name: Build - run: sh toucan/sbuild-macos.sh + run: sh toucan/sbuild-linux.sh - name: Install run: cmake --build build-Release --config Release --target install @@ -76,33 +78,6 @@ jobs: - name: Upload artifact URL run: echo 'Artifact URL ${{ steps.linux-package-artifact.outputs.artifact-url }}' - macos-13-build: - runs-on: macos-13 - - env: - FFMPEG_MINIMAL: ON - - steps: - - uses: actions/checkout@v4 - with: - path: toucan - submodules: recursive - - # \bug DYLD_LIBRARY_PATH is not being set here? - - name: Setup - run: | - echo "$PWD/install-Debug/bin" >> $GITHUB_PATH - echo "DYLD_LIBRARY_PATH=$PWD/install-Debug/lib:$DYLD_LIBRARY_PATH" >> $GITHUB_ENV - - - name: Build - run: sh toucan/sbuild-macos.sh Debug - - - name: Install - run: cmake --build build-Debug --config Debug --target install - - - name: Tests - run: build-Debug/tests/toucan-test/toucan-test toucan/data - macos-build: runs-on: macos-latest @@ -120,6 +95,7 @@ jobs: run: | echo "$PWD/install-Debug/bin" >> $GITHUB_PATH echo "DYLD_LIBRARY_PATH=$PWD/install-Debug/lib:$DYLD_LIBRARY_PATH" >> $GITHUB_ENV + cmake --version - name: Build run: sh toucan/sbuild-macos.sh Debug @@ -147,6 +123,7 @@ jobs: run: | echo "$PWD/install-Debug/bin" >> $GITHUB_PATH echo "DYLD_LIBRARY_PATH=$PWD/install-Debug/lib:$DYLD_LIBRARY_PATH" >> $GITHUB_ENV + cmake --version - name: Build run: bash toucan/sbuild-macos.sh @@ -190,6 +167,7 @@ jobs: echo %CD%\install-Debug\bin>> %GITHUB_PATH% echo %CD%\install-Debug\lib>> %GITHUB_PATH% echo CTEST_OUTPUT_ON_FAILURE=1 >> %GITHUB_ENV% + cmake --version - name: Build shell: cmd @@ -222,6 +200,7 @@ jobs: run: | echo %CD%\install-Debug\bin>> %GITHUB_PATH% echo %CD%\install-Debug\lib>> %GITHUB_PATH% + cmake --version - name: Build shell: cmd diff --git a/bin/toucan-render/App.cpp b/bin/toucan-render/App.cpp index 5d975c9..dc7ff2f 100644 --- a/bin/toucan-render/App.cpp +++ b/bin/toucan-render/App.cpp @@ -200,7 +200,7 @@ namespace toucan // Open the movie file. std::shared_ptr ffWrite; - if (MovieReadNode::hasExtension(outputPath.extension().string())) + if (hasExtension(outputPath.extension().string(), MovieReadNode::getExtensions())) { ffmpeg::VideoCodec videoCodec = ffmpeg::VideoCodec::MJPEG; if (_cmdLine.videoCodec->hasValue()) diff --git a/cmake/SuperBuild/BuildOpenImageIO.cmake b/cmake/SuperBuild/BuildOpenImageIO.cmake index d6df8bc..8e4b840 100644 --- a/cmake/SuperBuild/BuildOpenImageIO.cmake +++ b/cmake/SuperBuild/BuildOpenImageIO.cmake @@ -1,7 +1,7 @@ include(ExternalProject) set(OpenImageIO_GIT_REPOSITORY "https://github.com/AcademySoftwareFoundation/OpenImageIO.git") -set(OpenImageIO_GIT_TAG "v3.0.9.1") +set(OpenImageIO_GIT_TAG "v3.0.10.0") set(OpenImageIO_DEPS) if(toucan_PNG) diff --git a/cmake/SuperBuild/Buildexpat.cmake b/cmake/SuperBuild/Buildexpat.cmake index ecd53da..7fdb743 100644 --- a/cmake/SuperBuild/Buildexpat.cmake +++ b/cmake/SuperBuild/Buildexpat.cmake @@ -7,7 +7,8 @@ set(expat_ARGS ${toucan_EXTERNAL_PROJECT_ARGS} -DEXPAT_BUILD_TOOLS=OFF -DEXPAT_BUILD_EXAMPLES=OFF - -DEXPAT_BUILD_TESTS=OFF) + -DEXPAT_BUILD_TESTS=OFF + -DCMAKE_POLICY_VERSION_MINIMUM=3.5) ExternalProject_Add( expat diff --git a/cmake/SuperBuild/Buildpystring.cmake b/cmake/SuperBuild/Buildpystring.cmake index c7c21a3..0a2b4d0 100644 --- a/cmake/SuperBuild/Buildpystring.cmake +++ b/cmake/SuperBuild/Buildpystring.cmake @@ -3,7 +3,9 @@ include(ExternalProject) set(pystring_GIT_REPOSITORY "https://github.com/imageworks/pystring.git") set(pystring_GIT_TAG "v1.1.4") -set(pystring_ARGS ${toucan_EXTERNAL_PROJECT_ARGS}) +set(pystring_ARGS + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 + ${toucan_EXTERNAL_PROJECT_ARGS}) ExternalProject_Add( pystring diff --git a/cmake/SuperBuild/Buildyaml-cpp.cmake b/cmake/SuperBuild/Buildyaml-cpp.cmake index 5a53d1b..718f8b1 100644 --- a/cmake/SuperBuild/Buildyaml-cpp.cmake +++ b/cmake/SuperBuild/Buildyaml-cpp.cmake @@ -7,7 +7,8 @@ set(yaml-cpp_ARGS ${toucan_EXTERNAL_PROJECT_ARGS} -DYAML_CPP_BUILD_CONTRIB=OFF -DYAML_CPP_BUILD_TOOLS=OFF - -DYAML_CPP_BUILD_TESTS=OFF) + -DYAML_CPP_BUILD_TESTS=OFF + -DCMAKE_POLICY_VERSION_MINIMUM=3.5) ExternalProject_Add( yaml-cpp diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 3deb957..94c141f 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory(toucanResource) add_subdirectory(toucanRender) if(toucan_VIEW) add_subdirectory(toucanView) diff --git a/lib/toucanRender/CMakeLists.txt b/lib/toucanRender/CMakeLists.txt index a27d40f..b2199c0 100644 --- a/lib/toucanRender/CMakeLists.txt +++ b/lib/toucanRender/CMakeLists.txt @@ -45,6 +45,7 @@ endif() add_library(toucanRender ${HEADERS} ${HEADERS_PRIVATE} ${SOURCE}) set(LIBS_PUBLIC + toucanResource ftk::feather-tk-core OTIO::opentimelineio OTIO::opentime diff --git a/lib/toucanRender/Comp.cpp b/lib/toucanRender/Comp.cpp index 49242e4..6d364e1 100644 --- a/lib/toucanRender/Comp.cpp +++ b/lib/toucanRender/Comp.cpp @@ -29,16 +29,9 @@ namespace toucan OIIO::ImageBuf CompNode::exec() { OIIO::ImageBuf buf; - OTIO_NS::RationalTime offsetTime = _time; - if (!_timeOffset.is_invalid_time()) - { - offsetTime -= _timeOffset; - } if (_inputs.size() > 1 && _inputs[0] && _inputs[1]) { - _inputs[0]->setTime(offsetTime); auto fgBuf = _inputs[0]->exec(); - _inputs[1]->setTime(offsetTime); buf = _inputs[1]->exec(); const auto fgSpec = fgBuf.spec(); if (_premult && @@ -81,7 +74,6 @@ namespace toucan } else if (1 == _inputs.size() && _inputs[0]) { - _inputs[0]->setTime(offsetTime); buf = _inputs[0]->exec(); if (_premult) { diff --git a/lib/toucanRender/ImageEffect.cpp b/lib/toucanRender/ImageEffect.cpp index 6ef593b..1aefe01 100644 --- a/lib/toucanRender/ImageEffect.cpp +++ b/lib/toucanRender/ImageEffect.cpp @@ -78,12 +78,6 @@ namespace toucan { OIIO::ImageBuf out; - OTIO_NS::RationalTime offsetTime = _time; - if (!_timeOffset.is_invalid_time()) - { - offsetTime -= _timeOffset; - } - // Initialize the images. std::vector inputs; IMATH_NAMESPACE::V2i size = IMATH_NAMESPACE::V2i(0, 0); @@ -104,7 +98,6 @@ namespace toucan !_inputs.empty() && _inputs[0]) { - _inputs[0]->setTime(offsetTime); inputs.push_back(_inputs[0]->exec()); auto spec = inputs[0].spec(); if (size.x > 0 && size.y > 0) @@ -122,9 +115,7 @@ namespace toucan _inputs[0] && _inputs[1]) { - _inputs[0]->setTime(offsetTime); inputs.push_back(_inputs[0]->exec()); - _inputs[1]->setTime(offsetTime); inputs.push_back(_inputs[1]->exec()); auto spec = inputs[0].spec(); if (size.x > 0 && size.y > 0) @@ -143,7 +134,7 @@ namespace toucan if (spec.width > 0 && spec.height > 0) { PropertySet args; - args.setDouble(kOfxPropTime, 0, offsetTime.value()); + args.setDouble(kOfxPropTime, 0, _time.value()); OfxRectI bounds; bounds.x1 = 0; bounds.x2 = spec.width; diff --git a/lib/toucanRender/ImageGraph.cpp b/lib/toucanRender/ImageGraph.cpp index e6646b5..d6c1545 100644 --- a/lib/toucanRender/ImageGraph.cpp +++ b/lib/toucanRender/ImageGraph.cpp @@ -48,30 +48,27 @@ namespace toucan _timelineWrapper(timelineWrapper), _timeRange(timelineWrapper->getTimeRange()) { - _loadCache.setMax(10); + _readCache.setMax(20); // Get the image information from the first video clip. for (auto clip : getVideoClips(_timelineWrapper->getTimeline())) { if (auto externalRef = dynamic_cast(clip->media_reference())) { + std::shared_ptr read; try { - const std::filesystem::path path = _timelineWrapper->getMediaPath(externalRef->target_url()); - const MemoryReference mem = _timelineWrapper->getMemoryReference(externalRef->target_url()); - std::shared_ptr read; - if (MovieReadNode::hasExtension(path.extension().string())) - { - read = std::make_shared(path, nullptr, mem); - } - else if (SVGReadNode::hasExtension(path.extension().string())) - { - read = std::make_shared(path, nullptr, mem); - } - else - { - read = std::make_shared(path, nullptr, mem); - } + read = _timelineWrapper->createReadNode(externalRef); + } + catch (const std::exception& e) + { + _context.lock()->getSystem()->print( + logPrefix, + e.what(), + ftk::LogType::Error); + } + if (read) + { const auto& spec = read->getSpec(); if (spec.width > 0) { @@ -82,6 +79,14 @@ namespace toucan break; } } + } + else if (auto sequenceRef = dynamic_cast(clip->media_reference())) + { + std::shared_ptr read; + try + { + read = _timelineWrapper->createReadNode(sequenceRef); + } catch (const std::exception& e) { _context.lock()->getSystem()->print( @@ -89,21 +94,8 @@ namespace toucan e.what(), ftk::LogType::Error); } - } - else if (auto sequenceRef = dynamic_cast(clip->media_reference())) - { - try + if (read) { - auto read = std::make_shared( - _timelineWrapper->getMediaPath(sequenceRef->target_url_base()), - sequenceRef->name_prefix(), - sequenceRef->name_suffix(), - sequenceRef->start_frame(), - sequenceRef->frame_step(), - sequenceRef->rate(), - sequenceRef->frame_zero_padding(), - nullptr, - _timelineWrapper->getMemoryReferences()); const auto& spec = read->getSpec(); if (spec.width > 0) { @@ -114,13 +106,6 @@ namespace toucan break; } } - catch (const std::exception& e) - { - _context.lock()->getSystem()->print( - logPrefix, - e.what(), - ftk::LogType::Error); - } } else if (auto generatorRef = dynamic_cast(clip->media_reference())) { @@ -132,6 +117,7 @@ namespace toucan //! \bug Hard coded: _imageChannels = 4; _imageDataType = toImageDataType(OIIO::TypeDesc::UINT8); + break; } } } @@ -157,32 +143,45 @@ namespace toucan std::shared_ptr ImageGraph::exec( const std::shared_ptr& host, - const OTIO_NS::RationalTime& time) + const OTIO_NS::RationalTime& time, + const OTIO_NS::Item* itemNode) { + _host = host; + _itemNode = itemNode; + // Set the background color. OTIO_NS::AnyDictionary metaData; metaData["size"] = vecToAny(_imageSize); metaData["color"] = vecToAny(IMATH_NAMESPACE::V4f(0.F, 0.F, 0.F, 1.F)); - std::shared_ptr node = host->createNode(metaData, "toucan:Fill"); + auto node = host->createNode(metaData, "toucan:Fill"); - // Loop over the tracks. + // Apply time warps. auto stack = _timelineWrapper->getTimeline()->tracks(); + const auto& stackEffects = stack->effects(); + OTIO_NS::RationalTime t = time - _timeRange.start_time(); + t = _timeWarps(t, stack->available_range(), stackEffects); + + // Loop over the tracks. for (const auto& i : stack->children()) { if (auto track = OTIO_NS::dynamic_retainer_cast(i)) { if (track->kind() == OTIO_NS::Track::Kind::video && !track->find_clips().empty()) { - // Process this track. - auto trackNode = _track(host, time - _timeRange.start_time(), track); - - // Get the track effects. - const auto& effects = track->effects(); - if (trackNode && !effects.empty()) + // Apply time warps. + const auto& trackEffects = track->effects(); + OTIO_NS::RationalTime t2 = t; + if (!trackEffects.empty()) { - trackNode = _effects(host, track->trimmed_range(), effects, trackNode); + t2 = _timeWarps(t2, track->available_range(), trackEffects); } + // Process this track. + auto trackNode = _track(t2, track); + + // Add the track effects. + trackNode = _effects(t2, trackEffects, trackNode); + // Composite this track over the previous track. std::vector > nodes; if (trackNode) @@ -200,27 +199,29 @@ namespace toucan } } - // Get the stack effects. - const auto& effects = stack->effects(); - if (!effects.empty()) + // Add the stack effects. + node = _effects(t, stackEffects, node); + + // Clean up. + _host.reset(); + _itemNode = nullptr; + if (_outNode) { - node = _effects(host, stack->trimmed_range(), effects, node); + node = _outNode; } - - // Set the time. - node->setTime(time - _timeRange.start_time()); + _outNode.reset(); return node; } std::shared_ptr ImageGraph::_track( - const std::shared_ptr& host, const OTIO_NS::RationalTime& time, const OTIO_NS::SerializableObject::Retainer& track) { std::shared_ptr out; - // Find the items for the given time. + // Find the item for the given time. The previous and next items are + // also tracked for handling transitions. OTIO_NS::SerializableObject::Retainer item; OTIO_NS::SerializableObject::Retainer prev; OTIO_NS::SerializableObject::Retainer prev2; @@ -235,8 +236,6 @@ namespace toucan if (trimmedRangeInParent.has_value() && trimmedRangeInParent.value().contains(time)) { out = _item( - host, - trimmedRangeInParent.value(), track->transformed_time(time, item), item); if (i > 0) @@ -274,27 +273,24 @@ namespace toucan (time - trimmedRangeInParent.value().start_time()).value() / trimmedRangeInParent.value().duration().value(); + auto a = _item( + track->transformed_time(time, prevItem), + prevItem); + auto metaData = prevTransition->metadata(); metaData["value"] = value; - auto node = host->createNode( + auto node = _host->createNode( metaData, - prevTransition->transition_type()); + prevTransition->transition_type(), + { a, out }); if (!node) { - node = host->createNode( + node = _host->createNode( metaData, - "toucan:Dissolve"); - } - if (node) - { - auto a = _item( - host, - prevItem->trimmed_range_in_parent().value(), - track->transformed_time(time, prevItem), - prevItem); - node->setInputs({ a, out }); - out = node; + "toucan:Dissolve", + { a, out }); } + out = node; } } } @@ -309,27 +305,24 @@ namespace toucan (time - trimmedRangeInParent.value().start_time()).value() / trimmedRangeInParent.value().duration().value(); + auto b = _item( + track->transformed_time(time, nextItem), + nextItem); + auto metaData = nextTransition->metadata(); metaData["value"] = value; - auto node = host->createNode( + auto node = _host->createNode( metaData, - nextTransition->transition_type()); + nextTransition->transition_type(), + { out, b }); if (!node) { - node = host->createNode( + node = _host->createNode( metaData, - "toucan:Dissolve"); - } - if (node) - { - auto b = _item( - host, - nextItem->trimmed_range_in_parent().value(), - track->transformed_time(time, nextItem), - nextItem); - node->setInputs({ out, b }); - out = node; + "toucan:Dissolve", + { out, b }); } + out = node; } } } @@ -339,37 +332,30 @@ namespace toucan } std::shared_ptr ImageGraph::_item( - const std::shared_ptr& host, - const OTIO_NS::TimeRange& trimmedRangeInParent, const OTIO_NS::RationalTime& time, const OTIO_NS::SerializableObject::Retainer& item) { std::shared_ptr out; + OTIO_NS::RationalTime t = time; + + // Apply time warps. + const auto& effects = item->effects(); + t = _timeWarps(t, item->available_range(), effects); + + // Get the media. if (auto clip = OTIO_NS::dynamic_retainer_cast(item)) { - // Get the media reference. - if (auto externalRef = dynamic_cast(clip->media_reference())) + auto mediaRef = clip->media_reference(); + if (auto externalRef = dynamic_cast(mediaRef)) { std::shared_ptr read; - if (!_loadCache.get(externalRef, read)) + if (!_readCache.get(externalRef, read)) { try { - const std::filesystem::path path = _timelineWrapper->getMediaPath(externalRef->target_url()); - const MemoryReference mem = _timelineWrapper->getMemoryReference(externalRef->target_url()); - if (MovieReadNode::hasExtension(path.extension().string())) - { - read = std::make_shared(path, externalRef, mem); - } - else if (SVGReadNode::hasExtension(path.extension().string())) - { - read = std::make_shared(path, externalRef, mem); - } - else - { - read = std::make_shared(path, externalRef, mem); - } + read = _timelineWrapper->createReadNode(externalRef); + _readCache.add(externalRef, read); } catch (const std::exception& e) { @@ -378,58 +364,47 @@ namespace toucan e.what(), ftk::LogType::Error); } - _loadCache.add(externalRef, read); } if (read) { - OTIO_NS::RationalTime timeOffset = -item->trimmed_range().start_time(); - - //! \bug Workaround for when the available range does not match - //! the range in the media. - const OTIO_NS::TimeRange& timeRange = read->getTimeRange(); - const auto availableOpt = externalRef->available_range(); - if (availableOpt.has_value() && - !availableOpt.value().start_time().strictly_equal(timeRange.start_time())) + //! \bug Workaround for files that are missing timecode. + if (clip->available_range().start_time() != + read->getTimeRange().start_time()) { - timeOffset += availableOpt.value().start_time() - timeRange.start_time(); + t -= clip->available_range().start_time(); } - read->setTimeOffset(timeOffset); + read->setTime(t); } out = read; } - else if (auto sequenceRef = dynamic_cast(clip->media_reference())) + else if (auto sequenceRef = dynamic_cast(mediaRef)) { - std::shared_ptr read; - try - { - read = std::make_shared( - _timelineWrapper->getMediaPath(sequenceRef->target_url_base()), - sequenceRef->name_prefix(), - sequenceRef->name_suffix(), - sequenceRef->start_frame(), - sequenceRef->frame_step(), - sequenceRef->rate(), - sequenceRef->frame_zero_padding(), - sequenceRef, - _timelineWrapper->getMemoryReferences()); - } - catch (const std::exception& e) + std::shared_ptr read; + if (!_readCache.get(sequenceRef, read)) { - _context.lock()->getSystem()->print( - logPrefix, - e.what(), - ftk::LogType::Error); + try + { + read = _timelineWrapper->createReadNode(sequenceRef); + _readCache.add(sequenceRef, read); + } + catch (const std::exception& e) + { + _context.lock()->getSystem()->print( + logPrefix, + e.what(), + ftk::LogType::Error); + } } if (read) { - read->setTimeOffset(-item->trimmed_range().start_time()); + read->setTime(t); } out = read; } - else if (auto generatorRef = dynamic_cast(clip->media_reference())) + else if (auto generatorRef = dynamic_cast(mediaRef)) { - out = host->createNode( + out = _host->createNode( generatorRef->parameters(), generatorRef->generator_kind()); } @@ -438,51 +413,53 @@ namespace toucan { OTIO_NS::AnyDictionary metaData; metaData["size"] = vecToAny(_imageSize); - out = host->createNode(metaData, "toucan:Fill"); + out = _host->createNode(metaData, "toucan:Fill"); } - // Get the effects. - const auto& effects = item->effects(); - if (out && !effects.empty()) + // Add the effects. + out = _effects(t, effects, out); + + if (item == _itemNode) { - out = _effects(host, item->trimmed_range(), effects, out); + _outNode = out; } - // Set the time offset. - if (out) + return out; + } + + OTIO_NS::RationalTime ImageGraph::_timeWarps( + const OTIO_NS::RationalTime& time, + const OTIO_NS::TimeRange& timeRange, + const std::vector >& effects) + { + OTIO_NS::RationalTime out = time; + for (const auto& effect : effects) { - out->setTimeOffset(out->getTimeOffset() + trimmedRangeInParent.start_time()); + if (auto linearTimeWarp = dynamic_cast(effect.value)) + { + const double s = linearTimeWarp->time_scalar(); + out = OTIO_NS::RationalTime( + (out - timeRange.start_time()).value() * s, + time.rate()).round(); + } } - return out; } std::shared_ptr ImageGraph::_effects( - const std::shared_ptr& host, - const OTIO_NS::TimeRange& trimmedRange, + const OTIO_NS::RationalTime& time, const std::vector >& effects, const std::shared_ptr& input) { std::shared_ptr out = input; for (const auto& effect : effects) { - if (auto linearTimeWarp = dynamic_cast(effect.value)) - { - auto linearTimeWarpNode = std::make_shared( - static_cast(linearTimeWarp->time_scalar()), - trimmedRange, - std::vector >{ out }); - out = linearTimeWarpNode; - } - else + if (auto imageEffect = _host->createNode( + effect->metadata(), + effect->effect_name(), + { out })) { - if (auto imageEffect = host->createNode( - effect->metadata(), - effect->effect_name(), - { out })) - { - out = imageEffect; - } + out = imageEffect; } } return out; diff --git a/lib/toucanRender/ImageGraph.h b/lib/toucanRender/ImageGraph.h index 820c028..246732d 100644 --- a/lib/toucanRender/ImageGraph.h +++ b/lib/toucanRender/ImageGraph.h @@ -9,7 +9,6 @@ #include #include -#include #include #include @@ -18,8 +17,6 @@ namespace toucan { - class IReadNode; - //! Create image graphs from a timeline. class ImageGraph : public std::enable_shared_from_this { @@ -43,23 +40,25 @@ namespace toucan //! Get an image graph for the given time. std::shared_ptr exec( const std::shared_ptr&, - const OTIO_NS::RationalTime&); + const OTIO_NS::RationalTime&, + const OTIO_NS::Item* = nullptr); private: std::shared_ptr _track( - const std::shared_ptr&, const OTIO_NS::RationalTime&, const OTIO_NS::SerializableObject::Retainer&); std::shared_ptr _item( - const std::shared_ptr&, - const OTIO_NS::TimeRange& trimmedRangeInParent, const OTIO_NS::RationalTime&, const OTIO_NS::SerializableObject::Retainer&); + OTIO_NS::RationalTime _timeWarps( + const OTIO_NS::RationalTime&, + const OTIO_NS::TimeRange&, + const std::vector >&); + std::shared_ptr _effects( - const std::shared_ptr&, - const OTIO_NS::TimeRange& trimmedRange, + const OTIO_NS::RationalTime&, const std::vector >&, const std::shared_ptr&); @@ -70,6 +69,11 @@ namespace toucan IMATH_NAMESPACE::V2i _imageSize = IMATH_NAMESPACE::V2i(0, 0); int _imageChannels = 0; std::string _imageDataType; - ftk::LRUCache > _loadCache; + ftk::LRUCache > _readCache; + + // Temporary variables available during execution. + std::shared_ptr _host; + const OTIO_NS::Item* _itemNode = nullptr; + std::shared_ptr _outNode; }; } diff --git a/lib/toucanRender/ImageNode.cpp b/lib/toucanRender/ImageNode.cpp index 7c0b54f..3a91678 100644 --- a/lib/toucanRender/ImageNode.cpp +++ b/lib/toucanRender/ImageNode.cpp @@ -30,29 +30,14 @@ namespace toucan return _inputs; } - void IImageNode::setInputs(const std::vector >& value) + void IImageNode::setInputs(const std::vector >& inputs) { - _inputs = value; + _inputs = inputs; } - const OTIO_NS::RationalTime& IImageNode::getTimeOffset() const + void IImageNode::setTime(const OTIO_NS::RationalTime& value) { - return _timeOffset; - } - - void IImageNode::setTimeOffset(const OTIO_NS::RationalTime& timeOffset) - { - _timeOffset = timeOffset; - } - - const OTIO_NS::RationalTime& IImageNode::getTime() const - { - return _time; - } - - void IImageNode::setTime(const OTIO_NS::RationalTime& time) - { - _time = time; + _time = value; } std::vector IImageNode::graph(const std::string& name) @@ -62,20 +47,15 @@ namespace toucan ss << "digraph " << name << " {"; out.push_back(ss.str()); out.push_back(" node [shape=box, fontsize=12, margin=0.05, width=0, height=0];"); - _graph(_time, shared_from_this(), out); + _graph(shared_from_this(), out); out.push_back("}"); return out; } void IImageNode::_graph( - OTIO_NS::RationalTime time, const std::shared_ptr& node, std::vector& out) { - if (!node->_timeOffset.is_invalid_time()) - { - time -= node->_timeOffset; - } std::stringstream ss; const std::string graphName = node->_getGraphName(); ss << " " << graphName << " [label=\"" << node->getLabel() << "\"]"; @@ -87,7 +67,7 @@ namespace toucan std::stringstream ss; ss << " " << input->_getGraphName() << " -> " << graphName; out.push_back(ss.str()); - _graph(time, input, out); + _graph(input, out); } } } diff --git a/lib/toucanRender/ImageNode.h b/lib/toucanRender/ImageNode.h index 239c163..639fdb2 100644 --- a/lib/toucanRender/ImageNode.h +++ b/lib/toucanRender/ImageNode.h @@ -36,17 +36,6 @@ namespace toucan //! Set the inputs. void setInputs(const std::vector >&); - //! Get the time offset. - const OTIO_NS::RationalTime& getTimeOffset() const; - - //! Set the time offset. - //! - //! \todo How should time transforms be handled? - void setTimeOffset(const OTIO_NS::RationalTime&); - - //! Get the time. - const OTIO_NS::RationalTime& getTime() const; - //! Set the time. void setTime(const OTIO_NS::RationalTime&); @@ -58,14 +47,12 @@ namespace toucan protected: void _graph( - OTIO_NS::RationalTime, const std::shared_ptr&, std::vector&); std::string _getGraphName() const; std::string _name; - OTIO_NS::RationalTime _timeOffset; - OTIO_NS::RationalTime _time; std::vector > _inputs; + OTIO_NS::RationalTime _time; }; } diff --git a/lib/toucanRender/Read.cpp b/lib/toucanRender/Read.cpp index c0b1ba5..37f7cd1 100644 --- a/lib/toucanRender/Read.cpp +++ b/lib/toucanRender/Read.cpp @@ -3,6 +3,7 @@ #include "Read.h" +#include "TimelineWrapper.h" #include "Util.h" #include @@ -11,22 +12,13 @@ namespace toucan { - IReadNode::IReadNode( - const OTIO_NS::MediaReference* ref, - const std::string& name, - const std::vector >& inputs) : - IImageNode(name, inputs), - _ref(ref) + IReadNode::IReadNode(const std::string& name) : + IImageNode(name) {} IReadNode::~IReadNode() {} - const OTIO_NS::MediaReference* IReadNode::getRef() const - { - return _ref; - } - const OIIO::ImageSpec& IReadNode::getSpec() const { return _spec; @@ -39,10 +31,8 @@ namespace toucan ImageReadNode::ImageReadNode( const std::filesystem::path& path, - const OTIO_NS::MediaReference* ref, - const MemoryReference& memoryReference, - const std::vector >& inputs) : - IReadNode(ref, "ImageRead", inputs), + const MemoryReference& memoryReference) : + IReadNode("ImageRead"), _path(path), _memoryReader(getMemoryReader(memoryReference)) { @@ -112,94 +102,7 @@ namespace toucan std::vector ImageReadNode::getExtensions() { - return std::vector({ ".exr", ".tif", ".tiff", ".jpg", ".jpeg", ".png" }); - } - - bool ImageReadNode::hasExtension(const std::string& value) - { - const std::vector extensions = getExtensions(); - const auto i = std::find(extensions.begin(), extensions.end(), toLower(value)); - return i != extensions.end(); - } - - SVGReadNode::SVGReadNode( - const std::filesystem::path& path, - const OTIO_NS::MediaReference* ref, - const MemoryReference& memoryReference, - const std::vector >& inputs) : - IReadNode(ref, "SVGRead", inputs), - _path(path) - { - if (memoryReference.isValid()) - { - _svg = lunasvg::Document::loadFromData( - reinterpret_cast(memoryReference.getData()), - memoryReference.getSize()); - } - else - { - _svg = lunasvg::Document::loadFromFile(path.u8string()); - } - if (!_svg) - { - std::stringstream ss; - ss << "Cannot open file: " << _path.string(); - throw std::runtime_error(ss.str()); - } - _spec = OIIO::ImageSpec( - _svg->width(), - _svg->height(), - 4, - OIIO::TypeDesc::BASETYPE::UINT8); - } - - SVGReadNode::~SVGReadNode() - {} - - std::string SVGReadNode::getLabel() const - { - std::stringstream ss; - ss << "Read: " << _path.filename().string(); - return ss.str(); - } - - OIIO::ImageBuf SVGReadNode::exec() - { - OIIO::ImageBuf out; - - const int w = _svg->width(); - const int h = _svg->height(); - auto bitmap = _svg->renderToBitmap(w, h, 0x00000000); - if (!bitmap.isNull()) - { - out = OIIO::ImageBuf(_spec); - for (int y = 0; y < h; ++y) - { - uint8_t* imageP = reinterpret_cast(out.localpixels()) + y * w * 4; - const uint8_t* bitmapP = bitmap.data() + y * w * 4; - for (int x = 0; x < w; ++x, imageP += 4, bitmapP += 4) - { - imageP[0] = bitmapP[2]; - imageP[1] = bitmapP[1]; - imageP[2] = bitmapP[0]; - imageP[3] = bitmapP[3]; - } - } - } - - return out; - } - - std::vector SVGReadNode::getExtensions() - { - return std::vector({ ".svg" }); - } - - bool SVGReadNode::hasExtension(const std::string& value) - { - const std::vector extensions = getExtensions(); - const auto i = std::find(extensions.begin(), extensions.end(), toLower(value)); - return i != extensions.end(); + return { ".exr", ".tif", ".tiff", ".jpg", ".jpeg", ".png" }; } SequenceReadNode::SequenceReadNode( @@ -210,10 +113,8 @@ namespace toucan int frameStep, double rate, int frameZeroPadding, - const OTIO_NS::MediaReference* ref, - const MemoryReferences& memoryReferences, - const std::vector >& inputs) : - IReadNode(ref, "SequenceRead", inputs), + const MemoryReferences& memoryReferences) : + IReadNode("SequenceRead"), _base(base), _namePrefix(namePrefix), _nameSuffix(nameSuffix), @@ -264,18 +165,11 @@ namespace toucan { OIIO::ImageBuf out; - // Get the time. - OTIO_NS::RationalTime offsetTime = _time; - if (!_timeOffset.is_invalid_time()) - { - offsetTime -= _timeOffset; - } - // Open the sequence file. const std::string url = getSequenceFrame( _base, _namePrefix, - offsetTime.floor().to_frames(), + _time.to_frames(), _frameZeroPadding, _nameSuffix); std::unique_ptr memoryReader; @@ -323,22 +217,84 @@ namespace toucan std::vector SequenceReadNode::getExtensions() { - return std::vector({ ".exr", ".tif", ".png" }); + return { ".exr", ".tif", ".tiff", ".jpg", ".jpeg", ".png" }; } - bool SequenceReadNode::hasExtension(const std::string& value) + SVGReadNode::SVGReadNode( + const std::filesystem::path& path, + const MemoryReference& memoryReference) : + IReadNode("SVGRead"), + _path(path) { - const std::vector extensions = getExtensions(); - const auto i = std::find(extensions.begin(), extensions.end(), toLower(value)); - return i != extensions.end(); + if (memoryReference.isValid()) + { + _svg = lunasvg::Document::loadFromData( + reinterpret_cast(memoryReference.getData()), + memoryReference.getSize()); + } + else + { + _svg = lunasvg::Document::loadFromFile(path.u8string()); + } + if (!_svg) + { + std::stringstream ss; + ss << "Cannot open file: " << _path.string(); + throw std::runtime_error(ss.str()); + } + _spec = OIIO::ImageSpec( + _svg->width(), + _svg->height(), + 4, + OIIO::TypeDesc::BASETYPE::UINT8); + } + + SVGReadNode::~SVGReadNode() + {} + + std::string SVGReadNode::getLabel() const + { + std::stringstream ss; + ss << "Read: " << _path.filename().string(); + return ss.str(); + } + + OIIO::ImageBuf SVGReadNode::exec() + { + OIIO::ImageBuf out; + + const int w = _svg->width(); + const int h = _svg->height(); + auto bitmap = _svg->renderToBitmap(w, h, 0x00000000); + if (!bitmap.isNull()) + { + out = OIIO::ImageBuf(_spec); + for (int y = 0; y < h; ++y) + { + uint8_t* imageP = reinterpret_cast(out.localpixels()) + y * w * 4; + const uint8_t* bitmapP = bitmap.data() + y * w * 4; + for (int x = 0; x < w; ++x, imageP += 4, bitmapP += 4) + { + imageP[0] = bitmapP[2]; + imageP[1] = bitmapP[1]; + imageP[2] = bitmapP[0]; + imageP[3] = bitmapP[3]; + } + } + } + + return out; + } + + std::vector SVGReadNode::getExtensions() + { + return { ".svg" }; } MovieReadNode::MovieReadNode( const std::filesystem::path& path, - const OTIO_NS::MediaReference* ref, - const MemoryReference& memoryReference, - const std::vector >& inputs) : - IReadNode(ref, "MovieReadNode", inputs), + const MemoryReference& memoryReference) : + IReadNode("MovieReadNode"), _path(path), _memoryReader(getMemoryReader(memoryReference)) { @@ -361,15 +317,8 @@ namespace toucan { OIIO::ImageBuf out; - // Get the time. - OTIO_NS::RationalTime offsetTime = _time; - if (!_timeOffset.is_invalid_time()) - { - offsetTime -= _timeOffset; - } - // Read the image. - out = _ffRead->getImage(offsetTime); + out = _ffRead->getImage(_time); const auto& spec = out.spec(); if (3 == spec.nchannels) @@ -386,13 +335,61 @@ namespace toucan std::vector MovieReadNode::getExtensions() { - return std::vector({ ".mov", ".mp4", ".m4v", ".y4m" }); + return { ".mov", ".mp4", ".m4v", ".y4m" }; + } + + std::shared_ptr createReadNode( + const std::filesystem::path& path, + const MemoryReference& mem) + { + std::shared_ptr out; + if (hasExtension(path.extension().string(), MovieReadNode::getExtensions())) + { + out = std::make_shared(path, mem); + } + else if (hasExtension(path.extension().string(), ImageReadNode::getExtensions())) + { + out = std::make_shared(path, mem); + } + else if (hasExtension(path.extension().string(), SVGReadNode::getExtensions())) + { + out = std::make_shared(path, mem); + } + return out; + } + + std::shared_ptr createReadNode( + const std::string& base, + const std::string& namePrefix, + const std::string& nameSuffix, + int startFrame, + int frameStep, + double rate, + int frameZeroPadding, + const MemoryReferences& mem) + { + std::shared_ptr out; + if (hasExtension(nameSuffix, SequenceReadNode::getExtensions())) + { + out = std::make_shared( + base, + namePrefix, + nameSuffix, + startFrame, + frameStep, + rate, + frameZeroPadding, + mem); + } + return out; + } - bool MovieReadNode::hasExtension(const std::string& value) + bool hasExtension( + const std::string& extension, + const std::vector& extensions) { - const std::vector extensions = getExtensions(); - const auto i = std::find(extensions.begin(), extensions.end(), toLower(value)); + const auto i = std::find(extensions.begin(), extensions.end(), toLower(extension)); return i != extensions.end(); } } diff --git a/lib/toucanRender/Read.h b/lib/toucanRender/Read.h index 47e224c..5342ea0 100644 --- a/lib/toucanRender/Read.h +++ b/lib/toucanRender/Read.h @@ -7,8 +7,6 @@ #include #include -#include - #include #include @@ -21,21 +19,15 @@ namespace toucan class IReadNode : public IImageNode { public: - IReadNode( - const OTIO_NS::MediaReference*, - const std::string& name, - const std::vector >& = {}); + IReadNode(const std::string& name); virtual ~IReadNode() = 0; - const OTIO_NS::MediaReference* getRef() const; - const OIIO::ImageSpec& getSpec() const; const OTIO_NS::TimeRange& getTimeRange() const; protected: - const OTIO_NS::MediaReference* _ref = nullptr; OIIO::ImageSpec _spec; OTIO_NS::TimeRange _timeRange; }; @@ -46,9 +38,7 @@ namespace toucan public: ImageReadNode( const std::filesystem::path&, - const OTIO_NS::MediaReference*, - const MemoryReference & = {}, - const std::vector > & = {}); + const MemoryReference& = {}); virtual ~ImageReadNode(); @@ -58,39 +48,12 @@ namespace toucan static std::vector getExtensions(); - static bool hasExtension(const std::string&); - private: std::filesystem::path _path; std::shared_ptr _memoryReader; std::unique_ptr _input; }; - //! SVG read node. - class SVGReadNode : public IReadNode - { - public: - SVGReadNode( - const std::filesystem::path&, - const OTIO_NS::MediaReference*, - const MemoryReference& = {}, - const std::vector >& = {}); - - virtual ~SVGReadNode(); - - std::string getLabel() const override; - - OIIO::ImageBuf exec() override; - - static std::vector getExtensions(); - - static bool hasExtension(const std::string&); - - private: - std::filesystem::path _path; - std::unique_ptr _svg; - }; - //! Image sequence read node. class SequenceReadNode : public IReadNode { @@ -102,10 +65,8 @@ namespace toucan int startFrame, int frameStep, double rate, - int frameZerPadding, - const OTIO_NS::MediaReference*, - const MemoryReferences& = {}, - const std::vector >& = {}); + int frameZeroPadding, + const MemoryReferences& = {}); virtual ~SequenceReadNode(); @@ -115,8 +76,6 @@ namespace toucan static std::vector getExtensions(); - static bool hasExtension(const std::string&); - private: std::string _base; std::string _namePrefix; @@ -128,15 +87,34 @@ namespace toucan MemoryReferences _memoryReferences; }; + //! SVG read node. + class SVGReadNode : public IReadNode + { + public: + SVGReadNode( + const std::filesystem::path&, + const MemoryReference& = {}); + + virtual ~SVGReadNode(); + + std::string getLabel() const override; + + OIIO::ImageBuf exec() override; + + static std::vector getExtensions(); + + private: + std::filesystem::path _path; + std::unique_ptr _svg; + }; + //! Movie read node. class MovieReadNode : public IReadNode { public: MovieReadNode( const std::filesystem::path&, - const OTIO_NS::MediaReference*, - const MemoryReference& = {}, - const std::vector >& = {}); + const MemoryReference& = {}); virtual ~MovieReadNode(); @@ -146,11 +124,30 @@ namespace toucan static std::vector getExtensions(); - static bool hasExtension(const std::string&); - private: std::filesystem::path _path; std::shared_ptr _memoryReader; std::unique_ptr _ffRead; }; + + //! Create a read node. + std::shared_ptr createReadNode( + const std::filesystem::path&, + const MemoryReference& = {}); + + //! Create a read node. + std::shared_ptr createReadNode( + const std::string& base, + const std::string& namePrefix, + const std::string& nameSuffix, + int startFrame, + int frameStep, + double rate, + int frameZerPadding, + const MemoryReferences& = {}); + + //! Is the extension in the list? + bool hasExtension( + const std::string& extension, + const std::vector& extensions); } diff --git a/lib/toucanRender/TimeWarp.cpp b/lib/toucanRender/TimeWarp.cpp index f64878c..c19b9b5 100644 --- a/lib/toucanRender/TimeWarp.cpp +++ b/lib/toucanRender/TimeWarp.cpp @@ -5,40 +5,24 @@ namespace toucan { - LinearTimeWarpNode::LinearTimeWarpNode( + /*LinearTimeWarpNode::LinearTimeWarpNode( double timeScalar, - const OTIO_NS::TimeRange& timeRange, const std::vector >& inputs) : IImageNode("LinearTimeWarp", inputs), - _timeScalar(timeScalar), - _timeRange(timeRange) + _timeScalar(timeScalar) {} LinearTimeWarpNode::~LinearTimeWarpNode() {} - OIIO::ImageBuf LinearTimeWarpNode::exec() + OIIO::ImageBuf LinearTimeWarpNode::exec(const OTIO_NS::RationalTime& t) { OIIO::ImageBuf buf; if (!_inputs.empty()) { - OTIO_NS::RationalTime offsetTime = _time; - if (!_timeOffset.is_invalid_time()) - { - offsetTime -= _timeOffset; - } - double timeScalar = _timeScalar; - if (timeScalar < 0.0) - { - timeScalar *= -1.0; - offsetTime = _timeRange.duration() - - OTIO_NS::RationalTime(1.0, _timeRange.duration().rate()) - - offsetTime; - } - const OTIO_NS::RationalTime scaledTime = OTIO_NS::RationalTime(offsetTime.value() * timeScalar, _time.rate()).floor(); - _inputs[0]->setTime(scaledTime); - buf = _inputs[0]->exec(); + const OTIO_NS::RationalTime scaledTime = OTIO_NS::RationalTime(t.value() * _timeScalar, t.rate()).floor(); + buf = _inputs[0]->exec(scaledTime); } return buf; - } + }*/ } diff --git a/lib/toucanRender/TimeWarp.h b/lib/toucanRender/TimeWarp.h index 9562710..c702e73 100644 --- a/lib/toucanRender/TimeWarp.h +++ b/lib/toucanRender/TimeWarp.h @@ -8,20 +8,18 @@ namespace toucan { //! Linear time warp node. - class LinearTimeWarpNode : public IImageNode + /*class LinearTimeWarpNode : public IImageNode { public: LinearTimeWarpNode( double timeScalar, - const OTIO_NS::TimeRange&, const std::vector >& = {}); virtual ~LinearTimeWarpNode(); - OIIO::ImageBuf exec() override; + OIIO::ImageBuf exec(const OTIO_NS::RationalTime&) override; private: double _timeScalar = 1.F; - OTIO_NS::TimeRange _timeRange; - }; + };*/ } diff --git a/lib/toucanRender/TimelineWrapper.cpp b/lib/toucanRender/TimelineWrapper.cpp index f44a96a..5ed2c5d 100644 --- a/lib/toucanRender/TimelineWrapper.cpp +++ b/lib/toucanRender/TimelineWrapper.cpp @@ -72,6 +72,7 @@ namespace toucan const std::string extension = ftk::toLower(_path.extension().string()); if (".otio" == extension) { + // Open an .otio file. OTIO_NS::ErrorStatus errorStatus; _timeline = OTIO_NS::SerializableObject::Retainer( dynamic_cast(OTIO_NS::Timeline::from_json_file(_path.string(), &errorStatus))); @@ -82,6 +83,7 @@ namespace toucan } else if (".otiod" == extension) { + // Open an otiod archive. _path = _path / "content.otio"; OTIO_NS::ErrorStatus errorStatus; _timeline = OTIO_NS::SerializableObject::Retainer( @@ -93,6 +95,7 @@ namespace toucan } else if (".otioz" == extension) { + // Open an otioz archive. /* // Create a temp directory. _tmpPath = makeUniqueTemp(); @@ -241,10 +244,12 @@ namespace toucan } } } - else if (MovieReadNode::hasExtension(path.extension().string())) + else if (hasExtension(extension, MovieReadNode::getExtensions())) { - auto read = std::make_shared(path, nullptr); + // Open a movie file and create a timeline for it. + auto read = std::make_shared(path); _timeline = OTIO_NS::SerializableObject::Retainer(new OTIO_NS::Timeline); + _timeline->set_global_start_time(read->getTimeRange().start_time()); OTIO_NS::SerializableObject::Retainer track(new OTIO_NS::Track("Video")); _timeline->tracks()->append_child(track); OTIO_NS::SerializableObject::Retainer clip(new OTIO_NS::Clip); @@ -254,40 +259,30 @@ namespace toucan clip->set_media_reference(ref); clip->set_source_range(read->getTimeRange()); } - else if (ImageReadNode::hasExtension(path.extension().string()) || - SVGReadNode::hasExtension(path.extension().string()) || - SequenceReadNode::hasExtension(path.extension().string())) + else if (hasExtension(extension, ImageReadNode::getExtensions()) || + hasExtension(extension, SVGReadNode::getExtensions()) || + hasExtension(extension, SequenceReadNode::getExtensions())) { const auto sequence = getSequence(path); const auto split = splitFileNameNumber(sequence.front().stem().string()); if (split.second.empty()) { - std::shared_ptr read; - if (ImageReadNode::hasExtension(path.extension().u8string())) - { - read = std::make_shared(path, nullptr); - } - else if (SVGReadNode::hasExtension(path.extension().u8string())) - { - read = std::make_shared(path, nullptr); - } - if (read) - { - _timeline = OTIO_NS::SerializableObject::Retainer(new OTIO_NS::Timeline); - OTIO_NS::SerializableObject::Retainer track(new OTIO_NS::Track("Video")); - _timeline->tracks()->append_child(track); - OTIO_NS::SerializableObject::Retainer clip(new OTIO_NS::Clip); - track->append_child(clip); - OTIO_NS::SerializableObject::Retainer ref( - new OTIO_NS::ExternalReference(path.string())); - clip->set_media_reference(ref); - clip->set_source_range(OTIO_NS::TimeRange( - OTIO_NS::RationalTime(0.0, 24.0), - OTIO_NS::RationalTime(1.0, 24.0))); - } + // Open an image file and create a timeline for it. + _timeline = OTIO_NS::SerializableObject::Retainer(new OTIO_NS::Timeline); + OTIO_NS::SerializableObject::Retainer track(new OTIO_NS::Track("Video")); + _timeline->tracks()->append_child(track); + OTIO_NS::SerializableObject::Retainer clip(new OTIO_NS::Clip); + track->append_child(clip); + OTIO_NS::SerializableObject::Retainer ref( + new OTIO_NS::ExternalReference(path.string())); + clip->set_media_reference(ref); + clip->set_source_range(OTIO_NS::TimeRange( + OTIO_NS::RationalTime(0.0, 24.0), + OTIO_NS::RationalTime(1.0, 24.0))); } else { + // Open an image sequence and create a timeline for it. const std::string base = sequence.front().parent_path().string(); const std::string prefix = split.first; const std::string suffix = sequence.front().extension().string(); @@ -295,7 +290,6 @@ namespace toucan const int step = 1; const double rate = 24.0; const int padding = getNumberPadding(split.second); - auto read = std::make_shared(base, prefix, suffix, start, 1, rate, padding, nullptr); _timeline = OTIO_NS::SerializableObject::Retainer(new OTIO_NS::Timeline); OTIO_NS::SerializableObject::Retainer track(new OTIO_NS::Track("Video")); _timeline->tracks()->append_child(track); @@ -330,6 +324,11 @@ namespace toucan //} } + const std::filesystem::path& TimelineWrapper::getPath() const + { + return _path; + } + const OTIO_NS::SerializableObject::Retainer& TimelineWrapper::getTimeline() const { return _timeline; @@ -362,12 +361,32 @@ namespace toucan return out; } - const MemoryReferences& TimelineWrapper::getMemoryReferences() const + std::shared_ptr TimelineWrapper::createReadNode(const OTIO_NS::MediaReference* ref) { - return _memoryReferences; + std::shared_ptr out; + if (auto externalRef = dynamic_cast(ref)) + { + const std::string path = getMediaPath(externalRef->target_url()); + const MemoryReference mem = _getMemoryReference(externalRef->target_url()); + out = toucan::createReadNode(path, mem); + } + else if (auto seqRef = dynamic_cast(ref)) + { + const std::string path = getMediaPath(seqRef->target_url_base()); + out = toucan::createReadNode( + path, + seqRef->name_prefix(), + seqRef->name_suffix(), + seqRef->start_frame(), + seqRef->frame_step(), + seqRef->rate(), + seqRef->frame_zero_padding(), + _memoryReferences); + } + return out; } - MemoryReference TimelineWrapper::getMemoryReference(const std::string& url) const + MemoryReference TimelineWrapper::_getMemoryReference(const std::string& url) const { const auto i = _memoryReferences.find(url); return i != _memoryReferences.end() ? i->second : MemoryReference(); diff --git a/lib/toucanRender/TimelineWrapper.h b/lib/toucanRender/TimelineWrapper.h index be60348..d3c4e0e 100644 --- a/lib/toucanRender/TimelineWrapper.h +++ b/lib/toucanRender/TimelineWrapper.h @@ -5,6 +5,7 @@ #include +#include #include #include @@ -13,6 +14,8 @@ namespace toucan { + class IReadNode; + //! Timeline wrapper that supports .otiod and .otioz files. class TimelineWrapper : public std::enable_shared_from_this { @@ -21,16 +24,19 @@ namespace toucan ~TimelineWrapper(); + const std::filesystem::path& getPath() const; + const OTIO_NS::SerializableObject::Retainer& getTimeline() const; const OTIO_NS::TimeRange& getTimeRange() const; std::string getMediaPath(const std::string& url) const; - const MemoryReferences& getMemoryReferences() const; - MemoryReference getMemoryReference(const std::string& url) const; + std::shared_ptr createReadNode(const OTIO_NS::MediaReference*); private: + MemoryReference _getMemoryReference(const std::string& url) const; + std::filesystem::path _path; //std::filesystem::path _tmpPath; std::unique_ptr _memoryMap; diff --git a/lib/toucanResource/CMakeLists.txt b/lib/toucanResource/CMakeLists.txt new file mode 100644 index 0000000..926e01a --- /dev/null +++ b/lib/toucanResource/CMakeLists.txt @@ -0,0 +1,25 @@ +set(HEADERS) +set(SOURCE) +set(RESOURCES + images/toucan_64.svg) +foreach(RESOURCE ${RESOURCES}) + get_filename_component(RESOURCE_BASE ${RESOURCE} NAME_WE) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_BASE}.cpp + COMMAND $ ${CMAKE_SOURCE_DIR}/${RESOURCE} ${RESOURCE_BASE} toucan_resource + DEPENDS ${CMAKE_SOURCE_DIR}/${RESOURCE}) + list(APPEND SOURCE ${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_BASE}.cpp) +endforeach() + +add_library(toucanResource ${HEADERS} ${SOURCE}) +target_link_libraries(toucanResource) +set_target_properties(toucanResource PROPERTIES FOLDER lib) +set_target_properties(toucanResource PROPERTIES PUBLIC_HEADER "${HEADERS}") + +if(BUILD_SHARED_LIBS) + install( + TARGETS toucanResource + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) +endif() diff --git a/lib/toucanView/AudioClipItem.cpp b/lib/toucanView/AudioClipItem.cpp index 6efab0f..5138422 100644 --- a/lib/toucanView/AudioClipItem.cpp +++ b/lib/toucanView/AudioClipItem.cpp @@ -17,27 +17,24 @@ namespace toucan void AudioClipItem::_init( const std::shared_ptr& context, const ItemData& data, - const OTIO_NS::SerializableObject::Retainer& clip, - const OTIO_NS::SerializableObject::Retainer& timeline, + const OTIO_NS::Clip* clip, const ftk::Color4F& color, const std::shared_ptr& parent) { + auto timelineWrapper = data.file->getTimelineWrapper(); OTIO_NS::TimeRange timeRange = clip->transformed_time_range( clip->trimmed_range(), - timeline->tracks()); - if (timeline->global_start_time().has_value()) - { - timeRange = OTIO_NS::TimeRange( - timeline->global_start_time().value() + timeRange.start_time(), - timeRange.duration()); - } + timelineWrapper->getTimeline()->tracks()); + timeRange = OTIO_NS::TimeRange( + timelineWrapper->getTimeRange().start_time() + timeRange.start_time(), + timeRange.duration()); timeRange = OTIO_NS::TimeRange( timeRange.start_time().round(), timeRange.duration().round()); IItem::_init( context, data, - OTIO_NS::dynamic_retainer_cast(clip), + clip, timeRange, "toucan::AudioClipItem", parent); @@ -64,13 +61,10 @@ namespace toucan OTIO_NS::TimeRange markerRange( marker->marked_range().start_time() + trimmedRange.start_time(), marker->marked_range().duration()); - markerRange = clip->transformed_time_range(markerRange, timeline->tracks()); - if (timeline->global_start_time().has_value()) - { - markerRange = OTIO_NS::TimeRange( - timeline->global_start_time().value() + markerRange.start_time(), - markerRange.duration()); - } + markerRange = clip->transformed_time_range(markerRange, timelineWrapper->getTimeline()->tracks()); + markerRange = OTIO_NS::TimeRange( + timelineWrapper->getTimeRange().start_time() + markerRange.start_time(), + markerRange.duration()); auto markerItem = MarkerItem::create( context, data, @@ -90,13 +84,12 @@ namespace toucan std::shared_ptr AudioClipItem::create( const std::shared_ptr& context, const ItemData& data, - const OTIO_NS::SerializableObject::Retainer& clip, - const OTIO_NS::SerializableObject::Retainer& timeline, + const OTIO_NS::Clip* clip, const ftk::Color4F& color, const std::shared_ptr& parent) { auto out = std::make_shared(); - out->_init(context, data, clip, timeline, color, parent); + out->_init(context, data, clip, color, parent); return out; } @@ -115,7 +108,6 @@ namespace toucan _layout->setGeometry(value); _geom.g2 = ftk::margin(value, -_size.border, 0, -_size.border, 0); _geom.g3 = ftk::margin(_label->getGeometry(), -_size.border, 0, -_size.border, 0); - _selectionRect = _geom.g3; } ftk::Box2I AudioClipItem::getChildrenClipRect() const @@ -159,8 +151,7 @@ namespace toucan "Open Media", [this, externalReference] { - auto file = _file.lock(); - const std::filesystem::path path = file->getTimelineWrapper()->getMediaPath(externalReference->target_url()); + const std::filesystem::path path = _file->getTimelineWrapper()->getMediaPath(externalReference->target_url()); auto app = _app.lock(); app->open(path); }); diff --git a/lib/toucanView/AudioClipItem.h b/lib/toucanView/AudioClipItem.h index 505aa2e..762b972 100644 --- a/lib/toucanView/AudioClipItem.h +++ b/lib/toucanView/AudioClipItem.h @@ -20,8 +20,7 @@ namespace toucan void _init( const std::shared_ptr&, const ItemData&, - const OTIO_NS::SerializableObject::Retainer&, - const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::Clip*, const ftk::Color4F&, const std::shared_ptr& parent); @@ -32,8 +31,7 @@ namespace toucan static std::shared_ptr create( const std::shared_ptr&, const ItemData&, - const OTIO_NS::SerializableObject::Retainer&, - const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::Clip*, const ftk::Color4F&, const std::shared_ptr& parent = nullptr); @@ -51,7 +49,8 @@ namespace toucan private: void _textUpdate(); - OTIO_NS::SerializableObject::Retainer _clip; + const OTIO_NS::Timeline* _timeline = nullptr; + const OTIO_NS::Clip* _clip = nullptr; std::string _text; ftk::Color4F _color; diff --git a/lib/toucanView/File.cpp b/lib/toucanView/File.cpp index 800b23b..c8d53e9 100644 --- a/lib/toucanView/File.cpp +++ b/lib/toucanView/File.cpp @@ -5,7 +5,6 @@ #include "PlaybackModel.h" #include "SelectionModel.h" -#include "ThumbnailGenerator.h" #include "ViewModel.h" #include @@ -32,12 +31,6 @@ namespace toucan _selectionModel = std::make_shared(); - _thumbnailGenerator = std::make_shared( - context, - _path.parent_path(), - _timelineWrapper, - _host); - _currentImage = ftk::ObservableValue >::create(); _rootNode = ftk::ObservableValue >::create(); @@ -93,11 +86,6 @@ namespace toucan return _selectionModel; } - const std::shared_ptr& File::getThumbnailGenerator() const - { - return _thumbnailGenerator; - } - const IMATH_NAMESPACE::V2i& File::getImageSize() const { return _graph->getImageSize(); diff --git a/lib/toucanView/File.h b/lib/toucanView/File.h index 46738c3..9dd210d 100644 --- a/lib/toucanView/File.h +++ b/lib/toucanView/File.h @@ -17,7 +17,6 @@ namespace toucan { class PlaybackModel; class SelectionModel; - class ThumbnailGenerator; class ViewModel; //! Timeline file. @@ -49,9 +48,6 @@ namespace toucan //! Get the selection model. const std::shared_ptr& getSelectionModel() const; - //! Get the thumbnail generator. - const std::shared_ptr& getThumbnailGenerator() const; - //! Get the image size. const IMATH_NAMESPACE::V2i& getImageSize() const; @@ -82,7 +78,6 @@ namespace toucan std::shared_ptr _playbackModel; std::shared_ptr _viewModel; std::shared_ptr _selectionModel; - std::shared_ptr _thumbnailGenerator; std::shared_ptr > > _currentImage; OTIO_NS::RationalTime _currentTime; diff --git a/lib/toucanView/GapItem.cpp b/lib/toucanView/GapItem.cpp index d42de46..1090b31 100644 --- a/lib/toucanView/GapItem.cpp +++ b/lib/toucanView/GapItem.cpp @@ -3,6 +3,8 @@ #include "GapItem.h" +#include "File.h" + #include namespace toucan @@ -10,23 +12,23 @@ namespace toucan void GapItem::_init( const std::shared_ptr& context, const ItemData& data, - const OTIO_NS::SerializableObject::Retainer& gap, - const OTIO_NS::SerializableObject::Retainer& timeline, + const OTIO_NS::Gap* gap, const std::shared_ptr& parent) { + auto timelineWrapper = data.file->getTimelineWrapper(); OTIO_NS::TimeRange timeRange = gap->transformed_time_range( gap->trimmed_range(), - timeline->tracks()); - if (timeline->global_start_time().has_value()) - { - timeRange = OTIO_NS::TimeRange( - timeline->global_start_time().value() + timeRange.start_time(), - timeRange.duration()); - } + timelineWrapper->getTimeline()->tracks()); + timeRange = OTIO_NS::TimeRange( + timelineWrapper->getTimeRange().start_time() + timeRange.start_time(), + timeRange.duration()); + timeRange = OTIO_NS::TimeRange( + timeRange.start_time().round(), + timeRange.duration().round()); IItem::_init( context, data, - OTIO_NS::dynamic_retainer_cast(gap), + gap, timeRange, "toucan::ClipItem", parent); @@ -51,13 +53,10 @@ namespace toucan { OTIO_NS::TimeRange markerTimeRange = gap->transformed_time_range( marker->marked_range(), - timeline->tracks()); - if (timeline->global_start_time().has_value()) - { - markerTimeRange = OTIO_NS::TimeRange( - timeline->global_start_time().value() + markerTimeRange.start_time(), - markerTimeRange.duration()); - } + timelineWrapper->getTimeline()->tracks()); + markerTimeRange = OTIO_NS::TimeRange( + timelineWrapper->getTimeRange().start_time() + markerTimeRange.start_time(), + markerTimeRange.duration()); auto markerItem = MarkerItem::create( context, data, @@ -77,12 +76,11 @@ namespace toucan std::shared_ptr GapItem::create( const std::shared_ptr& context, const ItemData& data, - const OTIO_NS::SerializableObject::Retainer& gap, - const OTIO_NS::SerializableObject::Retainer& timeline, + const OTIO_NS::Gap* gap, const std::shared_ptr& parent) { auto out = std::make_shared(); - out->_init(context, data, gap, timeline, parent); + out->_init(context, data, gap, parent); return out; } @@ -101,7 +99,6 @@ namespace toucan _layout->setGeometry(value); _geom.g2 = ftk::margin(value, -_size.border, 0, -_size.border, 0); _geom.g3 = ftk::margin(_label->getGeometry(), -_size.border, 0, -_size.border, 0); - _selectionRect = _geom.g3; } ftk::Box2I GapItem::getChildrenClipRect() const diff --git a/lib/toucanView/GapItem.h b/lib/toucanView/GapItem.h index 8c38eab..f52c749 100644 --- a/lib/toucanView/GapItem.h +++ b/lib/toucanView/GapItem.h @@ -18,8 +18,7 @@ namespace toucan void _init( const std::shared_ptr&, const ItemData&, - const OTIO_NS::SerializableObject::Retainer&, - const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::Gap*, const std::shared_ptr& parent); public: @@ -29,8 +28,7 @@ namespace toucan static std::shared_ptr create( const std::shared_ptr&, const ItemData&, - const OTIO_NS::SerializableObject::Retainer&, - const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::Gap*, const std::shared_ptr& parent = nullptr); void setScale(double) override; @@ -46,7 +44,7 @@ namespace toucan private: void _textUpdate(); - OTIO_NS::SerializableObject::Retainer _gap; + const OTIO_NS::Gap* _gap = nullptr; std::string _text; ftk::Color4F _color; diff --git a/lib/toucanView/IItem.cpp b/lib/toucanView/IItem.cpp index 017e087..bf1fdb0 100644 --- a/lib/toucanView/IItem.cpp +++ b/lib/toucanView/IItem.cpp @@ -12,7 +12,7 @@ namespace toucan void IItem::_init( const std::shared_ptr& context, const ItemData& data, - const OTIO_NS::SerializableObject::Retainer& object, + const OTIO_NS::SerializableObjectWithMetadata* object, const OTIO_NS::TimeRange& timeRange, const std::string& objectName, const std::shared_ptr& parent) @@ -35,16 +35,11 @@ namespace toucan IItem::~IItem() {} - const OTIO_NS::SerializableObject::Retainer& IItem::getObject() const + const OTIO_NS::SerializableObjectWithMetadata* IItem::getObject() const { return _object; } - const ftk::Box2I& IItem::getSelectionRect() const - { - return _selectionRect; - } - bool IItem::isSelected() const { return _selected; @@ -58,12 +53,6 @@ namespace toucan _setDrawUpdate(); } - void IItem::setGeometry(const ftk::Box2I& value) - { - ITimeWidget::setGeometry(value); - _selectionRect = value; - } - void IItem::mousePressEvent(ftk::MouseClickEvent& event) { ITimeWidget::mousePressEvent(event); @@ -102,10 +91,7 @@ namespace toucan "Go To Start", [this] { - if (auto file = _file.lock()) - { - file->getPlaybackModel()->setCurrentTime(_timeRange.start_time()); - } + _file->getPlaybackModel()->setCurrentTime(_timeRange.start_time()); }); menu->addAction(action); @@ -117,10 +103,7 @@ namespace toucan "Set In/Out Points", [this] { - if (auto file = _file.lock()) - { - file->getPlaybackModel()->setInOutRange(_timeRange); - } + _file->getPlaybackModel()->setInOutRange(_timeRange); }); menu->addAction(action); } @@ -129,19 +112,12 @@ namespace toucan "Reset In/Out Points", [this] { - if (auto file = _file.lock()) - { - file->getPlaybackModel()->resetInOutPoints(); - } + _file->getPlaybackModel()->resetInOutPoints(); }); menu->addAction(action); - bool enabled = true; - if (auto file = _file.lock()) - { - enabled = - file->getPlaybackModel()->getInOutRange() != - file->getPlaybackModel()->getTimeRange(); - } + const bool enabled = + _file->getPlaybackModel()->getInOutRange() != + _file->getPlaybackModel()->getTimeRange(); menu->setEnabled(action, enabled); } } diff --git a/lib/toucanView/IItem.h b/lib/toucanView/IItem.h index b4adb03..6a80ef8 100644 --- a/lib/toucanView/IItem.h +++ b/lib/toucanView/IItem.h @@ -34,7 +34,7 @@ namespace toucan void _init( const std::shared_ptr&, const ItemData&, - const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::SerializableObjectWithMetadata*, const OTIO_NS::TimeRange&, const std::string& objectName, const std::shared_ptr& parent); @@ -43,10 +43,7 @@ namespace toucan virtual ~IItem() = 0; //! Get the OTIO object. - const OTIO_NS::SerializableObject::Retainer& getObject() const; - - //! Get the item selection rectangle. - const ftk::Box2I& getSelectionRect() const; + const OTIO_NS::SerializableObjectWithMetadata* getObject() const; //! Get whether the item is selected. bool isSelected() const; @@ -54,7 +51,6 @@ namespace toucan //! Set whether the item is selected. void setSelected(bool); - void setGeometry(const ftk::Box2I&) override; void mousePressEvent(ftk::MouseClickEvent&) override; void mouseReleaseEvent(ftk::MouseClickEvent&) override; @@ -63,10 +59,9 @@ namespace toucan virtual void _buildMenu(const std::shared_ptr&); std::weak_ptr _app; - std::weak_ptr _file; - OTIO_NS::SerializableObject::Retainer _object; + std::shared_ptr _file; + const OTIO_NS::SerializableObjectWithMetadata* _object = nullptr; TimeUnits _timeUnits = TimeUnits::First; - ftk::Box2I _selectionRect; bool _selected = false; std::shared_ptr _menu; diff --git a/lib/toucanView/MainWindow.cpp b/lib/toucanView/MainWindow.cpp index 9f4eb73..3586bc6 100644 --- a/lib/toucanView/MainWindow.cpp +++ b/lib/toucanView/MainWindow.cpp @@ -26,6 +26,11 @@ #include +namespace toucan_resource +{ + extern std::vector toucan_64; +} + namespace toucan { void MainWindow::_init( @@ -54,6 +59,10 @@ namespace toucan {} setDisplayScale(displayScale); + auto iconSystem = context->getSystem(); + iconSystem->add("toucan_64", toucan_resource::toucan_64); + setIcon(iconSystem->get("toucan_64", 1.0)); + _layout = ftk::VerticalLayout::create(context, shared_from_this()); _layout->setSpacingRole(ftk::SizeRole::None); diff --git a/lib/toucanView/MarkerItem.cpp b/lib/toucanView/MarkerItem.cpp index 9c242e9..2653e81 100644 --- a/lib/toucanView/MarkerItem.cpp +++ b/lib/toucanView/MarkerItem.cpp @@ -60,14 +60,14 @@ namespace toucan void MarkerItem::_init( const std::shared_ptr& context, const ItemData& data, - const OTIO_NS::SerializableObject::Retainer& marker, + const OTIO_NS::Marker* marker, const OTIO_NS::TimeRange& timeRange, const std::shared_ptr& parent) { IItem::_init( context, data, - OTIO_NS::dynamic_retainer_cast(marker), + marker, timeRange, "toucan::MarkerItem", parent); @@ -90,7 +90,7 @@ namespace toucan std::shared_ptr MarkerItem::create( const std::shared_ptr& context, const ItemData& data, - const OTIO_NS::SerializableObject::Retainer& marker, + const OTIO_NS::Marker* marker, const OTIO_NS::TimeRange& timeRange, const std::shared_ptr& parent) { diff --git a/lib/toucanView/MarkerItem.h b/lib/toucanView/MarkerItem.h index 4ba9984..fb3fe05 100644 --- a/lib/toucanView/MarkerItem.h +++ b/lib/toucanView/MarkerItem.h @@ -22,7 +22,7 @@ namespace toucan void _init( const std::shared_ptr&, const ItemData&, - const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::Marker*, const OTIO_NS::TimeRange&, const std::shared_ptr& parent); @@ -33,7 +33,7 @@ namespace toucan static std::shared_ptr create( const std::shared_ptr&, const ItemData&, - const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::Marker*, const OTIO_NS::TimeRange&, const std::shared_ptr& parent = nullptr); @@ -47,7 +47,8 @@ namespace toucan private: void _textUpdate(); - OTIO_NS::SerializableObject::Retainer _marker; + const OTIO_NS::Timeline* _timeline = nullptr; + const OTIO_NS::Marker* _marker = nullptr; std::string _text; ftk::Color4F _color; diff --git a/lib/toucanView/PlaybackModel.cpp b/lib/toucanView/PlaybackModel.cpp index 9e46bb2..774a127 100644 --- a/lib/toucanView/PlaybackModel.cpp +++ b/lib/toucanView/PlaybackModel.cpp @@ -99,22 +99,34 @@ namespace toucan setCurrentTime(timeRange.start_time()); break; case TimeAction::FramePrev: - setCurrentTime(currentTime - OTIO_NS::RationalTime(1.0, currentTime.rate())); + setCurrentTime( + currentTime - OTIO_NS::RationalTime(1.0, currentTime.rate()), + CurrentTime::Loop); break; case TimeAction::FramePrevX10: - setCurrentTime(currentTime - OTIO_NS::RationalTime(10.0, currentTime.rate())); + setCurrentTime( + currentTime - OTIO_NS::RationalTime(10.0, currentTime.rate()), + CurrentTime::Loop); break; case TimeAction::FramePrevX100: - setCurrentTime(currentTime - OTIO_NS::RationalTime(100.0, currentTime.rate())); + setCurrentTime( + currentTime - OTIO_NS::RationalTime(100.0, currentTime.rate()), + CurrentTime::Loop); break; case TimeAction::FrameNext: - setCurrentTime(currentTime + OTIO_NS::RationalTime(1.0, currentTime.rate())); + setCurrentTime( + currentTime + OTIO_NS::RationalTime(1.0, currentTime.rate()), + CurrentTime::Loop); break; case TimeAction::FrameNextX10: - setCurrentTime(currentTime + OTIO_NS::RationalTime(10.0, currentTime.rate())); + setCurrentTime( + currentTime + OTIO_NS::RationalTime(10.0, currentTime.rate()), + CurrentTime::Loop); break; case TimeAction::FrameNextX100: - setCurrentTime(currentTime + OTIO_NS::RationalTime(100.0, currentTime.rate())); + setCurrentTime( + currentTime + OTIO_NS::RationalTime(100.0, currentTime.rate()), + CurrentTime::Loop); break; case TimeAction::FrameEnd: setCurrentTime(timeRange.end_time_inclusive()); diff --git a/lib/toucanView/StackItem.cpp b/lib/toucanView/StackItem.cpp index ea98d72..c7fb24a 100644 --- a/lib/toucanView/StackItem.cpp +++ b/lib/toucanView/StackItem.cpp @@ -3,7 +3,11 @@ #include "StackItem.h" +#include "App.h" +#include "File.h" +#include "ThumbnailsWidget.h" #include "TrackItem.h" +#include "WindowModel.h" #include #include @@ -13,24 +17,21 @@ namespace toucan void StackItem::_init( const std::shared_ptr& context, const ItemData& data, - const OTIO_NS::SerializableObject::Retainer& stack, - const OTIO_NS::SerializableObject::Retainer& timeline , + const OTIO_NS::Stack* stack, const std::shared_ptr& parent) { + auto timelineWrapper = data.file->getTimelineWrapper(); OTIO_NS::TimeRange timeRange = stack->trimmed_range(); - if (timeline->global_start_time().has_value()) - { - timeRange = OTIO_NS::TimeRange( - timeline->global_start_time().value() + timeRange.start_time(), - timeRange.duration()); - } + timeRange = OTIO_NS::TimeRange( + timelineWrapper->getTimeRange().start_time() + timeRange.start_time(), + timeRange.duration()); timeRange = OTIO_NS::TimeRange( timeRange.start_time().round(), timeRange.duration().round()); IItem::_init( context, data, - OTIO_NS::dynamic_retainer_cast(stack), + stack, timeRange, "toucan::StackItem", parent); @@ -47,24 +48,30 @@ namespace toucan _label = ItemLabel::create(context, _layout); _label->setName(_text); + _thumbnailsWidget = ThumbnailsWidget::create( + context, + timelineWrapper, + _stack, + data.thumbnailGenerator, + data.thumbnailCache, + timeRange, + _layout); + const auto& markers = stack->markers(); if (!markers.empty()) { _markerLayout = TimeLayout::create(context, timeRange, _layout); for (const auto& marker : markers) { - OTIO_NS::TimeRange markerTimeRange = marker->marked_range(); - if (timeline->global_start_time().has_value()) - { - markerTimeRange = OTIO_NS::TimeRange( - timeline->global_start_time().value() + markerTimeRange.start_time(), - markerTimeRange.duration()); - } + OTIO_NS::TimeRange markerRange = marker->marked_range(); + markerRange = OTIO_NS::TimeRange( + timelineWrapper->getTimeRange().start_time() + markerRange.start_time(), + markerRange.duration()); auto markerItem = MarkerItem::create( context, data, marker, - markerTimeRange, + markerRange, _markerLayout); _markerItems.push_back(markerItem); } @@ -79,12 +86,18 @@ namespace toucan context, data, track, - timeline, _timeLayout); } } _textUpdate(); + + _thumbnailsObserver = ftk::ValueObserver::create( + data.app->getWindowModel()->observeThumbnails(), + [this](bool value) + { + _thumbnailsWidget->setVisible(value); + }); } StackItem::~StackItem() @@ -93,18 +106,18 @@ namespace toucan std::shared_ptr StackItem::create( const std::shared_ptr& context, const ItemData& data, - const OTIO_NS::SerializableObject::Retainer& stack, - const OTIO_NS::SerializableObject::Retainer& timeline, + const OTIO_NS::Stack* stack, const std::shared_ptr& parent) { auto out = std::make_shared(); - out->_init(context, data, stack, timeline, parent); + out->_init(context, data, stack, parent); return out; } void StackItem::setScale(double value) { IItem::setScale(value); + _thumbnailsWidget->setScale(value); if (_markerLayout) { _markerLayout->setScale(value); @@ -118,7 +131,6 @@ namespace toucan _layout->setGeometry(value); _geom.g2 = ftk::margin(value, -_size.border, 0, -_size.border, 0); _geom.g3 = ftk::margin(_label->getGeometry(), -_size.border, 0, -_size.border, 0); - _selectionRect = _geom.g3; } ftk::Box2I StackItem::getChildrenClipRect() const diff --git a/lib/toucanView/StackItem.h b/lib/toucanView/StackItem.h index 86246e9..ce73dd7 100644 --- a/lib/toucanView/StackItem.h +++ b/lib/toucanView/StackItem.h @@ -11,6 +11,8 @@ namespace toucan { + class ThumbnailsWidget; + //! Timeline stack item. class StackItem : public IItem { @@ -18,8 +20,7 @@ namespace toucan void _init( const std::shared_ptr&, const ItemData&, - const OTIO_NS::SerializableObject::Retainer&, - const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::Stack*, const std::shared_ptr& parent); public: @@ -29,8 +30,7 @@ namespace toucan static std::shared_ptr create( const std::shared_ptr&, const ItemData&, - const OTIO_NS::SerializableObject::Retainer&, - const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::Stack*, const std::shared_ptr& parent = nullptr); void setScale(double) override; @@ -46,16 +46,19 @@ namespace toucan private: void _textUpdate(); - OTIO_NS::SerializableObject::Retainer _stack; + const OTIO_NS::Stack* _stack = nullptr; std::string _text; ftk::Color4F _color; std::shared_ptr _layout; std::shared_ptr _label; + std::shared_ptr _thumbnailsWidget; std::shared_ptr _markerLayout; std::vector > _markerItems; std::shared_ptr _timeLayout; + std::shared_ptr > _thumbnailsObserver; + struct SizeData { bool init = true; diff --git a/lib/toucanView/ThumbnailGenerator.cpp b/lib/toucanView/ThumbnailGenerator.cpp index 3cf6b17..ae521a8 100644 --- a/lib/toucanView/ThumbnailGenerator.cpp +++ b/lib/toucanView/ThumbnailGenerator.cpp @@ -3,10 +3,14 @@ #include "ThumbnailGenerator.h" +#include +#include #include +#include #include +#include #include #include @@ -15,13 +19,13 @@ namespace toucan { std::string getThumbnailCacheKey( - const OTIO_NS::MediaReference* ref, + const OTIO_NS::Item* item, const OTIO_NS::RationalTime& time, int height) { std::vector s; std::stringstream ss; - ss << ref; + ss << item; s.push_back(ss.str()); s.push_back(ftk::Format("{0}@{1}").arg(time.value()).arg(time.rate())); s.push_back(ftk::Format("{0}").arg(height)); @@ -30,36 +34,22 @@ namespace toucan ThumbnailGenerator::ThumbnailGenerator( const std::shared_ptr& context, - const std::filesystem::path& path, - const std::shared_ptr& timelineWrapper, - const std::shared_ptr& host) : - _path(path), - _timelineWrapper(timelineWrapper), - _host(host) + const std::shared_ptr& host, + const std::shared_ptr& timelineWrapper) : + _host(host), + _timelineWrapper(timelineWrapper) { _logSystem = context->getSystem(); - _graph = std::make_shared(context, _path, _timelineWrapper); + _graph = std::make_shared( + context, + timelineWrapper->getPath().parent_path(), + timelineWrapper); _thread.running = true; _thread.thread = std::thread( [this] { - try - { - const IMATH_NAMESPACE::V2i& imageSize = _graph->getImageSize(); - _aspect = imageSize.y > 0 ? - (imageSize.x / static_cast(imageSize.y)) : - 0.F; - } - catch (const std::exception& e) - { - _logSystem->print( - "toucan::ThumbnailGenerator", - e.what(), - ftk::LogType::Error); - } - while (_thread.running) { _run(); @@ -81,41 +71,21 @@ namespace toucan } } - float ThumbnailGenerator::getAspect() const - { - return _aspect; - } - - ThumbnailRequest ThumbnailGenerator::getThumbnail( - const OTIO_NS::RationalTime& time, - int height) - { - return getThumbnail(nullptr, time, height); - } - - ThumbnailRequest ThumbnailGenerator::getThumbnail( - const OTIO_NS::MediaReference* ref, - const OTIO_NS::RationalTime& time, - int height) + std::future ThumbnailGenerator::getAspect( + const OTIO_NS::Item* item, + const OTIO_NS::RationalTime& time) { - _requestId++; - auto request = std::make_shared(); - request->id = _requestId; - request->ref = ref; + auto request = std::make_shared(); + request->item = item; request->time = time; - request->height = height; - ThumbnailRequest out; - out.id = _requestId; - out.height = height; - out.time = time; - out.future = request->promise.get_future(); + auto out = request->promise.get_future(); bool valid = false; { std::unique_lock lock(_mutex.mutex); if (!_mutex.stopped) { valid = true; - _mutex.requests.push_back(request); + _mutex.aspectRequests.push_back(request); } } if (valid) @@ -124,26 +94,34 @@ namespace toucan } else { - request->promise.set_value(nullptr); + request->promise.set_value(0.F); } return out; } - std::future ThumbnailGenerator::getAspect( - const OTIO_NS::MediaReference* ref, - const OTIO_NS::RationalTime& time) + ThumbnailRequest ThumbnailGenerator::getThumbnail( + const OTIO_NS::Item* item, + const OTIO_NS::RationalTime& time, + int height) { - auto request = std::make_shared(); - request->ref = ref; + _requestId++; + auto request = std::make_shared(); + request->id = _requestId; + request->item = item; request->time = time; - auto out = request->promise.get_future(); + request->height = height; + ThumbnailRequest out; + out.id = _requestId; + out.height = height; + out.time = time; + out.future = request->promise.get_future(); bool valid = false; { std::unique_lock lock(_mutex.mutex); if (!_mutex.stopped) { valid = true; - _mutex.aspectRequests.push_back(request); + _mutex.requests.push_back(request); } } if (valid) @@ -152,7 +130,7 @@ namespace toucan } else { - request->promise.set_value(0.F); + request->promise.set_value(nullptr); } return out; } @@ -208,26 +186,14 @@ namespace toucan } if (aspectRequest) { - float aspect = 0.F; - auto node = _graph->exec(_host, aspectRequest->time); - if (node = _findNode(node, aspectRequest->ref)) + float aspect = 1.F; + if (auto node = _graph->exec(_host, aspectRequest->time, aspectRequest->item)) { - try - { - node->setTime(aspectRequest->time - _timelineWrapper->getTimeRange().start_time()); - const OIIO::ImageBuf buf = node->exec(); - const OIIO::ImageSpec& spec = buf.spec(); - if (spec.height > 0) - { - aspect = spec.width / static_cast(spec.height); - } - } - catch (const std::exception& e) + OIIO::ImageBuf buf = node->exec(); + const auto& spec = buf.spec(); + if (spec.width > 0 && spec.height > 0) { - _logSystem->print( - "toucan::ThumbnailGenerator", - e.what(), - ftk::LogType::Error); + aspect = spec.width / static_cast(spec.height); } } aspectRequest->promise.set_value(aspect); @@ -235,28 +201,9 @@ namespace toucan if (request) { OIIO::ImageBuf buf; - try - { - auto node = _graph->exec(_host, request->time); - if (request->ref) - { - node = _findNode(node, request->ref); - if (node) - { - node->setTime(request->time - _timelineWrapper->getTimeRange().start_time()); - } - } - if (node) - { - buf = node->exec(); - } - } - catch (const std::exception& e) + if (auto node = _graph->exec(_host, request->time, request->item)) { - _logSystem->print( - "toucan::ThumbnailGenerator", - e.what(), - ftk::LogType::Error); + buf = node->exec(); } std::shared_ptr thumbnail; @@ -342,31 +289,4 @@ namespace toucan request->promise.set_value(nullptr); } } - - std::shared_ptr ThumbnailGenerator::_findNode( - const std::shared_ptr& node, - const OTIO_NS::MediaReference* ref) - { - std::shared_ptr out; - if (node) - { - auto read = std::dynamic_pointer_cast(node); - if (read && read->getRef() == ref) - { - out = node; - } - else - { - for (const auto& input : node->getInputs()) - { - out = _findNode(input, ref); - if (out) - { - break; - } - } - } - } - return out; - } } diff --git a/lib/toucanView/ThumbnailGenerator.h b/lib/toucanView/ThumbnailGenerator.h index 35ed584..1d50040 100644 --- a/lib/toucanView/ThumbnailGenerator.h +++ b/lib/toucanView/ThumbnailGenerator.h @@ -3,9 +3,7 @@ #pragma once -#include -#include -#include +#include #include #include @@ -19,9 +17,14 @@ namespace toucan { + class IReadNode; + class ImageEffectHost; + class ImageGraph; + class TimelineWrapper; + //! Get a thumbnail cache key. std::string getThumbnailCacheKey( - const OTIO_NS::MediaReference*, + const OTIO_NS::Item*, const OTIO_NS::RationalTime&, int height); @@ -40,28 +43,19 @@ namespace toucan public: ThumbnailGenerator( const std::shared_ptr&, - const std::filesystem::path&, - const std::shared_ptr&, - const std::shared_ptr&); + const std::shared_ptr&, + const std::shared_ptr&); ~ThumbnailGenerator(); - //! Get the timeline aspect ratio. - float getAspect() const; - - //! Get a timeline thumbnail. - ThumbnailRequest getThumbnail( - const OTIO_NS::RationalTime&, - int height); - - //! Get a media aspect ratio. + //! Get an aspect ratio. std::future getAspect( - const OTIO_NS::MediaReference*, + const OTIO_NS::Item*, const OTIO_NS::RationalTime&); - //! Get a media thumbnail. + //! Get a thumbnail. ThumbnailRequest getThumbnail( - const OTIO_NS::MediaReference*, + const OTIO_NS::Item*, const OTIO_NS::RationalTime&, int height); @@ -72,20 +66,14 @@ namespace toucan void _run(); void _cancel(); - std::shared_ptr _findNode( - const std::shared_ptr&, - const OTIO_NS::MediaReference*); - std::shared_ptr _logSystem; - std::filesystem::path _path; - std::shared_ptr _timelineWrapper; std::shared_ptr _host; + std::shared_ptr _timelineWrapper; std::shared_ptr _graph; - float _aspect = 1.F; struct AspectRequest { - const OTIO_NS::MediaReference* ref = nullptr; + const OTIO_NS::Item* item = nullptr; OTIO_NS::RationalTime time; std::promise promise; }; @@ -93,7 +81,7 @@ namespace toucan struct Request { uint64_t id = 0; - const OTIO_NS::MediaReference* ref = nullptr; + const OTIO_NS::Item* item = nullptr; OTIO_NS::RationalTime time; int height = 0; std::promise > promise; diff --git a/lib/toucanView/ThumbnailsWidget.cpp b/lib/toucanView/ThumbnailsWidget.cpp index 5d88051..14a93b5 100644 --- a/lib/toucanView/ThumbnailsWidget.cpp +++ b/lib/toucanView/ThumbnailsWidget.cpp @@ -4,6 +4,7 @@ #include "ThumbnailsWidget.h" #include +#include #include #include @@ -12,17 +13,23 @@ namespace toucan { void ThumbnailsWidget::_init( const std::shared_ptr& context, - const OTIO_NS::SerializableObject::Retainer& ref, + const std::shared_ptr& timelineWrapper, + const OTIO_NS::Item* item, const std::shared_ptr& thumbnailGenerator, const std::shared_ptr > >& thumbnailCache, const OTIO_NS::TimeRange& timeRange, const std::shared_ptr& parent) { ITimeWidget::_init(context, timeRange, "toucan::ThumbnailsWidget", parent); - _ref = ref; + + _timelineWrapper = timelineWrapper; + _item = item; _thumbnailGenerator = thumbnailGenerator; _thumbnailCache = thumbnailCache; - _thumbnailAspectRequest = _thumbnailGenerator->getAspect(ref, timeRange.start_time()); + + _thumbnailAspectRequest = _thumbnailGenerator->getAspect( + _item, + timeRange.start_time()); } ThumbnailsWidget::~ThumbnailsWidget() @@ -30,17 +37,28 @@ namespace toucan std::shared_ptr ThumbnailsWidget::create( const std::shared_ptr& context, - const OTIO_NS::SerializableObject::Retainer& ref, + const std::shared_ptr& timelineWrapper, + const OTIO_NS::Item* item, const std::shared_ptr& thumbnailGenerator, const std::shared_ptr > >& thumbnailCache, const OTIO_NS::TimeRange& timeRange, const std::shared_ptr& parent) { auto out = std::make_shared(); - out->_init(context, ref, thumbnailGenerator, thumbnailCache, timeRange, parent); + out->_init(context, timelineWrapper, item, thumbnailGenerator, thumbnailCache, timeRange, parent); return out; } + void ThumbnailsWidget::setScale(double value) + { + const bool changed = value != _scale; + ITimeWidget::setScale(value); + if (changed) + { + _cancelThumbnails(); + } + } + void ThumbnailsWidget::tickEvent( bool parentsVisible, bool parentsEnabled, @@ -58,7 +76,7 @@ namespace toucan if (i->future.valid() && i->future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { - const std::string cacheKey = getThumbnailCacheKey(_ref, i->time, _size.thumbnailHeight); + const std::string cacheKey = getThumbnailCacheKey(_item, i->time, _size.thumbnailHeight); const auto image = i->future.get(); _thumbnailCache->add(cacheKey, image); _setDrawUpdate(); @@ -90,6 +108,15 @@ namespace toucan } _setSizeHint(ftk::Size2I(0, _size.thumbnailHeight)); } + + void ThumbnailsWidget::clipEvent(const ftk::Box2I& clipRect, bool clipped) + { + ITimeWidget::clipEvent(clipRect, clipped); + if (clipped) + { + _cancelThumbnails(); + } + } void ThumbnailsWidget::drawEvent( const ftk::Box2I& drawRect, @@ -106,8 +133,8 @@ namespace toucan if (ftk::intersects(g2, drawRect)) { const OTIO_NS::RationalTime t = posToTime(x); + const std::string cacheKey = getThumbnailCacheKey(_item, t, _size.thumbnailHeight); std::shared_ptr image; - const std::string cacheKey = getThumbnailCacheKey(_ref, t, _size.thumbnailHeight); if (_thumbnailCache->get(cacheKey, image)) { if (image) @@ -129,7 +156,7 @@ namespace toucan if (j == _thumbnailRequests.end()) { _thumbnailRequests.push_back(_thumbnailGenerator->getThumbnail( - _ref, + _item, t, _size.thumbnailHeight)); } @@ -155,4 +182,15 @@ namespace toucan } _thumbnailGenerator->cancelThumbnails(cancel); } + + void ThumbnailsWidget::_cancelThumbnails() + { + std::vector cancel; + for (const auto& request : _thumbnailRequests) + { + cancel.push_back(request.id); + } + _thumbnailRequests.clear(); + _thumbnailGenerator->cancelThumbnails(cancel); + } } diff --git a/lib/toucanView/ThumbnailsWidget.h b/lib/toucanView/ThumbnailsWidget.h index 356133e..42a3f73 100644 --- a/lib/toucanView/ThumbnailsWidget.h +++ b/lib/toucanView/ThumbnailsWidget.h @@ -6,17 +6,22 @@ #include #include -#include +#include + +#include namespace toucan { + class TimelineWrapper; + //! Timeline thumbnails widget. class ThumbnailsWidget : public ITimeWidget { protected: void _init( const std::shared_ptr&, - const OTIO_NS::SerializableObject::Retainer&, + const std::shared_ptr&, + const OTIO_NS::Item*, const std::shared_ptr&, const std::shared_ptr > >&, const OTIO_NS::TimeRange&, @@ -28,21 +33,28 @@ namespace toucan //! Create a new widget. static std::shared_ptr create( const std::shared_ptr&, - const OTIO_NS::SerializableObject::Retainer&, + const std::shared_ptr&, + const OTIO_NS::Item*, const std::shared_ptr&, const std::shared_ptr > >&, const OTIO_NS::TimeRange&, const std::shared_ptr& parent = nullptr); + + void setScale(double) override; void tickEvent( bool parentsVisible, bool parentsEnabled, const ftk::TickEvent&) override; void sizeHintEvent(const ftk::SizeHintEvent&) override; + void clipEvent(const ftk::Box2I&, bool clipped) override; void drawEvent(const ftk::Box2I&, const ftk::DrawEvent&) override; private: - OTIO_NS::SerializableObject::Retainer _ref; + void _cancelThumbnails(); + + std::shared_ptr _timelineWrapper; + const OTIO_NS::Item* _item = nullptr; float _thumbnailAspect = 0.F; std::shared_ptr _thumbnailGenerator; std::shared_ptr > > _thumbnailCache; diff --git a/lib/toucanView/TimeLayout.cpp b/lib/toucanView/TimeLayout.cpp index b7cec37..1958fad 100644 --- a/lib/toucanView/TimeLayout.cpp +++ b/lib/toucanView/TimeLayout.cpp @@ -106,6 +106,7 @@ namespace toucan void TimeLayout::setGeometry(const ftk::Box2I& value) { ITimeWidget::setGeometry(value); + const int h = value.h(); for (const auto& child : getChildren()) { if (auto timeWidget = std::dynamic_pointer_cast(child)) @@ -113,12 +114,11 @@ namespace toucan const OTIO_NS::TimeRange& timeRange = timeWidget->getTimeRange(); const int t0 = timeToPos(timeRange.start_time()); const int t1 = timeToPos(timeRange.end_time_exclusive()); - const ftk::Size2I& childSizeHint = child->getSizeHint(); child->setGeometry(ftk::Box2I( t0, value.min.y, std::max(t1 - t0, timeWidget->getMinWidth()), - childSizeHint.h)); + h)); } } } diff --git a/lib/toucanView/TimeWidgets.cpp b/lib/toucanView/TimeWidgets.cpp index fd726de..664b572 100644 --- a/lib/toucanView/TimeWidgets.cpp +++ b/lib/toucanView/TimeWidgets.cpp @@ -47,7 +47,14 @@ namespace toucan { if (_callback) { - _callback(static_cast(index)); + TimeAction action = TimeAction::FrameStart; + switch (index) + { + case 1: action = TimeAction::FramePrev; break; + case 2: action = TimeAction::FrameNext; break; + case 3: action = TimeAction::FrameEnd; break; + } + _callback(action); } }); } diff --git a/lib/toucanView/TimelineItem.cpp b/lib/toucanView/TimelineItem.cpp index 06edb57..f8eb861 100644 --- a/lib/toucanView/TimelineItem.cpp +++ b/lib/toucanView/TimelineItem.cpp @@ -6,6 +6,7 @@ #include "App.h" #include "FilesModel.h" #include "StackItem.h" +#include "ThumbnailsWidget.h" #include "WindowModel.h" #include @@ -20,7 +21,7 @@ namespace toucan IItem::_init( context, data, - nullptr, + data.file->getTimelineWrapper()->getTimeline(), data.file->getTimelineWrapper()->getTimeRange(), "toucan::TimelineItem", parent); @@ -34,30 +35,18 @@ namespace toucan _timeline = data.file->getTimeline(); _timeRange = data.file->getTimelineWrapper()->getTimeRange(); _selectionModel = data.file->getSelectionModel(); - _thumbnailGenerator = data.thumbnailGenerator; - _thumbnailCache = data.thumbnailCache; - StackItem::create( + _stackItem = StackItem::create( context, data, _timeline->tracks(), - _timeline, shared_from_this()); _selectionObserver = ftk::ListObserver::create( _selectionModel->observeSelection(), [this](const std::vector& selection) { - _select(shared_from_this(), selection); - }); - - _thumbnailsObserver = ftk::ValueObserver::create( - data.app->getWindowModel()->observeThumbnails(), - [this](bool value) - { - _thumbnails = value; - _setSizeUpdate(); - _setDrawUpdate(); + _select(_stackItem, selection); }); } @@ -108,14 +97,9 @@ namespace toucan for (const auto& child : getChildren()) { const ftk::Size2I& sizeHint = child->getSizeHint(); - int h = timeHeight; - if (_thumbnails) - { - h += _size.thumbnailHeight; - } child->setGeometry(ftk::Box2I( g.min.x, - g.min.y + h, + g.min.y + timeHeight, sizeHint.w, sizeHint.h)); } @@ -125,30 +109,6 @@ namespace toucan } } - void TimelineItem::tickEvent( - bool parentsVisible, - bool parentsEnabled, - const ftk::TickEvent& event) - { - IItem::tickEvent(parentsVisible, parentsEnabled, event); - auto i = _thumbnailRequests.begin(); - while (i != _thumbnailRequests.end()) - { - if (i->future.valid() && - i->future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) - { - const auto image = i->future.get(); - _thumbnailCache->add(getThumbnailCacheKey(nullptr, i->time, _size.thumbnailHeight), image); - _setDrawUpdate(); - i = _thumbnailRequests.erase(i); - } - else - { - ++i; - } - } - } - void TimelineItem::sizeHintEvent(const ftk::SizeHintEvent& event) { IItem::sizeHintEvent(event); @@ -163,14 +123,6 @@ namespace toucan _size.thumbnailHeight = 2 * event.style->getSizeRole(ftk::SizeRole::SwatchLarge, event.displayScale); _size.fontInfo = event.style->getFontRole(ftk::FontRole::Mono, event.displayScale); _size.fontMetrics = event.fontSystem->getMetrics(_size.fontInfo); - std::vector ids; - for (const auto& request : _thumbnailRequests) - { - ids.push_back(request.id); - } - _thumbnailRequests.clear(); - _thumbnailGenerator->cancelThumbnails(ids); - _thumbnailCache->clear(); } int childSizeHint = 0; for (const auto& child : getChildren()) @@ -180,77 +132,10 @@ namespace toucan ftk::Size2I sizeHint( _timeRange.duration().rescaled_to(1.0).value() * _scale, _size.fontMetrics.lineHeight + _size.margin * 2); - if (_thumbnails) - { - sizeHint.h += _size.thumbnailHeight; - } sizeHint.h += childSizeHint; _setSizeHint(sizeHint); } - void TimelineItem::drawEvent(const ftk::Box2I& drawRect, const ftk::DrawEvent& event) - { - IItem::drawEvent(drawRect, event); - - const ftk::Box2I& g = getGeometry(); - const int thumbnailWidth = _size.thumbnailHeight * _thumbnailGenerator->getAspect(); - const int y = g.min.y + _size.fontMetrics.lineHeight + _size.margin * 2; - if (_thumbnails) - { - for (int x = g.min.x; x < g.max.x && thumbnailWidth > 0; x += thumbnailWidth) - { - const ftk::Box2I g2(x, y, thumbnailWidth, _size.thumbnailHeight); - if (ftk::intersects(g2, drawRect)) - { - const OTIO_NS::RationalTime t = posToTime(x); - std::shared_ptr image; - if (_thumbnailCache->get(getThumbnailCacheKey(nullptr, t, _size.thumbnailHeight), image)) - { - if (image) - { - event.render->drawImage( - image, - ftk::Box2I(x, y, image->getWidth(), image->getHeight())); - } - } - else - { - const auto j = std::find_if( - _thumbnailRequests.begin(), - _thumbnailRequests.end(), - [this, t](const ThumbnailRequest& request) - { - return t == request.time && _size.thumbnailHeight == request.height; - }); - if (j == _thumbnailRequests.end()) - { - _thumbnailRequests.push_back( - _thumbnailGenerator->getThumbnail(t, _size.thumbnailHeight)); - } - } - } - } - } - - std::vector cancel; - auto i = _thumbnailRequests.begin(); - while (i != _thumbnailRequests.end()) - { - const int x = timeToPos(i->time); - const ftk::Box2I g2(x, y, thumbnailWidth, _size.thumbnailHeight); - if (!ftk::intersects(g2, drawRect)) - { - cancel.push_back(i->id); - i = _thumbnailRequests.erase(i); - } - else - { - ++i; - } - } - _thumbnailGenerator->cancelThumbnails(cancel); - } - void TimelineItem::drawOverlayEvent(const ftk::Box2I& drawRect, const ftk::DrawEvent& event) { IItem::drawOverlayEvent(drawRect, event); @@ -328,7 +213,7 @@ namespace toucan static_cast(ftk::commandKeyModifier) == event.modifiers)) { std::shared_ptr selection; - _select(shared_from_this(), event.pos, selection); + _select(_stackItem, event.pos, selection); SelectionItem item; if (selection) { @@ -578,7 +463,7 @@ namespace toucan { if (auto iitem = std::dynamic_pointer_cast(widget)) { - if (ftk::contains(iitem->getSelectionRect(), pos)) + if (ftk::contains(iitem->getGeometry(), pos)) { out = iitem; } @@ -604,7 +489,7 @@ namespace toucan selection.end(), [object](const SelectionItem& item) { - return object.value == item.object.value; + return object == item.object; }); iitem->setSelected(i != selection.end()); } diff --git a/lib/toucanView/TimelineItem.h b/lib/toucanView/TimelineItem.h index ebca580..aae655f 100644 --- a/lib/toucanView/TimelineItem.h +++ b/lib/toucanView/TimelineItem.h @@ -10,7 +10,7 @@ namespace toucan { - class SelectionModel; + class StackItem; //! Timeline item. class TimelineItem : public IItem @@ -43,12 +43,7 @@ namespace toucan void setInOutRange(const OTIO_NS::TimeRange&); void setGeometry(const ftk::Box2I&) override; - void tickEvent( - bool parentsVisible, - bool parentsEnabled, - const ftk::TickEvent&) override; void sizeHintEvent(const ftk::SizeHintEvent&) override; - void drawEvent(const ftk::Box2I&, const ftk::DrawEvent&) override; void drawOverlayEvent(const ftk::Box2I&, const ftk::DrawEvent&) override; void mouseMoveEvent(ftk::MouseMoveEvent&) override; void mousePressEvent(ftk::MouseClickEvent&) override; @@ -79,16 +74,13 @@ namespace toucan const std::shared_ptr&, const std::vector&); - OTIO_NS::SerializableObject::Retainer _timeline; + const OTIO_NS::Timeline* _timeline = nullptr; OTIO_NS::TimeRange _timeRange; OTIO_NS::RationalTime _currentTime = OTIO_NS::RationalTime(-1.0, -1.0); std::function _currentTimeCallback; OTIO_NS::TimeRange _inOutRange; std::shared_ptr _selectionModel; - bool _thumbnails = true; - std::shared_ptr _thumbnailGenerator; - std::list _thumbnailRequests; - std::shared_ptr > > _thumbnailCache; + std::shared_ptr _stackItem; struct SizeData { @@ -117,6 +109,5 @@ namespace toucan MouseData _mouse; std::shared_ptr > _selectionObserver; - std::shared_ptr > _thumbnailsObserver; }; } diff --git a/lib/toucanView/TimelineWidget.cpp b/lib/toucanView/TimelineWidget.cpp index 560a825..c2906a6 100644 --- a/lib/toucanView/TimelineWidget.cpp +++ b/lib/toucanView/TimelineWidget.cpp @@ -46,6 +46,7 @@ namespace toucan viewState.scale = _scale; viewState.frameView = _frameView->get(); _file->getPlaybackModel()->setViewState(viewState); + _thumbnailGenerator.reset(); } _file = file; if (file) @@ -61,12 +62,19 @@ namespace toucan _sizeInit = true; } + auto context = getContext(); + auto app = appWeak.lock(); + _thumbnailGenerator = std::make_shared( + context, + app->getHost(), + file->getTimelineWrapper()); + ItemData data; - data.app = appWeak.lock(); + data.app = app; data.file = file; - data.thumbnailGenerator = file->getThumbnailGenerator(); + data.thumbnailGenerator = _thumbnailGenerator; data.thumbnailCache = std::make_shared > >(); - data.thumbnailCache->setMax(1000); + data.thumbnailCache->setMax(200); _timelineItem = TimelineItem::create(getContext(), data); _timelineItem->setScale(_scale); _timelineItem->setCurrentTimeCallback( @@ -250,6 +258,7 @@ namespace toucan void TimelineWidget::mouseReleaseEvent(ftk::MouseClickEvent& event) { IWidget::mouseReleaseEvent(event); + event.accept = true; } void TimelineWidget::scrollEvent(ftk::ScrollEvent& event) diff --git a/lib/toucanView/TimelineWidget.h b/lib/toucanView/TimelineWidget.h index 5b29a3f..9567716 100644 --- a/lib/toucanView/TimelineWidget.h +++ b/lib/toucanView/TimelineWidget.h @@ -16,6 +16,7 @@ namespace toucan class App; class File; class TimelineItem; + class ThumbnailGenerator; //! Timeline widget. class TimelineWidget : public ftk::IWidget @@ -82,6 +83,7 @@ namespace toucan std::shared_ptr > _frameView; bool _sizeInit = true; std::optional _viewState; + std::shared_ptr _thumbnailGenerator; std::shared_ptr _scrollWidget; std::shared_ptr _timelineItem; diff --git a/lib/toucanView/TrackItem.cpp b/lib/toucanView/TrackItem.cpp index 74f0615..9f6b7c1 100644 --- a/lib/toucanView/TrackItem.cpp +++ b/lib/toucanView/TrackItem.cpp @@ -4,6 +4,7 @@ #include "TrackItem.h" #include "AudioClipItem.h" +#include "File.h" #include "GapItem.h" #include "VideoClipItem.h" @@ -15,26 +16,23 @@ namespace toucan void TrackItem::_init( const std::shared_ptr& context, const ItemData& data, - const OTIO_NS::SerializableObject::Retainer& track, - const OTIO_NS::SerializableObject::Retainer& timeline, + const OTIO_NS::Track* track, const std::shared_ptr& parent) { + auto timelineWrapper = data.file->getTimelineWrapper(); OTIO_NS::TimeRange timeRange = track->transformed_time_range( track->trimmed_range(), - timeline->tracks()); - if (timeline->global_start_time().has_value()) - { - timeRange = OTIO_NS::TimeRange( - timeline->global_start_time().value() + timeRange.start_time(), - timeRange.duration()); - } + timelineWrapper->getTimeline()->tracks()); + timeRange = OTIO_NS::TimeRange( + timelineWrapper->getTimeRange().start_time() + timeRange.start_time(), + timeRange.duration()); timeRange = OTIO_NS::TimeRange( timeRange.start_time().round(), timeRange.duration().round()); IItem::_init( context, data, - OTIO_NS::dynamic_retainer_cast(track), + track, timeRange, "toucan::TrackItem", parent); @@ -59,20 +57,17 @@ namespace toucan _markerLayout = TimeLayout::create(context, timeRange, _layout); for (const auto& marker : markers) { - OTIO_NS::TimeRange markerTimeRange = track->transformed_time_range( + OTIO_NS::TimeRange markerRange = track->transformed_time_range( marker->marked_range(), - timeline->tracks()); - if (timeline->global_start_time().has_value()) - { - markerTimeRange = OTIO_NS::TimeRange( - timeline->global_start_time().value() + markerTimeRange.start_time(), - markerTimeRange.duration()); - } + timelineWrapper->getTimeline()->tracks()); + markerRange = OTIO_NS::TimeRange( + timelineWrapper->getTimeRange().start_time() + markerRange.start_time(), + markerRange.duration()); auto markerItem = MarkerItem::create( context, data, marker, - markerTimeRange, + markerRange, _markerLayout); _markerItems.push_back(markerItem); } @@ -89,7 +84,6 @@ namespace toucan context, data, clip, - timeline, ftk::Color4F(.4F, .4F, .6F), _timeLayout); } @@ -99,7 +93,6 @@ namespace toucan context, data, clip, - timeline, ftk::Color4F(.4F, .6F, .4F), _timeLayout); } @@ -110,7 +103,6 @@ namespace toucan context, data, gap, - timeline, _timeLayout); } } @@ -124,12 +116,11 @@ namespace toucan std::shared_ptr TrackItem::create( const std::shared_ptr& context, const ItemData& data, - const OTIO_NS::SerializableObject::Retainer& track, - const OTIO_NS::SerializableObject::Retainer& timeline, + const OTIO_NS::Track* track, const std::shared_ptr& parent) { auto out = std::make_shared(); - out->_init(context, data, track, timeline, parent); + out->_init(context, data, track, parent); return out; } @@ -149,7 +140,6 @@ namespace toucan _layout->setGeometry(value); _geom.g2 = ftk::margin(value, -_size.border, 0, -_size.border, 0); _geom.g3 = ftk::margin(_label->getGeometry(), -_size.border, 0, -_size.border, 0); - _selectionRect = _geom.g3; } ftk::Box2I TrackItem::getChildrenClipRect() const diff --git a/lib/toucanView/TrackItem.h b/lib/toucanView/TrackItem.h index 885b516..c40df2a 100644 --- a/lib/toucanView/TrackItem.h +++ b/lib/toucanView/TrackItem.h @@ -18,8 +18,7 @@ namespace toucan void _init( const std::shared_ptr&, const ItemData&, - const OTIO_NS::SerializableObject::Retainer&, - const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::Track*, const std::shared_ptr& parent); public: @@ -29,8 +28,7 @@ namespace toucan static std::shared_ptr create( const std::shared_ptr&, const ItemData&, - const OTIO_NS::SerializableObject::Retainer&, - const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::Track*, const std::shared_ptr& parent = nullptr); void setScale(double) override; @@ -46,7 +44,7 @@ namespace toucan private: void _textUpdate(); - OTIO_NS::SerializableObject::Retainer _track; + const OTIO_NS::Track* _track = nullptr; std::string _text; ftk::Color4F _color; diff --git a/lib/toucanView/VideoClipItem.cpp b/lib/toucanView/VideoClipItem.cpp index e11afc3..c79ee5f 100644 --- a/lib/toucanView/VideoClipItem.cpp +++ b/lib/toucanView/VideoClipItem.cpp @@ -19,27 +19,24 @@ namespace toucan void VideoClipItem::_init( const std::shared_ptr& context, const ItemData& data, - const OTIO_NS::SerializableObject::Retainer& clip, - const OTIO_NS::SerializableObject::Retainer& timeline, + const OTIO_NS::Clip* clip, const ftk::Color4F& color, const std::shared_ptr& parent) { + auto timelineWrapper = data.file->getTimelineWrapper(); OTIO_NS::TimeRange timeRange = clip->transformed_time_range( clip->trimmed_range(), - timeline->tracks()); - if (timeline->global_start_time().has_value()) - { - timeRange = OTIO_NS::TimeRange( - timeline->global_start_time().value() + timeRange.start_time(), - timeRange.duration()); - } + timelineWrapper->getTimeline()->tracks()); + timeRange = OTIO_NS::TimeRange( + timelineWrapper->getTimeRange().start_time() + timeRange.start_time(), + timeRange.duration()); timeRange = OTIO_NS::TimeRange( timeRange.start_time().round(), timeRange.duration().round()); IItem::_init( context, data, - OTIO_NS::dynamic_retainer_cast(clip), + clip, timeRange, "toucan::VideoClipItem", parent); @@ -58,7 +55,8 @@ namespace toucan _thumbnailsWidget = ThumbnailsWidget::create( context, - _clip->media_reference(), + timelineWrapper, + _clip, data.thumbnailGenerator, data.thumbnailCache, timeRange, @@ -74,13 +72,10 @@ namespace toucan OTIO_NS::TimeRange markerRange( marker->marked_range().start_time() + trimmedRange.start_time(), marker->marked_range().duration()); - markerRange = clip->transformed_time_range(markerRange, timeline->tracks()); - if (timeline->global_start_time().has_value()) - { - markerRange = OTIO_NS::TimeRange( - timeline->global_start_time().value() + markerRange.start_time(), - markerRange.duration()); - } + markerRange = clip->transformed_time_range(markerRange, timelineWrapper->getTimeline()->tracks()); + markerRange = OTIO_NS::TimeRange( + timelineWrapper->getTimeRange().start_time() + markerRange.start_time(), + markerRange.duration()); auto markerItem = MarkerItem::create( context, data, @@ -107,19 +102,19 @@ namespace toucan std::shared_ptr VideoClipItem::create( const std::shared_ptr& context, const ItemData& data, - const OTIO_NS::SerializableObject::Retainer& clip, - const OTIO_NS::SerializableObject::Retainer& timeline, + const OTIO_NS::Clip* clip, const ftk::Color4F& color, const std::shared_ptr& parent) { auto out = std::make_shared(); - out->_init(context, data, clip, timeline, color, parent); + out->_init(context, data, clip, color, parent); return out; } void VideoClipItem::setScale(double value) { IItem::setScale(value); + _thumbnailsWidget->setScale(value); if (_markerLayout) { _markerLayout->setScale(value); @@ -180,8 +175,7 @@ namespace toucan "Open Media", [this, externalReference] { - auto file = _file.lock(); - const std::filesystem::path path = file->getTimelineWrapper()->getMediaPath(externalReference->target_url()); + const std::filesystem::path path = _file->getTimelineWrapper()->getMediaPath(externalReference->target_url()); auto app = _app.lock(); app->open(path); }); @@ -194,9 +188,8 @@ namespace toucan "Open Image Sequence", [this, sequenceRef] { - auto file = _file.lock(); const std::string path = getSequenceFrame( - file->getTimelineWrapper()->getMediaPath(sequenceRef->target_url_base()), + _file->getTimelineWrapper()->getMediaPath(sequenceRef->target_url_base()), sequenceRef->name_prefix(), sequenceRef->start_frame(), sequenceRef->frame_zero_padding(), diff --git a/lib/toucanView/VideoClipItem.h b/lib/toucanView/VideoClipItem.h index bd116f5..28830dc 100644 --- a/lib/toucanView/VideoClipItem.h +++ b/lib/toucanView/VideoClipItem.h @@ -21,8 +21,7 @@ namespace toucan void _init( const std::shared_ptr&, const ItemData&, - const OTIO_NS::SerializableObject::Retainer&, - const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::Clip*, const ftk::Color4F&, const std::shared_ptr& parent); @@ -33,8 +32,7 @@ namespace toucan static std::shared_ptr create( const std::shared_ptr&, const ItemData&, - const OTIO_NS::SerializableObject::Retainer&, - const OTIO_NS::SerializableObject::Retainer&, + const OTIO_NS::Clip*, const ftk::Color4F&, const std::shared_ptr& parent = nullptr); @@ -52,7 +50,7 @@ namespace toucan private: void _textUpdate(); - OTIO_NS::SerializableObject::Retainer _clip; + const OTIO_NS::Clip* _clip = nullptr; std::string _text; ftk::Color4F _color; std::string _url; diff --git a/tests/toucanRenderTest/CompTest.cpp b/tests/toucanRenderTest/CompTest.cpp index a62bec3..de0b836 100644 --- a/tests/toucanRenderTest/CompTest.cpp +++ b/tests/toucanRenderTest/CompTest.cpp @@ -11,8 +11,8 @@ namespace toucan void compTest(const std::filesystem::path& path) { std::cout << "compTest" << std::endl; - auto fg = std::make_shared(path / "Letter_A.png", nullptr); - auto bg = std::make_shared(path / "Gradient.png", nullptr); + auto fg = std::make_shared(path / "Letter_A.png"); + auto bg = std::make_shared(path / "Gradient.png"); auto comp = std::make_shared( std::vector >{ fg, bg }); comp->setPremult(true); diff --git a/tests/toucanRenderTest/ImageGraphTest.cpp b/tests/toucanRenderTest/ImageGraphTest.cpp index dbf9d71..10362a6 100644 --- a/tests/toucanRenderTest/ImageGraphTest.cpp +++ b/tests/toucanRenderTest/ImageGraphTest.cpp @@ -59,10 +59,10 @@ namespace toucan { std::cout << " " << otioFile << ": " << time.value() << "/" << timeRange.duration().value() << std::endl; - if (auto op = graph->exec(host, time)) + if (auto node = graph->exec(host, time)) { - // Execute the image operation graph. - const auto buf = op->exec(); + // Execute the image graph. + const auto buf = node->exec(); // Save the image. const std::string fileName = getSequenceFrame( diff --git a/tests/toucanRenderTest/ReadTest.cpp b/tests/toucanRenderTest/ReadTest.cpp index 6722125..66dd288 100644 --- a/tests/toucanRenderTest/ReadTest.cpp +++ b/tests/toucanRenderTest/ReadTest.cpp @@ -12,7 +12,8 @@ namespace toucan void readTest(const std::filesystem::path& path) { std::cout << "readTest" << std::endl; - auto read = std::make_shared(path / "Letter_A.png", nullptr); + auto read = std::make_shared(path / "Letter_A.png"); + read->setTime(OTIO_NS::RationalTime(0.0, 24.0)); auto buf = read->exec(); const auto& spec = buf.spec(); assert(spec.width > 0); diff --git a/tests/toucanViewTest/PlaybackModelTest.cpp b/tests/toucanViewTest/PlaybackModelTest.cpp index 7dffd3f..13238a3 100644 --- a/tests/toucanViewTest/PlaybackModelTest.cpp +++ b/tests/toucanViewTest/PlaybackModelTest.cpp @@ -100,14 +100,14 @@ namespace toucan test.model->timeAction(TimeAction::FrameNextX10, nullptr); assert(OTIO_NS::RationalTime(11.0, 24.0) == test.currentTime); test.model->timeAction(TimeAction::FrameNextX100, nullptr); - assert(OTIO_NS::RationalTime(47.0, 24.0) == test.currentTime); + assert(OTIO_NS::RationalTime(0.0, 24.0) == test.currentTime); test.model->timeAction(TimeAction::FramePrev, nullptr); - assert(OTIO_NS::RationalTime(46.0, 24.0) == test.currentTime); + assert(OTIO_NS::RationalTime(47.0, 24.0) == test.currentTime); test.model->timeAction(TimeAction::FramePrevX10, nullptr); - assert(OTIO_NS::RationalTime(36.0, 24.0) == test.currentTime); + assert(OTIO_NS::RationalTime(37.0, 24.0) == test.currentTime); test.model->timeAction(TimeAction::FramePrevX100, nullptr); - assert(OTIO_NS::RationalTime(0.0, 24.0) == test.currentTime); + assert(OTIO_NS::RationalTime(47.0, 24.0) == test.currentTime); } { Test test(context);