From eb1a22c8e55ad01b2fba10e920c0e71f821e0211 Mon Sep 17 00:00:00 2001 From: Darby Johnston Date: Wed, 27 Nov 2024 14:17:30 -0800 Subject: [PATCH] Add export tool Signed-off-by: Darby Johnston --- cmake/SuperBuild/Builddtk-deps.cmake | 2 +- cmake/SuperBuild/Builddtk.cmake | 2 +- lib/toucan/Util.cpp | 7 +- lib/toucan/Util.h | 2 +- lib/toucanView/CMakeLists.txt | 2 - lib/toucanView/Export.cpp | 8 -- lib/toucanView/Export.h | 9 -- lib/toucanView/ExportTool.cpp | 152 ++++++++++++++++++++++++++- lib/toucanView/ExportTool.h | 34 +++++- lib/toucanView/JSONTool.cpp | 4 + lib/toucanView/JSONTool.h | 1 + 11 files changed, 191 insertions(+), 32 deletions(-) delete mode 100644 lib/toucanView/Export.cpp delete mode 100644 lib/toucanView/Export.h diff --git a/cmake/SuperBuild/Builddtk-deps.cmake b/cmake/SuperBuild/Builddtk-deps.cmake index ab6f4f2..865df89 100644 --- a/cmake/SuperBuild/Builddtk-deps.cmake +++ b/cmake/SuperBuild/Builddtk-deps.cmake @@ -1,7 +1,7 @@ include(ExternalProject) set(dtk_GIT_REPOSITORY "https://github.com/darbyjohnston/dtk.git") -set(dtk_GIT_TAG "957d7bb8e6f7e12fc291e8c31a4180ee4a9dfd8f") +set(dtk_GIT_TAG "fe317ed208c6befeeb832fd9e8c6cce18575f9cb") set(dtk-deps_ARGS ${toucan_EXTERNAL_PROJECT_ARGS} diff --git a/cmake/SuperBuild/Builddtk.cmake b/cmake/SuperBuild/Builddtk.cmake index ae7b410..556a538 100644 --- a/cmake/SuperBuild/Builddtk.cmake +++ b/cmake/SuperBuild/Builddtk.cmake @@ -1,7 +1,7 @@ include(ExternalProject) set(dtk_GIT_REPOSITORY "https://github.com/darbyjohnston/dtk.git") -set(dtk_GIT_TAG "957d7bb8e6f7e12fc291e8c31a4180ee4a9dfd8f") +set(dtk_GIT_TAG "fe317ed208c6befeeb832fd9e8c6cce18575f9cb") set(dtk_DEPS dtk-deps) set(dtk_ARGS diff --git a/lib/toucan/Util.cpp b/lib/toucan/Util.cpp index 6837979..5afd9e7 100644 --- a/lib/toucan/Util.cpp +++ b/lib/toucan/Util.cpp @@ -118,18 +118,17 @@ namespace toucan } std::string getSequenceFrame( - const std::string& base, + const std::filesystem::path& path, const std::string& namePrefix, int frame, int padding, const std::string& nameSuffix) { std::stringstream ss; - ss << base << - namePrefix << + ss << namePrefix << std::setw(padding) << std::setfill('0') << frame << nameSuffix; - return ss.str(); + return (path / ss.str()).string(); } namespace diff --git a/lib/toucan/Util.h b/lib/toucan/Util.h index a6ad4cf..b14ee03 100644 --- a/lib/toucan/Util.h +++ b/lib/toucan/Util.h @@ -36,7 +36,7 @@ namespace toucan //! Get an image sequence file name. std::string getSequenceFrame( - const std::string& base, + const std::filesystem::path&, const std::string& namePrefix, int frame, int padding, diff --git a/lib/toucanView/CMakeLists.txt b/lib/toucanView/CMakeLists.txt index 494cfc6..e8f49f8 100644 --- a/lib/toucanView/CMakeLists.txt +++ b/lib/toucanView/CMakeLists.txt @@ -4,7 +4,6 @@ set(HEADERS DocumentTab.h Document.h DocumentsModel.h - Export.h ExportTool.h GapItem.h GraphTool.h @@ -35,7 +34,6 @@ set(SOURCE DocumentTab.cpp Document.cpp DocumentsModel.cpp - Export.cpp ExportTool.cpp GapItem.cpp GraphTool.cpp diff --git a/lib/toucanView/Export.cpp b/lib/toucanView/Export.cpp deleted file mode 100644 index f5720b7..0000000 --- a/lib/toucanView/Export.cpp +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Contributors to the toucan project. - -#include "Export.h" - -namespace toucan -{ -} diff --git a/lib/toucanView/Export.h b/lib/toucanView/Export.h deleted file mode 100644 index bc26315..0000000 --- a/lib/toucanView/Export.h +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Contributors to the toucan project. - -#pragma once - -namespace toucan -{ -} - diff --git a/lib/toucanView/ExportTool.cpp b/lib/toucanView/ExportTool.cpp index b99ed85..eca44bb 100644 --- a/lib/toucanView/ExportTool.cpp +++ b/lib/toucanView/ExportTool.cpp @@ -3,15 +3,103 @@ #include "ExportTool.h" +#include "App.h" +#include "DocumentsModel.h" +#include "PlaybackModel.h" + +#include + +#include +#include +#include + namespace toucan { void ExportWidget::_init( const std::shared_ptr& context, + const std::shared_ptr& app, const std::shared_ptr& parent) { IWidget::_init(context, "toucan::ExportWidget", parent); + _host = app->getHost(); + _formats = + { + ".tiff", + ".png" + }; + _layout = dtk::VerticalLayout::create(context, shared_from_this()); + _layout->setMarginRole(dtk::SizeRole::MarginSmall); + _layout->setSpacingRole(dtk::SizeRole::SpacingSmall); + + auto vLayout = dtk::VerticalLayout::create(context, _layout); + vLayout->setSpacingRole(dtk::SizeRole::SpacingSmall); + auto label = dtk::Label::create(context, "Output directory:", vLayout); + _outputPathEdit = dtk::FileEdit::create(context, vLayout); + + auto gridLayout = dtk::GridLayout::create(context, _layout); + gridLayout->setSpacingRole(dtk::SizeRole::SpacingSmall); + + label = dtk::Label::create(context, "Base name:", gridLayout); + gridLayout->setGridPos(label, 0, 0); + _baseNameEdit = dtk::LineEdit::create(context, gridLayout); + _baseNameEdit->setText("render."); + gridLayout->setGridPos(_baseNameEdit, 0, 1); + + label = dtk::Label::create(context, "Number padding:", gridLayout); + gridLayout->setGridPos(label, 1, 0); + _paddingEdit = dtk::IntEdit::create(context, gridLayout); + _paddingEdit->setRange(dtk::RangeI(0, 9)); + gridLayout->setGridPos(_paddingEdit, 1, 1); + + label = dtk::Label::create(context, "File format:", gridLayout); + gridLayout->setGridPos(label, 2, 0); + _formatComboBox = dtk::ComboBox::create(context, _formats, gridLayout); + gridLayout->setGridPos(_formatComboBox, 2, 1); + + auto divider = dtk::Divider::create(context, dtk::Orientation::Vertical, _layout); + + auto exportSequenceButton = dtk::PushButton::create(context, "Export Sequence", _layout); + exportSequenceButton->setClickedCallback( + [this] + { + _timeRange = _document->getPlaybackModel()->getInOutRange(); + _export(); + }); + + auto exportCurrentButton = dtk::PushButton::create(context, "Export Current Frame", _layout); + exportCurrentButton->setClickedCallback( + [this] + { + _timeRange = OTIO_NS::TimeRange( + _document->getPlaybackModel()->getCurrentTime(), + OTIO_NS::RationalTime(1.0, _document->getPlaybackModel()->getTimeRange().duration().rate())); + _export(); + }); + + _timer = dtk::Timer::create(context); + _timer->setRepeating(true); + + _documentObserver = dtk::ValueObserver >::create( + app->getDocumentsModel()->observeCurrent(), + [this](const std::shared_ptr& document) + { + _document = document; + if (_document) + { + _graph = std::make_shared( + _document->getPath(), + _document->getTimelineWrapper()); + } + else + { + _timeRange = OTIO_NS::TimeRange(); + _time = OTIO_NS::RationalTime(); + _graph.reset(); + } + setEnabled(_document.get()); + }); } ExportWidget::~ExportWidget() @@ -19,10 +107,11 @@ namespace toucan std::shared_ptr ExportWidget::create( const std::shared_ptr& context, + const std::shared_ptr& app, const std::shared_ptr& parent) { auto out = std::shared_ptr(new ExportWidget); - out->_init(context, parent); + out->_init(context, app, parent); return out; } @@ -38,6 +127,56 @@ namespace toucan _setSizeHint(_layout->getSizeHint()); } + void ExportWidget::_export() + { + _time = _timeRange.start_time(); + + _dialog = dtk::ProgressDialog::create( + getContext(), + "Export", + "Exporting:", + getWindow()); + _dialog->setCloseCallback( + [this] + { + _timer->stop(); + _dialog.reset(); + }); + _dialog->show(); + + _timer->start( + std::chrono::microseconds(0), + [this] + { + if (auto node = _graph->exec(_host, _time)) + { + const auto buf = node->exec(); + const std::string fileName = getSequenceFrame( + _outputPathEdit->getPath().string(), + _baseNameEdit->getText(), + _time.to_frames(), + _paddingEdit->getValue(), + _formats[_formatComboBox->getCurrentIndex()]); + buf.write(fileName); + } + + const OTIO_NS::RationalTime end = _timeRange.end_time_inclusive(); + if (_time < end) + { + _time += OTIO_NS::RationalTime(1.0, _timeRange.duration().rate()); + const OTIO_NS::RationalTime duration = _timeRange.duration(); + const double v = duration.value() > 0.0 ? + (_time - _timeRange.start_time()).value() / static_cast(duration.value()) : + 0.0; + _dialog->setValue(v); + } + else + { + _dialog->close(); + } + }); + } + void ExportTool::_init( const std::shared_ptr& context, const std::shared_ptr& app, @@ -45,8 +184,11 @@ namespace toucan { IToolWidget::_init(context, app, "toucan::ExportTool", "Export", parent); - _layout = dtk::VerticalLayout::create(context, shared_from_this()); - _layout->setSpacingRole(dtk::SizeRole::None); + _scrollWidget = dtk::ScrollWidget::create(context, dtk::ScrollType::Both, shared_from_this()); + _scrollWidget->setBorder(false); + + _widget = ExportWidget::create(context, app); + _scrollWidget->setWidget(_widget); } ExportTool::~ExportTool() @@ -65,12 +207,12 @@ namespace toucan void ExportTool::setGeometry(const dtk::Box2I& value) { IToolWidget::setGeometry(value); - _layout->setGeometry(value); + _scrollWidget->setGeometry(value); } void ExportTool::sizeHintEvent(const dtk::SizeHintEvent& event) { IToolWidget::sizeHintEvent(event); - _setSizeHint(_layout->getSizeHint()); + _setSizeHint(_scrollWidget->getSizeHint()); } } diff --git a/lib/toucanView/ExportTool.h b/lib/toucanView/ExportTool.h index 7e096ff..4aabf5e 100644 --- a/lib/toucanView/ExportTool.h +++ b/lib/toucanView/ExportTool.h @@ -5,15 +5,29 @@ #include "IToolWidget.h" +#include + +#include +#include +#include +#include +#include +#include +#include #include +#include +#include namespace toucan { + class Document; + class ExportWidget : public dtk::IWidget { protected: void _init( const std::shared_ptr&, + const std::shared_ptr&, const std::shared_ptr& parent); public: @@ -21,13 +35,31 @@ namespace toucan static std::shared_ptr create( const std::shared_ptr&, + const std::shared_ptr&, const std::shared_ptr& parent = nullptr); void setGeometry(const dtk::Box2I&) override; void sizeHintEvent(const dtk::SizeHintEvent&) override; private: + void _export(); + + std::shared_ptr _host; + std::shared_ptr _document; + OTIO_NS::TimeRange _timeRange; + OTIO_NS::RationalTime _time; + std::shared_ptr _graph; + std::vector _formats; + std::shared_ptr _layout; + std::shared_ptr _outputPathEdit; + std::shared_ptr _baseNameEdit; + std::shared_ptr _paddingEdit; + std::shared_ptr _formatComboBox; + std::shared_ptr _dialog; + std::shared_ptr _timer; + + std::shared_ptr > > _documentObserver; }; class ExportTool : public IToolWidget @@ -50,7 +82,7 @@ namespace toucan void sizeHintEvent(const dtk::SizeHintEvent&) override; private: - std::shared_ptr _layout; + std::shared_ptr _scrollWidget; std::shared_ptr _widget; }; } diff --git a/lib/toucanView/JSONTool.cpp b/lib/toucanView/JSONTool.cpp index 8600ae8..5bb9cde 100644 --- a/lib/toucanView/JSONTool.cpp +++ b/lib/toucanView/JSONTool.cpp @@ -111,6 +111,9 @@ namespace toucan _scrollLayout->setSpacingRole(dtk::SizeRole::None); _scrollWidget->setWidget(_scrollLayout); + _nothingSelectedLabel = dtk::Label::create(context, "Nothing selected", _scrollLayout); + _nothingSelectedLabel->setMarginRole(dtk::SizeRole::MarginSmall); + dtk::Divider::create(context, dtk::Orientation::Vertical, _layout); _bottomLayout = dtk::HorizontalLayout::create(context, _layout); @@ -181,6 +184,7 @@ namespace toucan widget->setFilter(_searchBox->getText()); _widgets.push_back(widget); } + _nothingSelectedLabel->setVisible(selection.empty()); }); } else diff --git a/lib/toucanView/JSONTool.h b/lib/toucanView/JSONTool.h index dfc192a..e6a8b5f 100644 --- a/lib/toucanView/JSONTool.h +++ b/lib/toucanView/JSONTool.h @@ -77,6 +77,7 @@ namespace toucan std::shared_ptr _scrollWidget; std::shared_ptr _scrollLayout; std::vector > _widgets; + std::shared_ptr _nothingSelectedLabel; std::shared_ptr _bottomLayout; std::shared_ptr > > _documentObserver;