From f924d27e500a23a1038c24a824a174b64394df05 Mon Sep 17 00:00:00 2001 From: Darby Johnston Date: Tue, 3 Dec 2024 15:31:00 -0800 Subject: [PATCH 01/10] Refactoring Signed-off-by: Darby Johnston --- cmake/SuperBuild/Builddtk-deps.cmake | 2 +- cmake/SuperBuild/Builddtk.cmake | 2 +- lib/toucanView/App.cpp | 2 +- lib/toucanView/App.h | 9 ++ lib/toucanView/GapItem.cpp | 2 +- lib/toucanView/JSONTool.cpp | 1 + lib/toucanView/MainWindow.cpp | 53 ++++++-- lib/toucanView/MainWindow.h | 2 +- lib/toucanView/MenuBar.cpp | 194 +++++++++++++-------------- lib/toucanView/MenuBar.h | 12 +- lib/toucanView/PlaybackBar.cpp | 2 + lib/toucanView/PlaybackBar.h | 2 + lib/toucanView/TimeUnitsModel.cpp | 44 +++--- lib/toucanView/TimeUnitsModel.h | 6 +- lib/toucanView/TimeWidgets.cpp | 2 + lib/toucanView/TimelineItem.cpp | 2 +- lib/toucanView/ToolBar.cpp | 46 +++---- lib/toucanView/WindowModel.cpp | 94 ++++++++++--- lib/toucanView/WindowModel.h | 39 ++++-- 19 files changed, 311 insertions(+), 205 deletions(-) diff --git a/cmake/SuperBuild/Builddtk-deps.cmake b/cmake/SuperBuild/Builddtk-deps.cmake index ac0c09e..eebe369 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 "b9515ed232f27dbba1398a81d25dad0d2caf231a") +set(dtk_GIT_TAG "0bd51326554b033502b4b912bc3f24f2cc87ac16") set(dtk-deps_ARGS ${toucan_EXTERNAL_PROJECT_ARGS} diff --git a/cmake/SuperBuild/Builddtk.cmake b/cmake/SuperBuild/Builddtk.cmake index 8e66eaa..47eec07 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 "b9515ed232f27dbba1398a81d25dad0d2caf231a") +set(dtk_GIT_TAG "0bd51326554b033502b4b912bc3f24f2cc87ac16") set(dtk_DEPS dtk-deps) set(dtk_ARGS diff --git a/lib/toucanView/App.cpp b/lib/toucanView/App.cpp index 05ffae9..b37f198 100644 --- a/lib/toucanView/App.cpp +++ b/lib/toucanView/App.cpp @@ -60,7 +60,7 @@ namespace toucan _filesModel = std::make_shared(context, _host); - _windowModel = std::make_shared(); + _windowModel = std::make_shared(context); _window = MainWindow::create( context, diff --git a/lib/toucanView/App.h b/lib/toucanView/App.h index 61651cb..a73cd84 100644 --- a/lib/toucanView/App.h +++ b/lib/toucanView/App.h @@ -14,6 +14,7 @@ namespace toucan class TimeUnitsModel; class WindowModel; + //! Application. class App : public dtk::App { protected: @@ -24,13 +25,21 @@ namespace toucan public: virtual ~App(); + //! Create a new application. static std::shared_ptr create( const std::shared_ptr&, std::vector&); + //! Get the time units model. const std::shared_ptr& getTimeUnitsModel() const; + + //! Get the image effect host. const std::shared_ptr& getHost() const; + + //! Get the files model. const std::shared_ptr& getFilesModel() const; + + //! Get the window model. const std::shared_ptr& getWindowModel() const; private: diff --git a/lib/toucanView/GapItem.cpp b/lib/toucanView/GapItem.cpp index 6f98057..6608ddf 100644 --- a/lib/toucanView/GapItem.cpp +++ b/lib/toucanView/GapItem.cpp @@ -59,7 +59,7 @@ namespace toucan _size.displayScale = event.displayScale; _size.margin = event.style->getSizeRole(dtk::SizeRole::MarginInside, event.displayScale); _size.border = event.style->getSizeRole(dtk::SizeRole::Border, event.displayScale); - _size.fontInfo = event.style->getFontRole(dtk::FontRole::Label , event.displayScale); + _size.fontInfo = event.style->getFontRole(dtk::FontRole::Label, event.displayScale); _size.fontMetrics = event.fontSystem->getMetrics(_size.fontInfo); _size.textSize = event.fontSystem->getSize(_text, _size.fontInfo); _draw.glyphs.clear(); diff --git a/lib/toucanView/JSONTool.cpp b/lib/toucanView/JSONTool.cpp index e87568c..60bbf24 100644 --- a/lib/toucanView/JSONTool.cpp +++ b/lib/toucanView/JSONTool.cpp @@ -26,6 +26,7 @@ namespace toucan _text = dtk::split(item->to_json_string(), { '\n' }); _label = dtk::Label::create(context); + _label->setFontRole(dtk::FontRole::Mono); _label->setMarginRole(dtk::SizeRole::MarginSmall); _bellows = dtk::Bellows::create(context, item->name(), shared_from_this()); diff --git a/lib/toucanView/MainWindow.cpp b/lib/toucanView/MainWindow.cpp index 3587ab2..360666d 100644 --- a/lib/toucanView/MainWindow.cpp +++ b/lib/toucanView/MainWindow.cpp @@ -17,8 +17,11 @@ #include #include +#include #include +#include + namespace toucan { void MainWindow::_init( @@ -31,6 +34,21 @@ namespace toucan _app = app; + float displayScale = 0.F; + try + { + auto settings = context->getSystem(); + const auto json = std::any_cast(settings->get("MainWindow")); + auto i = json.find("DisplayScale"); + if (i != json.end() && i->is_number()) + { + displayScale = i->get(); + } + } + catch (const std::exception&) + {} + setDisplayScale(displayScale); + _layout = dtk::VerticalLayout::create(context, shared_from_this()); _layout->setSpacingRole(dtk::SizeRole::None); @@ -78,9 +96,9 @@ namespace toucan _timelineWidget = TimelineWidget::create(context, app, _bottomLayout); _timelineWidget->setVStretch(dtk::Stretch::Expanding); - _infoDivider = dtk::Divider::create(context, dtk::Orientation::Vertical, _bottomLayout); + _infoDivider = dtk::Divider::create(context, dtk::Orientation::Vertical, _layout); - _infoBar = InfoBar::create(context, app, _bottomLayout); + _infoBar = InfoBar::create(context, app, _layout); std::weak_ptr appWeak(app); _tabWidget->setCallback( @@ -148,25 +166,26 @@ namespace toucan _tabWidget->setCurrentTab(index); }); - _controlsObserver = dtk::MapObserver::create( - app->getWindowModel()->observeControls(), - [this](const std::map& value) + _componentsObserver = dtk::MapObserver::create( + app->getWindowModel()->observeComponents(), + [this](const std::map& value) { - auto i = value.find(WindowControl::ToolBar); + auto i = value.find(WindowComponent::ToolBar); _toolBar->setVisible(i->second); _toolBarDivider->setVisible(i->second); - i = value.find(WindowControl::PlaybackBar); + i = value.find(WindowComponent::PlaybackBar); + auto j = value.find(WindowComponent::TimelineWidget); + _bottomLayout->setVisible(i->second || j->second); _playbackBar->setVisible(i->second); - auto j = value.find(WindowControl::TimelineWidget); _timelineWidget->setVisible(j->second); - auto k = value.find(WindowControl::InfoBar); - _infoBar->setVisible(k->second); - _bottomLayout->setVisible(i->second || j->second || k->second); _timelineDivider->setVisible(i->second && j->second); - _infoDivider->setVisible((i->second || j->second) && k->second); - i = value.find(WindowControl::Tools); + auto k = value.find(WindowComponent::InfoBar); + _infoBar->setVisible(k->second); + _infoDivider->setVisible(k->second); + + i = value.find(WindowComponent::Tools); _toolWidget->setVisible(i->second); }); @@ -179,7 +198,13 @@ namespace toucan } MainWindow::~MainWindow() - {} + { + nlohmann::json json; + json["DisplayScale"] = getDisplayScale(); + auto context = getContext(); + auto settings = context->getSystem(); + settings->set("MainWindow", json); + } std::shared_ptr MainWindow::create( const std::shared_ptr& context, diff --git a/lib/toucanView/MainWindow.h b/lib/toucanView/MainWindow.h index 1771925..acc252e 100644 --- a/lib/toucanView/MainWindow.h +++ b/lib/toucanView/MainWindow.h @@ -75,7 +75,7 @@ namespace toucan std::shared_ptr > _addObserver; std::shared_ptr > _removeObserver; std::shared_ptr > _fileObserver; - std::shared_ptr > _controlsObserver; + std::shared_ptr > _componentsObserver; std::shared_ptr > _tooltipsObserver; }; } diff --git a/lib/toucanView/MenuBar.cpp b/lib/toucanView/MenuBar.cpp index 7f3f615..d8bed6a 100644 --- a/lib/toucanView/MenuBar.cpp +++ b/lib/toucanView/MenuBar.cpp @@ -35,8 +35,8 @@ namespace toucan _selectMenuInit(context, app); _timeMenuInit(context, app); _playbackMenuInit(context, app); - _windowMenuInit(context, app, window); _viewMenuInit(context, app); + _windowMenuInit(context, app, window); _filesObserver = dtk::ListObserver >::create( _filesModel->observeFiles(), @@ -67,8 +67,8 @@ namespace toucan _selectMenuUpdate(); _timeMenuUpdate(); _playbackMenuUpdate(); - _windowMenuUpdate(); _viewMenuUpdate(); + _windowMenuUpdate(); }); _fileIndexObserver = dtk::ValueObserver::create( @@ -570,6 +570,76 @@ namespace toucan _menus["Playback"]->addItem(_actions["Playback/Toggle"]); } + void MenuBar::_viewMenuInit( + const std::shared_ptr& context, + const std::shared_ptr& app) + { + _menus["View"] = dtk::Menu::create(context); + addMenu("View", _menus["View"]); + + _actions["View/ZoomIn"] = std::make_shared( + "Zoom In", + "ViewZoomIn", + dtk::Key::Equal, + 0, + [this] + { + if (_file) + { + _file->getViewModel()->zoomIn(); + } + }); + _actions["View/ZoomIn"]->toolTip = "View zoom in"; + _menus["View"]->addItem(_actions["View/ZoomIn"]); + + _actions["View/ZoomOut"] = std::make_shared( + "Zoom Out", + "ViewZoomOut", + dtk::Key::Minus, + 0, + [this] + { + if (_file) + { + _file->getViewModel()->zoomOut(); + } + }); + _actions["View/ZoomOut"]->toolTip = "View zoom out"; + _menus["View"]->addItem(_actions["View/ZoomOut"]); + + _actions["View/ZoomReset"] = std::make_shared( + "Zoom Reset", + "ViewZoomReset", + dtk::Key::_0, + 0, + [this] + { + if (_file) + { + _file->getViewModel()->zoomReset(); + } + }); + _actions["View/ZoomReset"]->toolTip = "Reset the view zoom"; + _menus["View"]->addItem(_actions["View/ZoomReset"]); + + _menus["View"]->addDivider(); + + _actions["View/Frame"] = std::make_shared( + "Frame View", + "ViewFrame", + dtk::Key::Backspace, + 0, + [this](bool value) + { + if (_file) + { + _file->getViewModel()->setFrame(value); + } + }); + _actions["View/Frame"]->toolTip = "Frame the view"; + _menus["View"]->addItem(_actions["View/Frame"]); + } + void MenuBar::_windowMenuInit( const std::shared_ptr& context, const std::shared_ptr& app, @@ -596,31 +666,31 @@ namespace toucan _menus["Window"]->addDivider(); - struct Control + struct Component { - WindowControl control; + WindowComponent component; std::string action; std::string text; }; - const std::vector controls = + const std::vector components = { - { WindowControl::ToolBar, "ToolBar", "Tool Bar" }, - { WindowControl::PlaybackBar, "PlaybackBar", "Playback Bar" }, - { WindowControl::TimelineWidget, "TimelineWidget", "Timeline Widget" }, - { WindowControl::InfoBar, "InfoBar", "Information Bar" }, - { WindowControl::Tools, "Tools", "Tools" } + { WindowComponent::ToolBar, "ToolBar", "Tool Bar" }, + { WindowComponent::PlaybackBar, "PlaybackBar", "Playback Bar" }, + { WindowComponent::TimelineWidget, "TimelineWidget", "Timeline Widget" }, + { WindowComponent::InfoBar, "InfoBar", "Information Bar" }, + { WindowComponent::Tools, "Tools", "Tools" } }; std::weak_ptr appWeak(app); - for (const auto& control : controls) + for (const auto& component : components) { - const std::string actionName = dtk::Format("Window/{0}").arg(control.action); + const std::string actionName = dtk::Format("Window/{0}").arg(component.action); _actions[actionName] = std::make_shared( - control.text, - [appWeak, control](bool value) + component.text, + [appWeak, component](bool value) { if (auto app = appWeak.lock()) { - app->getWindowModel()->setControl(control.control, value); + app->getWindowModel()->setComponent(component.component, value); } }); _menus["Window"]->addItem(_actions[actionName]); @@ -727,19 +797,19 @@ namespace toucan _menus["Window"]->setItemChecked(_actions["Window/FullScreen"], value); }); - _controlsObserver = dtk::MapObserver::create( - app->getWindowModel()->observeControls(), - [this](const std::map& value) + _componentsObserver = dtk::MapObserver::create( + app->getWindowModel()->observeComponents(), + [this](const std::map& value) { - auto i = value.find(WindowControl::ToolBar); + auto i = value.find(WindowComponent::ToolBar); _menus["Window"]->setItemChecked(_actions["Window/ToolBar"], i->second); - i = value.find(WindowControl::PlaybackBar); + i = value.find(WindowComponent::PlaybackBar); _menus["Window"]->setItemChecked(_actions["Window/PlaybackBar"], i->second); - i = value.find(WindowControl::TimelineWidget); + i = value.find(WindowComponent::TimelineWidget); _menus["Window"]->setItemChecked(_actions["Window/TimelineWidget"], i->second); - i = value.find(WindowControl::InfoBar); + i = value.find(WindowComponent::InfoBar); _menus["Window"]->setItemChecked(_actions["Window/InfoBar"], i->second); - i = value.find(WindowControl::Tools); + i = value.find(WindowComponent::Tools); _menus["Window"]->setItemChecked(_actions["Window/Tools"], i->second); }); @@ -761,76 +831,6 @@ namespace toucan }); } - void MenuBar::_viewMenuInit( - const std::shared_ptr& context, - const std::shared_ptr& app) - { - _menus["View"] = dtk::Menu::create(context); - addMenu("View", _menus["View"]); - - _actions["View/ZoomIn"] = std::make_shared( - "Zoom In", - "ViewZoomIn", - dtk::Key::Equal, - 0, - [this] - { - if (_file) - { - _file->getViewModel()->zoomIn(); - } - }); - _actions["View/ZoomIn"]->toolTip = "View zoom in"; - _menus["View"]->addItem(_actions["View/ZoomIn"]); - - _actions["View/ZoomOut"] = std::make_shared( - "Zoom Out", - "ViewZoomOut", - dtk::Key::Minus, - 0, - [this] - { - if (_file) - { - _file->getViewModel()->zoomOut(); - } - }); - _actions["View/ZoomOut"]->toolTip = "View zoom out"; - _menus["View"]->addItem(_actions["View/ZoomOut"]); - - _actions["View/ZoomReset"] = std::make_shared( - "Zoom Reset", - "ViewZoomReset", - dtk::Key::_0, - 0, - [this] - { - if (_file) - { - _file->getViewModel()->zoomReset(); - } - }); - _actions["View/ZoomReset"]->toolTip = "Reset the view zoom"; - _menus["View"]->addItem(_actions["View/ZoomReset"]); - - _menus["View"]->addDivider(); - - _actions["View/Frame"] = std::make_shared( - "Frame View", - "ViewFrame", - dtk::Key::Backspace, - 0, - [this](bool value) - { - if (_file) - { - _file->getViewModel()->setFrame(value); - } - }); - _actions["View/Frame"]->toolTip = "Frame the view"; - _menus["View"]->addItem(_actions["View/Frame"]); - } - void MenuBar::_fileMenuUpdate() { const bool file = _file.get(); @@ -887,10 +887,6 @@ namespace toucan _menus["Playback"]->setItemEnabled(_actions["Playback/Toggle"], file); } - void MenuBar::_windowMenuUpdate() - { - } - void MenuBar::_viewMenuUpdate() { const bool file = _file.get(); @@ -913,4 +909,8 @@ namespace toucan _menus["View"]->setItemEnabled(_actions["View/ZoomReset"], file); _menus["View"]->setItemEnabled(_actions["View/Frame"], file); } + + void MenuBar::_windowMenuUpdate() + { + } } diff --git a/lib/toucanView/MenuBar.h b/lib/toucanView/MenuBar.h index b71b5a5..74783f7 100644 --- a/lib/toucanView/MenuBar.h +++ b/lib/toucanView/MenuBar.h @@ -52,20 +52,20 @@ namespace toucan void _playbackMenuInit( const std::shared_ptr&, const std::shared_ptr&); + void _viewMenuInit( + const std::shared_ptr&, + const std::shared_ptr&); void _windowMenuInit( const std::shared_ptr&, const std::shared_ptr&, const std::shared_ptr&); - void _viewMenuInit( - const std::shared_ptr&, - const std::shared_ptr&); void _fileMenuUpdate(); void _selectMenuUpdate(); void _timeMenuUpdate(); void _playbackMenuUpdate(); - void _windowMenuUpdate(); void _viewMenuUpdate(); + void _windowMenuUpdate(); std::weak_ptr _app; std::shared_ptr _filesModel; @@ -81,11 +81,11 @@ namespace toucan std::shared_ptr > _fileIndexObserver; std::shared_ptr > _recentFilesObserver; std::shared_ptr > _playbackObserver; + std::shared_ptr > _frameViewObserver; std::shared_ptr > _fullScreenObserver; - std::shared_ptr > _controlsObserver; + std::shared_ptr > _componentsObserver; std::shared_ptr > _displayScaleObserver; std::shared_ptr > _tooltipsObserver; - std::shared_ptr > _frameViewObserver; }; } diff --git a/lib/toucanView/PlaybackBar.cpp b/lib/toucanView/PlaybackBar.cpp index 34d9701..2c70afc 100644 --- a/lib/toucanView/PlaybackBar.cpp +++ b/lib/toucanView/PlaybackBar.cpp @@ -18,6 +18,7 @@ namespace toucan _layout = dtk::HorizontalLayout::create(context, shared_from_this()); _layout->setMarginRole(dtk::SizeRole::MarginInside); _layout->setSpacingRole(dtk::SizeRole::SpacingSmall); + _layout->setVAlign(dtk::VAlign::Center); _playbackButtons = PlaybackButtons::create(context, _layout); @@ -33,6 +34,7 @@ namespace toucan context, { "Timecode", "Frames", "Seconds" }, _layout); + _timeUnitsComboBox->setTooltip("Set the time units"); _frameButtons->setCallback( [this](TimeAction value) diff --git a/lib/toucanView/PlaybackBar.h b/lib/toucanView/PlaybackBar.h index 25694a5..409bd6b 100644 --- a/lib/toucanView/PlaybackBar.h +++ b/lib/toucanView/PlaybackBar.h @@ -15,6 +15,7 @@ namespace toucan class App; class File; + //! Playback tool bar. class PlaybackBar : public dtk::IWidget { protected: @@ -26,6 +27,7 @@ namespace toucan public: virtual ~PlaybackBar(); + //! Create a new widget. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, diff --git a/lib/toucanView/TimeUnitsModel.cpp b/lib/toucanView/TimeUnitsModel.cpp index e9d96d4..63e77c8 100644 --- a/lib/toucanView/TimeUnitsModel.cpp +++ b/lib/toucanView/TimeUnitsModel.cpp @@ -4,6 +4,8 @@ #include "TimeUnitsModel.h" #include +#include +#include #include @@ -12,44 +14,26 @@ namespace toucan { - namespace - { - const std::array timeUnits = - { - "Timecode", - "Frames", - "Seconds" - }; - } - - std::string toString(TimeUnits value) - { - return timeUnits[static_cast(value)]; - } - - TimeUnits fromString(const std::string& value) - { - const auto i = std::find(timeUnits.begin(), timeUnits.end(), value); - return i != timeUnits.end() ? - static_cast(i - timeUnits.begin()) : - TimeUnits::Timecode; - } + DTK_ENUM_IMPL( + TimeUnits, + "Timecode", + "Frames", + "Seconds"); TimeUnitsModel::TimeUnitsModel(const std::shared_ptr& context) { _context = context; + TimeUnits value = TimeUnits::Timecode; try { auto settings = context->getSystem(); const auto json = std::any_cast(settings->get("TimeUnits")); - if (json.is_object()) + auto i = json.find("Units"); + if (i != json.end() && i->is_string()) { - auto i = json.find("Units"); - if (i != json.end()) - { - value = toucan::fromString(i->get()); - } + std::stringstream ss(i->get()); + ss >> value; } } catch (const std::exception&) @@ -61,7 +45,9 @@ namespace toucan TimeUnitsModel::~TimeUnitsModel() { nlohmann::json json; - json["Units"] = toucan::toString(_timeUnits->get()); + std::stringstream ss; + ss << _timeUnits->get(); + json["Units"] = ss.str(); auto context = _context.lock(); auto settings = context->getSystem(); settings->set("TimeUnits", json); diff --git a/lib/toucanView/TimeUnitsModel.h b/lib/toucanView/TimeUnitsModel.h index 5303e06..e1d27d4 100644 --- a/lib/toucanView/TimeUnitsModel.h +++ b/lib/toucanView/TimeUnitsModel.h @@ -15,8 +15,12 @@ namespace toucan { Timecode, Frames, - Seconds + Seconds, + + Count, + First = Timecode }; + DTK_ENUM(TimeUnits); //! Convert to a string. std::string toString(TimeUnits); diff --git a/lib/toucanView/TimeWidgets.cpp b/lib/toucanView/TimeWidgets.cpp index 10800b8..1534245 100644 --- a/lib/toucanView/TimeWidgets.cpp +++ b/lib/toucanView/TimeWidgets.cpp @@ -177,6 +177,7 @@ namespace toucan _layout->setSpacingRole(dtk::SizeRole::SpacingTool); _lineEdit = dtk::LineEdit::create(context, _layout); + _lineEdit->setFontRole(dtk::FontRole::Mono); _lineEdit->setFormat("00:00:00:00"); _incButtons = dtk::IncButtons::create(context, _layout); @@ -333,6 +334,7 @@ namespace toucan _timeUnitsModel = timeUnitsModel; _label = dtk::Label::create(context, shared_from_this()); + _label->setFontRole(dtk::FontRole::Mono); _label->setMarginRole(dtk::SizeRole::MarginInside); _timeUpdate(); diff --git a/lib/toucanView/TimelineItem.cpp b/lib/toucanView/TimelineItem.cpp index 1a367b9..5194ab4 100644 --- a/lib/toucanView/TimelineItem.cpp +++ b/lib/toucanView/TimelineItem.cpp @@ -154,7 +154,7 @@ namespace toucan _size.handle = event.style->getSizeRole(dtk::SizeRole::Handle, event.displayScale); _size.thumbnailSize.h = 2 * event.style->getSizeRole(dtk::SizeRole::SwatchLarge, event.displayScale); _size.thumbnailSize.w = _size.thumbnailSize.h * _thumbnailGenerator->getAspect(); - _size.fontInfo = event.style->getFontRole(dtk::FontRole::Label, event.displayScale); + _size.fontInfo = event.style->getFontRole(dtk::FontRole::Mono, event.displayScale); _size.fontMetrics = event.fontSystem->getMetrics(_size.fontInfo); std::vector ids; for (const auto& request : _thumbnailRequests) diff --git a/lib/toucanView/ToolBar.cpp b/lib/toucanView/ToolBar.cpp index 7457fae..0a3f259 100644 --- a/lib/toucanView/ToolBar.cpp +++ b/lib/toucanView/ToolBar.cpp @@ -49,25 +49,6 @@ namespace toucan _buttons[name] = button; } - dtk::Divider::create(context, dtk::Orientation::Horizontal, _layout); - - hLayout = dtk::HorizontalLayout::create(context, _layout); - hLayout->setSpacingRole(dtk::SizeRole::SpacingTool); - auto i = actions.find("Window/FullScreen"); - auto button = dtk::ToolButton::create(context, hLayout); - button->setIcon(i->second->icon); - button->setCheckable(true); - button->setTooltip(i->second->toolTip); - button->setCheckedCallback( - [i](bool value) - { - if (i->second->checkedCallback) - { - i->second->checkedCallback(value); - } - }); - _buttons["Window/FullScreen"] = button; - dtk::Divider::create(context, dtk::Orientation::Horizontal, _layout); hLayout = dtk::HorizontalLayout::create(context, _layout); @@ -80,8 +61,8 @@ namespace toucan }; for (const auto& name : actionNames) { - i = actions.find(name); - button = dtk::ToolButton::create(context, hLayout); + auto i = actions.find(name); + auto button = dtk::ToolButton::create(context, hLayout); button->setIcon(i->second->icon); button->setTooltip(i->second->toolTip); button->setClickedCallback( @@ -95,8 +76,8 @@ namespace toucan _buttons[name] = button; } - i = actions.find("View/Frame"); - button = dtk::ToolButton::create(context, hLayout); + auto i = actions.find("View/Frame"); + auto button = dtk::ToolButton::create(context, hLayout); button->setIcon(i->second->icon); button->setCheckable(true); button->setTooltip(i->second->toolTip); @@ -110,6 +91,25 @@ namespace toucan }); _buttons["View/Frame"] = button; + dtk::Divider::create(context, dtk::Orientation::Horizontal, _layout); + + hLayout = dtk::HorizontalLayout::create(context, _layout); + hLayout->setSpacingRole(dtk::SizeRole::SpacingTool); + i = actions.find("Window/FullScreen"); + button = dtk::ToolButton::create(context, hLayout); + button->setIcon(i->second->icon); + button->setCheckable(true); + button->setTooltip(i->second->toolTip); + button->setCheckedCallback( + [i](bool value) + { + if (i->second->checkedCallback) + { + i->second->checkedCallback(value); + } + }); + _buttons["Window/FullScreen"] = button; + _widgetUpdate(); _filesObserver = dtk::ListObserver >::create( diff --git a/lib/toucanView/WindowModel.cpp b/lib/toucanView/WindowModel.cpp index ac10ac2..538017f 100644 --- a/lib/toucanView/WindowModel.cpp +++ b/lib/toucanView/WindowModel.cpp @@ -3,48 +3,102 @@ #include "WindowModel.h" +#include +#include +#include + +#include + +#include + namespace toucan { - WindowModel::WindowModel() + DTK_ENUM_IMPL( + WindowComponent, + "ToolBar", + "PlaybackBar", + "TimelineWidget", + "InfoBar", + "Tools"); + + WindowModel::WindowModel(const std::shared_ptr& context) { - std::map values = + _context = context; + + std::map components = { - { WindowControl::ToolBar, true }, - { WindowControl::PlaybackBar, true }, - { WindowControl::TimelineWidget, true }, - { WindowControl::InfoBar, true }, - { WindowControl::Tools, true } + { WindowComponent::ToolBar, true }, + { WindowComponent::PlaybackBar, true }, + { WindowComponent::TimelineWidget, true }, + { WindowComponent::InfoBar, true }, + { WindowComponent::Tools, true } }; - _controls = dtk::ObservableMap::create(values); - _tooltips = dtk::ObservableValue::create(true); + bool tooltips = true; + try + { + auto settings = context->getSystem(); + const auto json = std::any_cast(settings->get("WindowModel")); + for (auto& i : components) + { + std::stringstream ss; + ss << i.first; + auto j = json.find(ss.str()); + if (j != json.end() && j->is_boolean()) + { + i.second = j->get(); + } + } + auto i = json.find("Tooltips"); + if (i != json.end() && i->is_boolean()) + { + tooltips = i->get(); + } + } + catch (const std::exception&) + {} + + _components = dtk::ObservableMap::create(components); + _tooltips = dtk::ObservableValue::create(tooltips); } WindowModel::~WindowModel() - {} + { + nlohmann::json json; + for (const auto i : _components->get()) + { + std::stringstream ss; + ss << i.first; + json[ss.str()] = i.second; + } + json["Tooltips"] = _tooltips->get(); + auto context = _context.lock(); + auto settings = context->getSystem(); + settings->set("WindowModel", json); + } - const std::map WindowModel::getControls() const + const std::map WindowModel::getComponents() const { - return _controls->get(); + return _components->get(); } - std::shared_ptr > WindowModel::observeControls() const + std::shared_ptr > WindowModel::observeComponents() const { - return _controls; + return _components; } - void WindowModel::setControls(const std::map& value) + void WindowModel::setComponents(const std::map& value) { - _controls->setIfChanged(value); + _components->setIfChanged(value); } - bool WindowModel::getControl(WindowControl value) const + bool WindowModel::getComponent(WindowComponent value) const { - return _controls->getItem(value); + return _components->getItem(value); } - void WindowModel::setControl(WindowControl control, bool value) + void WindowModel::setComponent(WindowComponent component, bool value) { - _controls->setItemOnlyIfChanged(control, value); + _components->setItemOnlyIfChanged(component, value); } bool WindowModel::getTooltips() const diff --git a/lib/toucanView/WindowModel.h b/lib/toucanView/WindowModel.h index b0f6a41..e671d09 100644 --- a/lib/toucanView/WindowModel.h +++ b/lib/toucanView/WindowModel.h @@ -3,40 +3,61 @@ #pragma once +#include #include #include namespace toucan { - enum class WindowControl + //! Window components. + enum class WindowComponent { ToolBar, PlaybackBar, TimelineWidget, InfoBar, - Tools + Tools, + + Count, + First = ToolBar }; + DTK_ENUM(WindowComponent); + //! Window model. class WindowModel : public std::enable_shared_from_this { public: - WindowModel(); + WindowModel(const std::shared_ptr&); virtual ~WindowModel(); - const std::map getControls() const; - std::shared_ptr > observeControls() const; - void setControls(const std::map&); + //! Get the window components. + const std::map getComponents() const; + + //! Observe the window components. + std::shared_ptr > observeComponents() const; + + //! Set the window components. + void setComponents(const std::map&); - bool getControl(WindowControl) const; - void setControl(WindowControl, bool); + //! Get a window component. + bool getComponent(WindowComponent) const; + //! Set a window component. + void setComponent(WindowComponent, bool); + + //! Get whether tooltips are enabled. bool getTooltips() const; + + //! Observe whether tooltips are enabled. std::shared_ptr > observeTooltips() const; + + //! Set whether tooltips are enabled. void setTooltips(bool); private: - std::shared_ptr > _controls; + std::weak_ptr _context; + std::shared_ptr > _components; std::shared_ptr > _tooltips; }; } From 67826f772010506a49871c378ff99fcedc73874f Mon Sep 17 00:00:00 2001 From: Darby Johnston Date: Tue, 3 Dec 2024 15:56:56 -0800 Subject: [PATCH 02/10] Refactoring Signed-off-by: Darby Johnston --- lib/toucanView/ClipItem.h | 2 + lib/toucanView/ExportTool.h | 4 ++ lib/toucanView/File.h | 23 +++++++ lib/toucanView/FileTab.h | 4 +- lib/toucanView/FilesModel.h | 2 +- lib/toucanView/GapItem.h | 2 + lib/toucanView/GraphTool.h | 4 ++ lib/toucanView/IItem.h | 10 +++ lib/toucanView/IToolWidget.h | 2 + lib/toucanView/InfoBar.h | 2 + lib/toucanView/JSONTool.h | 7 ++ lib/toucanView/MainWindow.h | 2 + lib/toucanView/MenuBar.cpp | 4 +- lib/toucanView/MenuBar.h | 3 + lib/toucanView/PlaybackBar.h | 2 +- lib/toucanView/StackItem.h | 2 + lib/toucanView/ThumbnailGenerator.h | 5 ++ lib/toucanView/TimeWidgets.h | 17 +++++ lib/toucanView/TimelineItem.h | 8 +++ lib/toucanView/TimelineWidget.h | 12 ++++ lib/toucanView/ToolBar.cpp | 2 +- lib/toucanView/ToolBar.h | 2 + lib/toucanView/TrackItem.h | 2 + lib/toucanView/ViewModel.cpp | 14 ++-- lib/toucanView/ViewModel.h | 25 +++++-- lib/toucanView/Viewport.cpp | 100 ++++++++++++++-------------- lib/toucanView/Viewport.h | 60 ++++++++++++----- 27 files changed, 237 insertions(+), 85 deletions(-) diff --git a/lib/toucanView/ClipItem.h b/lib/toucanView/ClipItem.h index 4608f4f..35bc1b2 100644 --- a/lib/toucanView/ClipItem.h +++ b/lib/toucanView/ClipItem.h @@ -9,6 +9,7 @@ namespace toucan { + //! Timeline clip item. class ClipItem : public IItem { protected: @@ -22,6 +23,7 @@ namespace toucan public: virtual ~ClipItem(); + //! Create a new item. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, diff --git a/lib/toucanView/ExportTool.h b/lib/toucanView/ExportTool.h index 3049b87..aa69d5e 100644 --- a/lib/toucanView/ExportTool.h +++ b/lib/toucanView/ExportTool.h @@ -22,6 +22,7 @@ namespace toucan { class File; + //! Export widget. class ExportWidget : public dtk::IWidget { protected: @@ -33,6 +34,7 @@ namespace toucan public: virtual ~ExportWidget(); + //! Create a new widget. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, @@ -62,6 +64,7 @@ namespace toucan std::shared_ptr > > _fileObserver; }; + //! Export tool. class ExportTool : public IToolWidget { protected: @@ -73,6 +76,7 @@ namespace toucan public: virtual ~ExportTool(); + //! Create a new tool. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, diff --git a/lib/toucanView/File.h b/lib/toucanView/File.h index 6d84360..509da4a 100644 --- a/lib/toucanView/File.h +++ b/lib/toucanView/File.h @@ -20,6 +20,7 @@ namespace toucan class ThumbnailGenerator; class ViewModel; + //! Timeline file. class File : std::enable_shared_from_this { public: @@ -30,24 +31,46 @@ namespace toucan ~File(); + //! Get the path. const std::filesystem::path& getPath() const; + //! Get the timeline wrapper. const std::shared_ptr& getTimelineWrapper() const; + + //! Get the timeline. const OTIO_NS::SerializableObject::Retainer& getTimeline() const; + //! Get the playback model. const std::shared_ptr& getPlaybackModel() const; + + //! Get the view model. const std::shared_ptr& getViewModel() const; + + //! 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; + + //! Get the number of image channels. int getImageChannels() const; + + //! Get the image data type. const std::string& getImageDataType() const; + //! Observe the current image. std::shared_ptr > > observeCurrentImage() const; + //! Observe the root node. std::shared_ptr > > observeRootNode() const; + + //! Observe the current node. std::shared_ptr > > observeCurrentNode() const; + + //! Set the current node. void setCurrentNode(const std::shared_ptr&); private: diff --git a/lib/toucanView/FileTab.h b/lib/toucanView/FileTab.h index ae83518..cd4f62f 100644 --- a/lib/toucanView/FileTab.h +++ b/lib/toucanView/FileTab.h @@ -11,7 +11,7 @@ namespace toucan class File; class Viewport; - //! File tab. + //! Timeline file tab. class FileTab : public dtk::IWidget { protected: @@ -24,7 +24,7 @@ namespace toucan public: virtual ~FileTab(); - //! Create a new file tab. + //! Create a new tab. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, diff --git a/lib/toucanView/FilesModel.h b/lib/toucanView/FilesModel.h index 2d88bec..8f14017 100644 --- a/lib/toucanView/FilesModel.h +++ b/lib/toucanView/FilesModel.h @@ -15,7 +15,7 @@ namespace toucan class File; class ImageEffectHost; - //! Files model. + //! Timeline files model. class FilesModel : public std::enable_shared_from_this { public: diff --git a/lib/toucanView/GapItem.h b/lib/toucanView/GapItem.h index 9beb6de..2243a7b 100644 --- a/lib/toucanView/GapItem.h +++ b/lib/toucanView/GapItem.h @@ -9,6 +9,7 @@ namespace toucan { + //! Timeline gap item. class GapItem : public IItem { protected: @@ -21,6 +22,7 @@ namespace toucan public: virtual ~GapItem(); + //! Create a new item. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, diff --git a/lib/toucanView/GraphTool.h b/lib/toucanView/GraphTool.h index 780aa6f..8f4cfa3 100644 --- a/lib/toucanView/GraphTool.h +++ b/lib/toucanView/GraphTool.h @@ -16,6 +16,7 @@ namespace toucan { class File; + //! Image graph widget. class GraphWidget : public dtk::IWidget { protected: @@ -27,6 +28,7 @@ namespace toucan public: virtual ~GraphWidget(); + //! Create a new widget. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, @@ -77,6 +79,7 @@ namespace toucan std::shared_ptr > > _currentNodeObserver; }; + //! Image graph tool. class GraphTool : public IToolWidget { protected: @@ -88,6 +91,7 @@ namespace toucan public: virtual ~GraphTool(); + //! Create a new tool. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, diff --git a/lib/toucanView/IItem.h b/lib/toucanView/IItem.h index a21ef70..a88a02b 100644 --- a/lib/toucanView/IItem.h +++ b/lib/toucanView/IItem.h @@ -11,6 +11,7 @@ namespace toucan { class App; + //! Base class for timeline items. class IItem : public dtk::IWidget { protected: @@ -25,16 +26,25 @@ namespace toucan public: virtual ~IItem() = 0; + //! Get the OTIO item. const OTIO_NS::SerializableObject::Retainer& getItem() const; + //! Get the time range. const OTIO_NS::TimeRange& getTimeRange(); + //! Set the item scale. void setScale(double); + //! Get whether the item is selected. bool isSelected() const; + + //! Set whether the item is selected. void setSelected(bool); + //! Convert a position to a time. OTIO_NS::RationalTime posToTime(double) const; + + //! Convert a time to a position. int timeToPos(const OTIO_NS::RationalTime&) const; protected: diff --git a/lib/toucanView/IToolWidget.h b/lib/toucanView/IToolWidget.h index baab5a6..19e4c93 100644 --- a/lib/toucanView/IToolWidget.h +++ b/lib/toucanView/IToolWidget.h @@ -9,6 +9,7 @@ namespace toucan { class App; + //! Base class for tools. class IToolWidget : public dtk::IWidget { protected: @@ -22,6 +23,7 @@ namespace toucan public: virtual ~IToolWidget() = 0; + //! Get the tool text. const std::string& getText() const; protected: diff --git a/lib/toucanView/InfoBar.h b/lib/toucanView/InfoBar.h index f2a8ed2..dd14acc 100644 --- a/lib/toucanView/InfoBar.h +++ b/lib/toucanView/InfoBar.h @@ -14,6 +14,7 @@ namespace toucan class App; class File; + //! Information tool bar. class InfoBar : public dtk::IWidget { protected: @@ -25,6 +26,7 @@ namespace toucan public: virtual ~InfoBar(); + //! Create a new tool bar. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, diff --git a/lib/toucanView/JSONTool.h b/lib/toucanView/JSONTool.h index 18ef45d..b5bbdd8 100644 --- a/lib/toucanView/JSONTool.h +++ b/lib/toucanView/JSONTool.h @@ -18,6 +18,7 @@ namespace toucan { class File; + //! JSON widget. class JSONWidget : public dtk::IWidget { protected: @@ -29,12 +30,16 @@ namespace toucan public: virtual ~JSONWidget(); + //! Create a new widget. static std::shared_ptr create( const std::shared_ptr&, const OTIO_NS::SerializableObject::Retainer&, const std::shared_ptr& parent = nullptr); + //! Set whether the widget is open. void setOpen(bool); + + //! Set the filter. void setFilter(const std::string&); void setGeometry(const dtk::Box2I&) override; @@ -50,6 +55,7 @@ namespace toucan std::shared_ptr _bellows; }; + //! JSON tool. class JSONTool : public IToolWidget { protected: @@ -61,6 +67,7 @@ namespace toucan public: virtual ~JSONTool(); + //! Create a new tool. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, diff --git a/lib/toucanView/MainWindow.h b/lib/toucanView/MainWindow.h index acc252e..d834d56 100644 --- a/lib/toucanView/MainWindow.h +++ b/lib/toucanView/MainWindow.h @@ -24,6 +24,7 @@ namespace toucan class TimelineWidget; class ToolBar; + //! Main window. class MainWindow : public dtk::Window { protected: @@ -36,6 +37,7 @@ namespace toucan public: virtual ~MainWindow(); + //! Create a new window. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, diff --git a/lib/toucanView/MenuBar.cpp b/lib/toucanView/MenuBar.cpp index d8bed6a..a338e0e 100644 --- a/lib/toucanView/MenuBar.cpp +++ b/lib/toucanView/MenuBar.cpp @@ -633,7 +633,7 @@ namespace toucan { if (_file) { - _file->getViewModel()->setFrame(value); + _file->getViewModel()->setFrameView(value); } }); _actions["View/Frame"]->toolTip = "Frame the view"; @@ -893,7 +893,7 @@ namespace toucan if (file) { _frameViewObserver = dtk::ValueObserver::create( - _file->getViewModel()->observeFrame(), + _file->getViewModel()->observeFrameView(), [this](bool value) { _menus["View"]->setItemChecked(_actions["View/Frame"], value); diff --git a/lib/toucanView/MenuBar.h b/lib/toucanView/MenuBar.h index 74783f7..e073bb4 100644 --- a/lib/toucanView/MenuBar.h +++ b/lib/toucanView/MenuBar.h @@ -19,6 +19,7 @@ namespace toucan class FilesModel; class MainWindow; + //! Menu bar. class MenuBar : public dtk::MenuBar { protected: @@ -31,12 +32,14 @@ namespace toucan public: virtual ~MenuBar(); + //! Create a new menu bar. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, const std::shared_ptr&, const std::shared_ptr& parent = nullptr); + //! Get the actions. const std::map >& getActions() const; private: diff --git a/lib/toucanView/PlaybackBar.h b/lib/toucanView/PlaybackBar.h index 409bd6b..d035b43 100644 --- a/lib/toucanView/PlaybackBar.h +++ b/lib/toucanView/PlaybackBar.h @@ -27,7 +27,7 @@ namespace toucan public: virtual ~PlaybackBar(); - //! Create a new widget. + //! Create a new tool bar. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, diff --git a/lib/toucanView/StackItem.h b/lib/toucanView/StackItem.h index 6399dd3..6d85c05 100644 --- a/lib/toucanView/StackItem.h +++ b/lib/toucanView/StackItem.h @@ -9,6 +9,7 @@ namespace toucan { + //! Timeline stack item. class StackItem : public IItem { protected: @@ -21,6 +22,7 @@ namespace toucan public: virtual ~StackItem(); + //! Create a new item. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, diff --git a/lib/toucanView/ThumbnailGenerator.h b/lib/toucanView/ThumbnailGenerator.h index 87fd6b2..91fb092 100644 --- a/lib/toucanView/ThumbnailGenerator.h +++ b/lib/toucanView/ThumbnailGenerator.h @@ -18,6 +18,7 @@ namespace toucan { + //! Thumbnail request. struct ThumbnailRequest { uint64_t id = 0; @@ -26,6 +27,7 @@ namespace toucan std::future > future; }; + //! Thumbnail generator. class ThumbnailGenerator : public std::enable_shared_from_this { public: @@ -36,12 +38,15 @@ namespace toucan ~ThumbnailGenerator(); + //! Get the aspect ratio. float getAspect() const; + //! Get a thumbnail. ThumbnailRequest getThumbnail( const OTIO_NS::RationalTime&, int height); + //! Cancel thumbnail requests. void cancelThumbnails(const std::vector&); private: diff --git a/lib/toucanView/TimeWidgets.h b/lib/toucanView/TimeWidgets.h index f95a34d..de22e8b 100644 --- a/lib/toucanView/TimeWidgets.h +++ b/lib/toucanView/TimeWidgets.h @@ -16,6 +16,7 @@ namespace toucan { + //! Frame buttons. class FrameButtons : public dtk::IWidget { protected: @@ -26,10 +27,12 @@ namespace toucan public: virtual ~FrameButtons(); + //! Create a new widget. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr& parent = nullptr); + //! Set the callback. void setCallback(const std::function&); void setGeometry(const dtk::Box2I&) override; @@ -41,6 +44,7 @@ namespace toucan std::shared_ptr _buttonGroup; }; + //! Playback buttons. class PlaybackButtons : public dtk::IWidget { protected: @@ -51,12 +55,15 @@ namespace toucan public: virtual ~PlaybackButtons(); + //! Create a new widget. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr& parent = nullptr); + //! Set the playback. void setPlayback(Playback); + //! Set the callback. void setCallback(const std::function&); void setGeometry(const dtk::Box2I&) override; @@ -71,6 +78,7 @@ namespace toucan std::shared_ptr _buttonGroup; }; + //! Time edit. class TimeEdit : public dtk::IWidget { protected: @@ -82,14 +90,19 @@ namespace toucan public: virtual ~TimeEdit(); + //! Create a new widget. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr& timeUnitsModel, const std::shared_ptr& parent = nullptr); + //! Set the time. void setTime(const OTIO_NS::RationalTime&); + + //! Set the time range. void setTimeRange(const OTIO_NS::TimeRange&); + //! Set the callback. void setCallback(const std::function&); void setGeometry(const dtk::Box2I&) override; @@ -114,6 +127,7 @@ namespace toucan std::shared_ptr > _timeUnitsObserver; }; + //! Time label. class TimeLabel : public dtk::IWidget { protected: @@ -125,13 +139,16 @@ namespace toucan public: virtual ~TimeLabel(); + //! Create a new widget. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr& timeUnitsModel, const std::shared_ptr& parent = nullptr); + //! Set the time. void setTime(const OTIO_NS::RationalTime&); + //! Set the margin size role. void setMarginRole(dtk::SizeRole); void setGeometry(const dtk::Box2I&) override; diff --git a/lib/toucanView/TimelineItem.h b/lib/toucanView/TimelineItem.h index 186a136..b5664b2 100644 --- a/lib/toucanView/TimelineItem.h +++ b/lib/toucanView/TimelineItem.h @@ -15,6 +15,7 @@ namespace toucan class File; class SelectionModel; + //! Timeline item. class TimelineItem : public IItem { protected: @@ -27,16 +28,23 @@ namespace toucan public: virtual ~TimelineItem(); + //! Create a new item. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, const std::shared_ptr&, const std::shared_ptr& parent = nullptr); + //! Get the current time. const OTIO_NS::RationalTime& getCurrentTime() const; + + //! Set the current time. void setCurrentTime(const OTIO_NS::RationalTime&); + + //! Set the current time callback. void setCurrentTimeCallback(const std::function&); + //! Set the in/out range. void setInOutRange(const OTIO_NS::TimeRange&); void setGeometry(const dtk::Box2I&) override; diff --git a/lib/toucanView/TimelineWidget.h b/lib/toucanView/TimelineWidget.h index 7a977b0..de9c9b3 100644 --- a/lib/toucanView/TimelineWidget.h +++ b/lib/toucanView/TimelineWidget.h @@ -16,6 +16,7 @@ namespace toucan class File; class TimelineItem; + //! Timeline widget. class TimelineWidget : public dtk::IWidget { protected: @@ -27,17 +28,28 @@ namespace toucan public: virtual ~TimelineWidget(); + //! Create a new widget. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, const std::shared_ptr& parent = nullptr); + //! Set the view zoom. void setViewZoom(double); + + //! Set the view zoom. void setViewZoom(double, const dtk::V2I& focus); + //! Get whether frame view is enabled. bool hasFrameView() const; + + //! Frame the view. void frameView(); + + //! Observe whether frame view is enabled. std::shared_ptr > observeFrameView() const; + + //! Set whether frame view is enabled. void setFrameView(bool); void setGeometry(const dtk::Box2I&) override; diff --git a/lib/toucanView/ToolBar.cpp b/lib/toucanView/ToolBar.cpp index 0a3f259..a7b8422 100644 --- a/lib/toucanView/ToolBar.cpp +++ b/lib/toucanView/ToolBar.cpp @@ -175,7 +175,7 @@ namespace toucan if (_file) { _frameViewObserver = dtk::ValueObserver::create( - _file->getViewModel()->observeFrame(), + _file->getViewModel()->observeFrameView(), [this](bool value) { _buttons["View/Frame"]->setChecked(value); diff --git a/lib/toucanView/ToolBar.h b/lib/toucanView/ToolBar.h index d88cdd4..93ec9de 100644 --- a/lib/toucanView/ToolBar.h +++ b/lib/toucanView/ToolBar.h @@ -15,6 +15,7 @@ namespace toucan class File; class MainWindow; + //! Tool bar. class ToolBar : public dtk::IWidget { protected: @@ -28,6 +29,7 @@ namespace toucan public: virtual ~ToolBar(); + //! Create a new tool bar. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, diff --git a/lib/toucanView/TrackItem.h b/lib/toucanView/TrackItem.h index 3418882..ecdbfff 100644 --- a/lib/toucanView/TrackItem.h +++ b/lib/toucanView/TrackItem.h @@ -9,6 +9,7 @@ namespace toucan { + //! Timeline track item. class TrackItem : public IItem { protected: @@ -21,6 +22,7 @@ namespace toucan public: virtual ~TrackItem(); + //! Create a new item. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, diff --git a/lib/toucanView/ViewModel.cpp b/lib/toucanView/ViewModel.cpp index 5f2e59d..1e695e3 100644 --- a/lib/toucanView/ViewModel.cpp +++ b/lib/toucanView/ViewModel.cpp @@ -12,7 +12,7 @@ namespace toucan _zoomIn = dtk::ObservableValue::create(false); _zoomOut = dtk::ObservableValue::create(false); _zoomReset = dtk::ObservableValue::create(false); - _frame = dtk::ObservableValue::create(true); + _frameView = dtk::ObservableValue::create(true); } ViewModel::~ViewModel() @@ -48,18 +48,18 @@ namespace toucan return _zoomReset; } - bool ViewModel::getFrame() const + bool ViewModel::getFrameView() const { - return _frame->get(); + return _frameView->get(); } - std::shared_ptr > ViewModel::observeFrame() const + std::shared_ptr > ViewModel::observeFrameView() const { - return _frame; + return _frameView; } - void ViewModel::setFrame(bool value) + void ViewModel::setFrameView(bool value) { - _frame->setIfChanged(value); + _frameView->setIfChanged(value); } } diff --git a/lib/toucanView/ViewModel.h b/lib/toucanView/ViewModel.h index eb9a2db..90d05d1 100644 --- a/lib/toucanView/ViewModel.h +++ b/lib/toucanView/ViewModel.h @@ -9,6 +9,7 @@ namespace toucan { + //! View model. class ViewModel : public std::enable_shared_from_this { public: @@ -16,21 +17,37 @@ namespace toucan virtual ~ViewModel(); + //! Zoom in. void zoomIn(); + + //! Zoom out. void zoomOut(); + + //! Reset the zoom. void zoomReset(); + + //! Observe the zoom in. std::shared_ptr > observeZoomIn() const; + + //! Observe the zoom out. std::shared_ptr > observeZoomOut() const; + + //! Observe the zoom reset. std::shared_ptr > observeZoomReset() const; - bool getFrame() const; - std::shared_ptr > observeFrame() const; - void setFrame(bool); + //! Get whether frame view is enabled. + bool getFrameView() const; + + //! Observe whether frame view is enabled. + std::shared_ptr > observeFrameView() const; + + //! Set whether frame view is enabled. + void setFrameView(bool); private: std::shared_ptr > _zoomIn; std::shared_ptr > _zoomOut; std::shared_ptr > _zoomReset; - std::shared_ptr > _frame; + std::shared_ptr > _frameView; }; } diff --git a/lib/toucanView/Viewport.cpp b/lib/toucanView/Viewport.cpp index d8a85a4..5f67c77 100644 --- a/lib/toucanView/Viewport.cpp +++ b/lib/toucanView/Viewport.cpp @@ -20,9 +20,9 @@ namespace toucan _setMousePressEnabled(true); _viewModel = file->getViewModel(); - _pos = dtk::ObservableValue::create(); - _zoom = dtk::ObservableValue::create(1.F); - _frame = dtk::ObservableValue::create(true); + _viewPos = dtk::ObservableValue::create(); + _viewZoom = dtk::ObservableValue::create(1.F); + _frameView = dtk::ObservableValue::create(true); _imageObserver = dtk::ValueObserver >::create( file->observeCurrentImage(), @@ -38,7 +38,7 @@ namespace toucan { if (value) { - zoomIn(); + viewZoomIn(); } }); @@ -48,7 +48,7 @@ namespace toucan { if (value) { - zoomOut(); + viewZoomOut(); } }); @@ -58,15 +58,15 @@ namespace toucan { if (value) { - zoomReset(); + viewZoomReset(); } }); _frameObserver = dtk::ValueObserver::create( - _viewModel->observeFrame(), + _viewModel->observeFrameView(), [this](bool value) { - setFrame(value); + setFrameView(value); }); } @@ -83,83 +83,83 @@ namespace toucan return out; } - const dtk::V2I& Viewport::getPos() const + const dtk::V2I& Viewport::getViewPos() const { - return _pos->get(); + return _viewPos->get(); } - float Viewport::getZoom() const + float Viewport::getViewZoom() const { - return _zoom->get(); + return _viewZoom->get(); } - std::shared_ptr > Viewport::observePos() const + std::shared_ptr > Viewport::observeViewPos() const { - return _pos; + return _viewPos; } - std::shared_ptr > Viewport::observeZoom() const + std::shared_ptr > Viewport::observeViewZoom() const { - return _zoom; + return _viewZoom; } - void Viewport::setPosZoom(const dtk::V2I& pos, float zoom) + void Viewport::setViewPosZoom(const dtk::V2I& pos, float zoom) { - setFrame(false); - bool changed = _pos->setIfChanged(pos); - changed |= _zoom->setIfChanged(zoom); + setFrameView(false); + bool changed = _viewPos->setIfChanged(pos); + changed |= _viewZoom->setIfChanged(zoom); if (changed) { _setDrawUpdate(); } } - void Viewport::setZoom(float value) + void Viewport::setViewZoom(float value) { const dtk::Box2I& g = getGeometry(); const dtk::V2I viewportCenter(g.w() / 2, g.h() / 2); - setZoom(value, _isMouseInside() ? (_getMousePos() - g.min) : viewportCenter); + setViewZoom(value, _isMouseInside() ? (_getMousePos() - g.min) : viewportCenter); } - void Viewport::setZoom(float zoom, const dtk::V2I& focus) + void Viewport::setViewZoom(float zoom, const dtk::V2I& focus) { - dtk::V2I pos = _pos->get(); - const float zoomPrev = _zoom->get(); + dtk::V2I pos = _viewPos->get(); + const float zoomPrev = _viewZoom->get(); pos.x = focus.x + (pos.x - focus.x) * (zoom / zoomPrev); pos.y = focus.y + (pos.y - focus.y) * (zoom / zoomPrev); - setPosZoom(pos, zoom); + setViewPosZoom(pos, zoom); } - void Viewport::zoomIn(double amount) + void Viewport::viewZoomIn(double amount) { - setZoom(_zoom->get() * amount); + setViewZoom(_viewZoom->get() * amount); } - void Viewport::zoomOut(double amount) + void Viewport::viewZoomOut(double amount) { - setZoom(_zoom->get() / amount); + setViewZoom(_viewZoom->get() / amount); } - void Viewport::zoomReset() + void Viewport::viewZoomReset() { - setZoom(1.F); + setViewZoom(1.F); } - bool Viewport::getFrame() const + bool Viewport::getFrameView() const { - return _frame->get(); + return _frameView->get(); } - std::shared_ptr > Viewport::observeFrame() const + std::shared_ptr > Viewport::observeFrameView() const { - return _frame; + return _frameView; } - void Viewport::setFrame(bool value) + void Viewport::setFrameView(bool value) { - if (_frame->setIfChanged(value)) + if (_frameView->setIfChanged(value)) { - _viewModel->setFrame(value); + _viewModel->setFrameView(value); _setDrawUpdate(); } } @@ -173,15 +173,15 @@ namespace toucan dtk::Color4F(0.F, 0.F, 0.F)); if (_image) { - if (_frame->get()) + if (_frameView->get()) { _frameUpdate(); } const dtk::Size2I& imageSize = _image->getSize(); dtk::M44F vm; vm = vm * dtk::translate(dtk::V3F(g.min.x, g.min.y, 0.F)); - vm = vm * dtk::translate(dtk::V3F(_pos->get().x, _pos->get().y, 0.F)); - vm = vm * dtk::scale(dtk::V3F(_zoom->get(), _zoom->get(), 1.F)); + vm = vm * dtk::translate(dtk::V3F(_viewPos->get().x, _viewPos->get().y, 0.F)); + vm = vm * dtk::scale(dtk::V3F(_viewZoom->get(), _viewZoom->get(), 1.F)); const auto m = event.render->getTransform(); event.render->setTransform(m * vm); dtk::ImageOptions options; @@ -202,7 +202,7 @@ namespace toucan { event.accept = true; const dtk::V2I& mousePressPos = _getMousePressPos(); - _pos->setIfChanged(dtk::V2I( + _viewPos->setIfChanged(dtk::V2I( _viewMousePress.x + (event.pos.x - mousePressPos.x), _viewMousePress.y + (event.pos.y - mousePressPos.y))); _setDrawUpdate(); @@ -215,8 +215,8 @@ namespace toucan if (0 == event.button && 0 == event.modifiers) { event.accept = true; - setFrame(false); - _viewMousePress = _pos->get(); + setFrameView(false); + _viewMousePress = _viewPos->get(); } } @@ -234,18 +234,18 @@ namespace toucan event.accept = true; if (event.value.y > 0) { - zoomOut(.9F); + viewZoomOut(.9F); } else if (event.value.y < 0) { - zoomIn(.9F); + viewZoomIn(.9F); } } } void Viewport::_frameUpdate() { - if (_frame && _image) + if (_frameView->get() && _image) { const dtk::Box2I& g = getGeometry(); const dtk::Size2I imageSize = _image->getSize(); @@ -259,8 +259,8 @@ namespace toucan g.w() / 2.F - c.x * zoom, g.h() / 2.F - c.y * zoom); - _pos->setIfChanged(pos); - _zoom->setIfChanged(zoom); + _viewPos->setIfChanged(pos); + _viewZoom->setIfChanged(zoom); } } } diff --git a/lib/toucanView/Viewport.h b/lib/toucanView/Viewport.h index 6994559..2ec5398 100644 --- a/lib/toucanView/Viewport.h +++ b/lib/toucanView/Viewport.h @@ -12,6 +12,7 @@ namespace toucan class File; class ViewModel; + //! Viewport widget. class Viewport : public dtk::IWidget { protected: @@ -23,25 +24,50 @@ namespace toucan public: virtual ~Viewport(); + //! Create a new widget. static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, const std::shared_ptr& parent = nullptr); - const dtk::V2I& getPos() const; - float getZoom() const; - std::shared_ptr > observePos() const; - std::shared_ptr > observeZoom() const; - void setPosZoom(const dtk::V2I&, float); - void setZoom(float); - void setZoom(float, const dtk::V2I& focus); - void zoomIn(double amount = 2.F); - void zoomOut(double amount = 2.F); - void zoomReset(); - - bool getFrame() const; - std::shared_ptr > observeFrame() const; - void setFrame(bool); + //! Get the view position. + const dtk::V2I& getViewPos() const; + + //! Get the view zoom. + float getViewZoom() const; + + //! Observe the view position. + std::shared_ptr > observeViewPos() const; + + //! Observe the view zoom. + std::shared_ptr > observeViewZoom() const; + + //! Set the view position and zoom. + void setViewPosZoom(const dtk::V2I&, float); + + //! Set the view zoom. + void setViewZoom(float); + + //! Set the view zoom. + void setViewZoom(float, const dtk::V2I& focus); + + //! Zoom in the view. + void viewZoomIn(double amount = 2.F); + + //! Zoom out the view. + void viewZoomOut(double amount = 2.F); + + //! Reset the view zoom. + void viewZoomReset(); + + //! Get whether frame view is enabled. + bool getFrameView() const; + + //! Observe whether frame view is enabled. + std::shared_ptr > observeFrameView() const; + + //! Set whether frame view is enabled. + void setFrameView(bool); void drawEvent(const dtk::Box2I&, const dtk::DrawEvent&) override; void mouseMoveEvent(dtk::MouseMoveEvent&) override; @@ -54,9 +80,9 @@ namespace toucan std::shared_ptr _viewModel; std::shared_ptr _image; - std::shared_ptr > _pos; - std::shared_ptr > _zoom; - std::shared_ptr > _frame; + std::shared_ptr > _viewPos; + std::shared_ptr > _viewZoom; + std::shared_ptr > _frameView; dtk::V2I _viewMousePress; std::shared_ptr > > _imageObserver; From f854c6ad464ed991132e8e9910ef853443ea399b Mon Sep 17 00:00:00 2001 From: Darby Johnston Date: Tue, 3 Dec 2024 18:32:59 -0800 Subject: [PATCH 03/10] Refactoring Signed-off-by: Darby Johnston --- bin/toucan-render/App.cpp | 32 +- cmake/SuperBuild/Builddtk-deps.cmake | 2 +- cmake/SuperBuild/Builddtk.cmake | 2 +- lib/toucan/CMakeLists.txt | 4 + lib/toucan/FFmpeg.cpp | 60 ++ lib/toucan/FFmpeg.h | 30 + lib/toucan/FFmpegRead.cpp | 1016 +++++++++++++------------- lib/toucan/FFmpegRead.h | 106 +-- lib/toucan/FFmpegWrite.cpp | 304 ++++++++ lib/toucan/FFmpegWrite.h | 55 ++ lib/toucan/Read.cpp | 2 +- lib/toucan/Read.h | 2 +- lib/toucanView/ExportTool.cpp | 126 +++- lib/toucanView/ExportTool.h | 19 +- 14 files changed, 1139 insertions(+), 621 deletions(-) create mode 100644 lib/toucan/FFmpeg.cpp create mode 100644 lib/toucan/FFmpeg.h create mode 100644 lib/toucan/FFmpegWrite.cpp create mode 100644 lib/toucan/FFmpegWrite.h diff --git a/bin/toucan-render/App.cpp b/bin/toucan-render/App.cpp index b797e80..8e3293f 100644 --- a/bin/toucan-render/App.cpp +++ b/bin/toucan-render/App.cpp @@ -5,6 +5,7 @@ #include "Util.h" +#include #include #include @@ -258,6 +259,16 @@ namespace toucan OIIO::ROI(0, filmstripSize.x, 0, filmstripSize.y, 0, 1, 0, 4)); } + // Open the movie file. + std::shared_ptr ffWrite; + if (ffmpeg::isExtension(outputPath.extension().string())) + { + ffWrite = std::make_shared( + outputPath, + OIIO::ImageSpec(imageSize.x, imageSize.y, 3), + timeRange.duration().rate()); + } + // Render the timeline frames. if (!_options.y4m.empty()) { @@ -284,13 +295,20 @@ namespace toucan { if (!_args.outputRaw) { - const std::string fileName = getSequenceFrame( - outputPath.parent_path().string(), - outputSplit.first, - outputStartFrame + time.to_frames(), - outputNumberPadding, - outputPath.extension().string()); - buf.write(fileName); + if (ffWrite) + { + ffWrite->writeImage(buf, time); + } + else + { + const std::string fileName = getSequenceFrame( + outputPath.parent_path().string(), + outputSplit.first, + outputStartFrame + time.to_frames(), + outputNumberPadding, + outputPath.extension().string()); + buf.write(fileName); + } } else if (!_options.raw.empty()) { diff --git a/cmake/SuperBuild/Builddtk-deps.cmake b/cmake/SuperBuild/Builddtk-deps.cmake index eebe369..a0107f0 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 "0bd51326554b033502b4b912bc3f24f2cc87ac16") +set(dtk_GIT_TAG "b5d7a808efae236ee1a4635956cf500fae9528e2") set(dtk-deps_ARGS ${toucan_EXTERNAL_PROJECT_ARGS} diff --git a/cmake/SuperBuild/Builddtk.cmake b/cmake/SuperBuild/Builddtk.cmake index 47eec07..8981a6a 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 "0bd51326554b033502b4b912bc3f24f2cc87ac16") +set(dtk_GIT_TAG "b5d7a808efae236ee1a4635956cf500fae9528e2") set(dtk_DEPS dtk-deps) set(dtk_ARGS diff --git a/lib/toucan/CMakeLists.txt b/lib/toucan/CMakeLists.txt index 1a20074..c692dbb 100644 --- a/lib/toucan/CMakeLists.txt +++ b/lib/toucan/CMakeLists.txt @@ -1,6 +1,8 @@ set(HEADERS Comp.h + FFmpeg.h FFmpegRead.h + FFmpegWrite.h ImageEffect.h ImageEffectHost.h ImageGraph.h @@ -20,7 +22,9 @@ set(HEADERS_PRIVATE ImageEffect_p.h) set(SOURCE Comp.cpp + FFmpeg.cpp FFmpegRead.cpp + FFmpegWrite.cpp ImageEffect.cpp ImageEffectHost.cpp ImageGraph.cpp diff --git a/lib/toucan/FFmpeg.cpp b/lib/toucan/FFmpeg.cpp new file mode 100644 index 0000000..554016c --- /dev/null +++ b/lib/toucan/FFmpeg.cpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#include "FFmpeg.h" + +#include "Util.h" + +extern "C" +{ +#include +} + +#include + +namespace toucan +{ + namespace ffmpeg + { + void log(void*, int level, const char* fmt, va_list vl) + { + switch (level) + { + case AV_LOG_PANIC: + case AV_LOG_FATAL: + case AV_LOG_ERROR: + case AV_LOG_WARNING: + case AV_LOG_INFO: + { + char buf[4096]; + vsnprintf(buf, 4096, fmt, vl); + std::cout << buf; + } + break; + case AV_LOG_VERBOSE: + default: break; + } + } + + AVRational swap(AVRational value) + { + return AVRational({ value.den, value.num }); + } + + bool isExtension(const std::string& extension) + { + const std::string lower = toLower(extension); + return + lower == ".mp4" || + lower == ".m4v" || + lower == ".mov"; + } + + std::string getErrorLabel(int r) + { + char buf[4096]; + av_strerror(r, buf, 4096); + return std::string(buf); + } + } +} diff --git a/lib/toucan/FFmpeg.h b/lib/toucan/FFmpeg.h new file mode 100644 index 0000000..43f27d6 --- /dev/null +++ b/lib/toucan/FFmpeg.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#pragma once + +#include + +extern "C" +{ +#include +#include +} + +namespace toucan +{ + namespace ffmpeg + { + //! FFmpeg log callback. + void log(void*, int level, const char* fmt, va_list vl); + + //! Swap the numerator and denominator. + AVRational swap(AVRational); + + //! Check whether the given extension is compatible. + bool isExtension(const std::string&); + + //! Get an error label. + std::string getErrorLabel(int); + } +} diff --git a/lib/toucan/FFmpegRead.cpp b/lib/toucan/FFmpegRead.cpp index b368bd2..fc5a940 100644 --- a/lib/toucan/FFmpegRead.cpp +++ b/lib/toucan/FFmpegRead.cpp @@ -16,587 +16,559 @@ extern "C" namespace toucan { - namespace + namespace ffmpeg { - const size_t avIOContextBufferSize = 4096; - - void logCallback(void*, int level, const char* fmt, va_list vl) + namespace { - switch (level) + const size_t avIOContextBufferSize = 4096; + + class Packet { - case AV_LOG_PANIC: - case AV_LOG_FATAL: - case AV_LOG_ERROR: - case AV_LOG_WARNING: - case AV_LOG_INFO: + public: + Packet() { - char buf[4096]; - vsnprintf(buf, 4096, fmt, vl); - std::cout << buf; + p = av_packet_alloc(); } - break; - case AV_LOG_VERBOSE: - default: break; - } - } - AVRational swap(AVRational value) - { - return AVRational({ value.den, value.num }); + ~Packet() + { + av_packet_free(&p); + } + + AVPacket* p = nullptr; + }; } - class Packet + Read::Read( + const std::filesystem::path& path, + const MemoryReference& memoryReference) : + _path(path), + _memoryReference(memoryReference) { - public: - Packet() - { - p = av_packet_alloc(); - } - - ~Packet() - { - av_packet_free(&p); - } - - AVPacket* p = nullptr; - }; + av_log_set_level(AV_LOG_QUIET); + //av_log_set_level(AV_LOG_VERBOSE); + //av_log_set_callback(log); - size_t getByteCount(const OIIO::ImageSpec& spec) - { - size_t type = 0; - switch (spec.format.basetype) + if (memoryReference.isValid()) { + _avFormatContext = avformat_alloc_context(); + if (!_avFormatContext) + { + throw std::runtime_error("Cannot allocate format context"); + } - } - return spec.width * spec.height * type; - } - } - - FFmpegRead::FFmpegRead( - const std::filesystem::path& path, - const MemoryReference& memoryReference) : - _path(path), - _memoryReference(memoryReference) - { - av_log_set_level(AV_LOG_QUIET); - //av_log_set_level(AV_LOG_VERBOSE); - //av_log_set_callback(logCallback); + _avIOBufferData = AVIOBufferData( + reinterpret_cast(memoryReference.getData()), + memoryReference.getSize()); + _avIOContextBuffer = static_cast(av_malloc(avIOContextBufferSize)); + _avIOContext = avio_alloc_context( + _avIOContextBuffer, + avIOContextBufferSize, + 0, + &_avIOBufferData, + &_avIOBufferRead, + nullptr, + &_avIOBufferSeek); + if (!_avIOContext) + { + throw std::runtime_error("Cannot allocate I/O context"); + } - if (memoryReference.isValid()) - { - _avFormatContext = avformat_alloc_context(); - if (!_avFormatContext) - { - throw std::runtime_error("Cannot allocate format context"); + _avFormatContext->pb = _avIOContext; } - _avIOBufferData = AVIOBufferData( - reinterpret_cast(memoryReference.getData()), - memoryReference.getSize()); - _avIOContextBuffer = static_cast(av_malloc(avIOContextBufferSize)); - _avIOContext = avio_alloc_context( - _avIOContextBuffer, - avIOContextBufferSize, - 0, - &_avIOBufferData, - &_avIOBufferRead, + const std::string fileName = path.string(); + int r = avformat_open_input( + &_avFormatContext, + !_avFormatContext ? fileName.c_str() : nullptr, nullptr, - &_avIOBufferSeek); - if (!_avIOContext) + nullptr); + if (r < 0 || !_avFormatContext) { - throw std::runtime_error("Cannot allocate I/O context"); + throw std::runtime_error("Cannot open file"); } - _avFormatContext->pb = _avIOContext; - } - - const std::string fileName = path.string(); - int r = avformat_open_input( - &_avFormatContext, - !_avFormatContext ? fileName.c_str() : nullptr, - nullptr, - nullptr); - if (r < 0 || !_avFormatContext) - { - throw std::runtime_error("Cannot open file"); - } - - r = avformat_find_stream_info(_avFormatContext, nullptr); - if (r < 0) - { - throw std::runtime_error("Cannot find stream info"); - } - for (unsigned int i = 0; i < _avFormatContext->nb_streams; ++i) - { - //av_dump_format(_avFormatContext, 0, fileName.c_str(), 0); - if (AVMEDIA_TYPE_VIDEO == _avFormatContext->streams[i]->codecpar->codec_type && - AV_DISPOSITION_DEFAULT == _avFormatContext->streams[i]->disposition) + r = avformat_find_stream_info(_avFormatContext, nullptr); + if (r < 0) { - _avStream = i; - break; + throw std::runtime_error("Cannot find stream info"); } - } - if (-1 == _avStream) - { for (unsigned int i = 0; i < _avFormatContext->nb_streams; ++i) { - if (AVMEDIA_TYPE_VIDEO == _avFormatContext->streams[i]->codecpar->codec_type) + //av_dump_format(_avFormatContext, 0, fileName.c_str(), 0); + if (AVMEDIA_TYPE_VIDEO == _avFormatContext->streams[i]->codecpar->codec_type && + AV_DISPOSITION_DEFAULT == _avFormatContext->streams[i]->disposition) { _avStream = i; break; } } - } - - int dataStream = -1; - for (unsigned int i = 0; i < _avFormatContext->nb_streams; ++i) - { - if (AVMEDIA_TYPE_DATA == _avFormatContext->streams[i]->codecpar->codec_type && - AV_DISPOSITION_DEFAULT == _avFormatContext->streams[i]->disposition) + if (-1 == _avStream) { - dataStream = i; - break; + for (unsigned int i = 0; i < _avFormatContext->nb_streams; ++i) + { + if (AVMEDIA_TYPE_VIDEO == _avFormatContext->streams[i]->codecpar->codec_type) + { + _avStream = i; + break; + } + } } - } - if (-1 == dataStream) - { + + int dataStream = -1; for (unsigned int i = 0; i < _avFormatContext->nb_streams; ++i) { - if (AVMEDIA_TYPE_DATA == _avFormatContext->streams[i]->codecpar->codec_type) + if (AVMEDIA_TYPE_DATA == _avFormatContext->streams[i]->codecpar->codec_type && + AV_DISPOSITION_DEFAULT == _avFormatContext->streams[i]->disposition) { dataStream = i; break; } } - } - std::string timecode; - if (dataStream != -1) - { - AVDictionaryEntry* tag = nullptr; - while ((tag = av_dict_get( - _avFormatContext->streams[dataStream]->metadata, - "", - tag, - AV_DICT_IGNORE_SUFFIX))) + if (-1 == dataStream) { - if ("timecode" == toLower(tag->key)) + for (unsigned int i = 0; i < _avFormatContext->nb_streams; ++i) { - timecode = tag->value; - break; + if (AVMEDIA_TYPE_DATA == _avFormatContext->streams[i]->codecpar->codec_type) + { + dataStream = i; + break; + } } } - } - - if (_avStream != -1) - { - //av_dump_format(_avFormatContext, _avStream, fileName.c_str(), 0); - - auto avVideoStream = _avFormatContext->streams[_avStream]; - auto avVideoCodecParameters = avVideoStream->codecpar; - auto avVideoCodec = avcodec_find_decoder(avVideoCodecParameters->codec_id); - if (!avVideoCodec) - { - throw std::runtime_error("No video codec found"); - } - _avCodecParameters[_avStream] = avcodec_parameters_alloc(); - if (!_avCodecParameters[_avStream]) - { - throw std::runtime_error("Cannot allocate parameters"); - } - avcodec_parameters_copy(_avCodecParameters[_avStream], avVideoCodecParameters); - _avCodecContext[_avStream] = avcodec_alloc_context3(avVideoCodec); - if (!_avCodecParameters[_avStream]) - { - throw std::runtime_error("Cannot allocate context"); - } - avcodec_parameters_to_context(_avCodecContext[_avStream], _avCodecParameters[_avStream]); - _avCodecContext[_avStream]->thread_count = 0; - _avCodecContext[_avStream]->thread_type = FF_THREAD_FRAME; - r = avcodec_open2(_avCodecContext[_avStream], avVideoCodec, 0); - if (r < 0) + std::string timecode; + if (dataStream != -1) { - throw std::runtime_error("Cannot open stream"); + AVDictionaryEntry* tag = nullptr; + while ((tag = av_dict_get( + _avFormatContext->streams[dataStream]->metadata, + "", + tag, + AV_DICT_IGNORE_SUFFIX))) + { + if ("timecode" == toLower(tag->key)) + { + timecode = tag->value; + break; + } + } } - int width = _avCodecParameters[_avStream]->width; - int height = _avCodecParameters[_avStream]->height; - double pixelAspectRatio = 1.0; - if (_avCodecParameters[_avStream]->sample_aspect_ratio.den > 0 && - _avCodecParameters[_avStream]->sample_aspect_ratio.num > 0) + if (_avStream != -1) { - pixelAspectRatio = av_q2d(_avCodecParameters[_avStream]->sample_aspect_ratio); - } + //av_dump_format(_avFormatContext, _avStream, fileName.c_str(), 0); - _avInputPixelFormat = static_cast(_avCodecParameters[_avStream]->format); - int nchannels = 0; - OIIO::TypeDesc format = OIIO::TypeDesc::UNKNOWN; - switch (_avInputPixelFormat) - { - case AV_PIX_FMT_RGB24: - _avOutputPixelFormat = _avInputPixelFormat; - nchannels = 3; - format = OIIO::TypeUInt8; - break; - case AV_PIX_FMT_GRAY8: - _avOutputPixelFormat = _avInputPixelFormat; - nchannels = 1; - format = OIIO::TypeUInt8; - break; - case AV_PIX_FMT_RGBA: - _avOutputPixelFormat = _avInputPixelFormat; - nchannels = 4; - format = OIIO::TypeUInt8; - break; - case AV_PIX_FMT_YUV420P: - case AV_PIX_FMT_YUV422P: - case AV_PIX_FMT_YUV444P: - _avOutputPixelFormat = AV_PIX_FMT_RGB24; - nchannels = 3; - format = OIIO::TypeUInt8; - break; - case AV_PIX_FMT_YUV420P10BE: - case AV_PIX_FMT_YUV420P10LE: - case AV_PIX_FMT_YUV420P12BE: - case AV_PIX_FMT_YUV420P12LE: - case AV_PIX_FMT_YUV420P16BE: - case AV_PIX_FMT_YUV420P16LE: - case AV_PIX_FMT_YUV422P10BE: - case AV_PIX_FMT_YUV422P10LE: - case AV_PIX_FMT_YUV422P12BE: - case AV_PIX_FMT_YUV422P12LE: - case AV_PIX_FMT_YUV422P16BE: - case AV_PIX_FMT_YUV422P16LE: - case AV_PIX_FMT_YUV444P10BE: - case AV_PIX_FMT_YUV444P10LE: - case AV_PIX_FMT_YUV444P12BE: - case AV_PIX_FMT_YUV444P12LE: - case AV_PIX_FMT_YUV444P16BE: - case AV_PIX_FMT_YUV444P16LE: - _avOutputPixelFormat = AV_PIX_FMT_RGB48; - nchannels = 3; - format = OIIO::TypeUInt16; - break; - case AV_PIX_FMT_YUVA420P: - case AV_PIX_FMT_YUVA422P: - case AV_PIX_FMT_YUVA444P: - _avOutputPixelFormat = AV_PIX_FMT_RGBA; - nchannels = 4; - format = OIIO::TypeUInt8; - break; - case AV_PIX_FMT_YUVA444P10BE: - case AV_PIX_FMT_YUVA444P10LE: - case AV_PIX_FMT_YUVA444P12BE: - case AV_PIX_FMT_YUVA444P12LE: - case AV_PIX_FMT_YUVA444P16BE: - case AV_PIX_FMT_YUVA444P16LE: - _avOutputPixelFormat = AV_PIX_FMT_RGBA64; - nchannels = 4; - format = OIIO::TypeUInt16; - break; - default: - _avOutputPixelFormat = AV_PIX_FMT_RGB24; - nchannels = 3; - format = OIIO::TypeUInt8; - break; - } - _spec = OIIO::ImageSpec(width, height, nchannels, format); + auto avVideoStream = _avFormatContext->streams[_avStream]; + auto avVideoCodecParameters = avVideoStream->codecpar; + auto avVideoCodec = avcodec_find_decoder(avVideoCodecParameters->codec_id); + if (!avVideoCodec) + { + throw std::runtime_error("No video codec found"); + } + _avCodecParameters[_avStream] = avcodec_parameters_alloc(); + if (!_avCodecParameters[_avStream]) + { + throw std::runtime_error("Cannot allocate parameters"); + } + avcodec_parameters_copy(_avCodecParameters[_avStream], avVideoCodecParameters); + _avCodecContext[_avStream] = avcodec_alloc_context3(avVideoCodec); + if (!_avCodecParameters[_avStream]) + { + throw std::runtime_error("Cannot allocate context"); + } + avcodec_parameters_to_context(_avCodecContext[_avStream], _avCodecParameters[_avStream]); + _avCodecContext[_avStream]->thread_count = 0; + _avCodecContext[_avStream]->thread_type = FF_THREAD_FRAME; + r = avcodec_open2(_avCodecContext[_avStream], avVideoCodec, 0); + if (r < 0) + { + throw std::runtime_error("Cannot open stream"); + } - _avSpeed = av_guess_frame_rate(_avFormatContext, avVideoStream, nullptr); - const double speed = av_q2d(_avSpeed); + int width = _avCodecParameters[_avStream]->width; + int height = _avCodecParameters[_avStream]->height; + double pixelAspectRatio = 1.0; + if (_avCodecParameters[_avStream]->sample_aspect_ratio.den > 0 && + _avCodecParameters[_avStream]->sample_aspect_ratio.num > 0) + { + pixelAspectRatio = av_q2d(_avCodecParameters[_avStream]->sample_aspect_ratio); + } - std::size_t frameCount = 0; - if (avVideoStream->nb_frames > 0) - { - frameCount = avVideoStream->nb_frames; - } - else if (avVideoStream->duration != AV_NOPTS_VALUE) - { - frameCount = av_rescale_q( - avVideoStream->duration, - avVideoStream->time_base, - swap(avVideoStream->r_frame_rate)); - } - else if (_avFormatContext->duration != AV_NOPTS_VALUE) - { - frameCount = av_rescale_q( - _avFormatContext->duration, - av_get_time_base_q(), - swap(avVideoStream->r_frame_rate)); - } + _avInputPixelFormat = static_cast(_avCodecParameters[_avStream]->format); + int nchannels = 0; + OIIO::TypeDesc format = OIIO::TypeDesc::UNKNOWN; + switch (_avInputPixelFormat) + { + case AV_PIX_FMT_RGB24: + _avOutputPixelFormat = _avInputPixelFormat; + nchannels = 3; + format = OIIO::TypeUInt8; + break; + case AV_PIX_FMT_GRAY8: + _avOutputPixelFormat = _avInputPixelFormat; + nchannels = 1; + format = OIIO::TypeUInt8; + break; + case AV_PIX_FMT_RGBA: + _avOutputPixelFormat = _avInputPixelFormat; + nchannels = 4; + format = OIIO::TypeUInt8; + break; + case AV_PIX_FMT_YUV420P: + case AV_PIX_FMT_YUV422P: + case AV_PIX_FMT_YUV444P: + _avOutputPixelFormat = AV_PIX_FMT_RGB24; + nchannels = 3; + format = OIIO::TypeUInt8; + break; + case AV_PIX_FMT_YUV420P10BE: + case AV_PIX_FMT_YUV420P10LE: + case AV_PIX_FMT_YUV420P12BE: + case AV_PIX_FMT_YUV420P12LE: + case AV_PIX_FMT_YUV420P16BE: + case AV_PIX_FMT_YUV420P16LE: + case AV_PIX_FMT_YUV422P10BE: + case AV_PIX_FMT_YUV422P10LE: + case AV_PIX_FMT_YUV422P12BE: + case AV_PIX_FMT_YUV422P12LE: + case AV_PIX_FMT_YUV422P16BE: + case AV_PIX_FMT_YUV422P16LE: + case AV_PIX_FMT_YUV444P10BE: + case AV_PIX_FMT_YUV444P10LE: + case AV_PIX_FMT_YUV444P12BE: + case AV_PIX_FMT_YUV444P12LE: + case AV_PIX_FMT_YUV444P16BE: + case AV_PIX_FMT_YUV444P16LE: + _avOutputPixelFormat = AV_PIX_FMT_RGB48; + nchannels = 3; + format = OIIO::TypeUInt16; + break; + case AV_PIX_FMT_YUVA420P: + case AV_PIX_FMT_YUVA422P: + case AV_PIX_FMT_YUVA444P: + _avOutputPixelFormat = AV_PIX_FMT_RGBA; + nchannels = 4; + format = OIIO::TypeUInt8; + break; + case AV_PIX_FMT_YUVA444P10BE: + case AV_PIX_FMT_YUVA444P10LE: + case AV_PIX_FMT_YUVA444P12BE: + case AV_PIX_FMT_YUVA444P12LE: + case AV_PIX_FMT_YUVA444P16BE: + case AV_PIX_FMT_YUVA444P16LE: + _avOutputPixelFormat = AV_PIX_FMT_RGBA64; + nchannels = 4; + format = OIIO::TypeUInt16; + break; + default: + _avOutputPixelFormat = AV_PIX_FMT_RGB24; + nchannels = 3; + format = OIIO::TypeUInt8; + break; + } + _spec = OIIO::ImageSpec(width, height, nchannels, format); - AVDictionaryEntry* tag = nullptr; - while ((tag = av_dict_get(_avFormatContext->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) - { - const std::string key(tag->key); - const std::string value(tag->value); - if ("timecode" == toLower(key)) + _avSpeed = av_guess_frame_rate(_avFormatContext, avVideoStream, nullptr); + const double speed = av_q2d(_avSpeed); + + std::size_t frameCount = 0; + if (avVideoStream->nb_frames > 0) { - timecode = value; + frameCount = avVideoStream->nb_frames; + } + else if (avVideoStream->duration != AV_NOPTS_VALUE) + { + frameCount = av_rescale_q( + avVideoStream->duration, + avVideoStream->time_base, + swap(avVideoStream->r_frame_rate)); + } + else if (_avFormatContext->duration != AV_NOPTS_VALUE) + { + frameCount = av_rescale_q( + _avFormatContext->duration, + av_get_time_base_q(), + swap(avVideoStream->r_frame_rate)); } - } - OTIO_NS::RationalTime startTime(0.0, speed); - if (!timecode.empty()) - { - opentime::ErrorStatus errorStatus; - const OTIO_NS::RationalTime time = OTIO_NS::RationalTime::from_timecode( - timecode, - speed, - &errorStatus); - if (!opentime::is_error(errorStatus)) + AVDictionaryEntry* tag = nullptr; + while ((tag = av_dict_get(_avFormatContext->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { - startTime = time.floor(); + const std::string key(tag->key); + const std::string value(tag->value); + if ("timecode" == toLower(key)) + { + timecode = value; + } } + + OTIO_NS::RationalTime startTime(0.0, speed); + if (!timecode.empty()) + { + opentime::ErrorStatus errorStatus; + const OTIO_NS::RationalTime time = OTIO_NS::RationalTime::from_timecode( + timecode, + speed, + &errorStatus); + if (!opentime::is_error(errorStatus)) + { + startTime = time.floor(); + } + } + _timeRange = OTIO_NS::TimeRange( + startTime, + OTIO_NS::RationalTime(frameCount, speed)); + _currentTime = startTime; + + _avFrame = av_frame_alloc(); + if (!_avFrame) + { + throw std::runtime_error("Cannot allocate frame"); + } + _avFrame2 = av_frame_alloc(); + if (!_avFrame2) + { + throw std::runtime_error("Cannot allocate frame"); + } + //! \bug These fields need to be filled out for + //! sws_scale_frame()? + _avFrame2->format = _avOutputPixelFormat; + _avFrame2->width = width; + _avFrame2->height = height; + _avFrame2->buf[0] = av_buffer_alloc(_spec.image_bytes()); + + _swsContext = sws_alloc_context(); + if (!_swsContext) + { + throw std::runtime_error("Cannot allocate context"); + } + av_opt_set_defaults(_swsContext); + int r = av_opt_set_int(_swsContext, "srcw", _avCodecParameters[_avStream]->width, AV_OPT_SEARCH_CHILDREN); + r = av_opt_set_int(_swsContext, "srch", _avCodecParameters[_avStream]->height, AV_OPT_SEARCH_CHILDREN); + r = av_opt_set_int(_swsContext, "src_format", _avInputPixelFormat, AV_OPT_SEARCH_CHILDREN); + r = av_opt_set_int(_swsContext, "dstw", _avCodecParameters[_avStream]->width, AV_OPT_SEARCH_CHILDREN); + r = av_opt_set_int(_swsContext, "dsth", _avCodecParameters[_avStream]->height, AV_OPT_SEARCH_CHILDREN); + r = av_opt_set_int(_swsContext, "dst_format", _avOutputPixelFormat, AV_OPT_SEARCH_CHILDREN); + r = av_opt_set_int(_swsContext, "sws_flags", SWS_FAST_BILINEAR, AV_OPT_SEARCH_CHILDREN); + r = av_opt_set_int(_swsContext, "threads", 0, AV_OPT_SEARCH_CHILDREN); + r = sws_init_context(_swsContext, nullptr, nullptr); + if (r < 0) + { + throw std::runtime_error("Cannot initialize sws context"); + } + + const int* inTable = nullptr; + int inFull = 0; + const int* outTable = nullptr; + int outFull = 0; + int brightness = 0; + int contrast = 0; + int saturation = 0; + r = sws_getColorspaceDetails( + _swsContext, + (int**)&inTable, + &inFull, + (int**)&outTable, + &outFull, + &brightness, + &contrast, + &saturation); + + AVColorSpace colorSpace = _avCodecParameters[_avStream]->color_space; + if (AVCOL_SPC_UNSPECIFIED == colorSpace) + { + colorSpace = AVCOL_SPC_BT709; + } + inFull = 1; + outFull = 1; + + r = sws_setColorspaceDetails( + _swsContext, + sws_getCoefficients(colorSpace), + inFull, + sws_getCoefficients(AVCOL_SPC_BT709), + outFull, + brightness, + contrast, + saturation); } - _timeRange = OTIO_NS::TimeRange( - startTime, - OTIO_NS::RationalTime(frameCount, speed)); - _currentTime = startTime; + } - _avFrame = av_frame_alloc(); - if (!_avFrame) + Read::~Read() + { + if (_swsContext) { - throw std::runtime_error("Cannot allocate frame"); + sws_freeContext(_swsContext); } - _avFrame2 = av_frame_alloc(); - if (!_avFrame2) + if (_avFrame2) { - throw std::runtime_error("Cannot allocate frame"); + av_frame_free(&_avFrame2); } - //! \bug These fields need to be filled out for - //! sws_scale_frame()? - _avFrame2->format = _avOutputPixelFormat; - _avFrame2->width = width; - _avFrame2->height = height; - _avFrame2->buf[0] = av_buffer_alloc(_spec.image_bytes()); - - _swsContext = sws_alloc_context(); - if (!_swsContext) + if (_avFrame) { - throw std::runtime_error("Cannot allocate context"); + av_frame_free(&_avFrame); } - av_opt_set_defaults(_swsContext); - int r = av_opt_set_int(_swsContext, "srcw", _avCodecParameters[_avStream]->width, AV_OPT_SEARCH_CHILDREN); - r = av_opt_set_int(_swsContext, "srch", _avCodecParameters[_avStream]->height, AV_OPT_SEARCH_CHILDREN); - r = av_opt_set_int(_swsContext, "src_format", _avInputPixelFormat, AV_OPT_SEARCH_CHILDREN); - r = av_opt_set_int(_swsContext, "dstw", _avCodecParameters[_avStream]->width, AV_OPT_SEARCH_CHILDREN); - r = av_opt_set_int(_swsContext, "dsth", _avCodecParameters[_avStream]->height, AV_OPT_SEARCH_CHILDREN); - r = av_opt_set_int(_swsContext, "dst_format", _avOutputPixelFormat, AV_OPT_SEARCH_CHILDREN); - r = av_opt_set_int(_swsContext, "sws_flags", SWS_FAST_BILINEAR, AV_OPT_SEARCH_CHILDREN); - r = av_opt_set_int(_swsContext, "threads", 0, AV_OPT_SEARCH_CHILDREN); - r = sws_init_context(_swsContext, nullptr, nullptr); - if (r < 0) + for (auto i : _avCodecContext) { - throw std::runtime_error("Cannot initialize sws context"); + avcodec_close(i.second); + avcodec_free_context(&i.second); } - - const int* inTable = nullptr; - int inFull = 0; - const int* outTable = nullptr; - int outFull = 0; - int brightness = 0; - int contrast = 0; - int saturation = 0; - r = sws_getColorspaceDetails( - _swsContext, - (int**)&inTable, - &inFull, - (int**)&outTable, - &outFull, - &brightness, - &contrast, - &saturation); - - AVColorSpace colorSpace = _avCodecParameters[_avStream]->color_space; - if (AVCOL_SPC_UNSPECIFIED == colorSpace) + for (auto i : _avCodecParameters) + { + avcodec_parameters_free(&i.second); + } + if (_avIOContext) { - colorSpace = AVCOL_SPC_BT709; + avio_context_free(&_avIOContext); + } + //! \bug Free'd by avio_context_free()? + //if (_avIOContextBuffer) + //{ + // av_free(_avIOContextBuffer); + //} + if (_avFormatContext) + { + avformat_close_input(&_avFormatContext); } - inFull = 1; - outFull = 1; - - r = sws_setColorspaceDetails( - _swsContext, - sws_getCoefficients(colorSpace), - inFull, - sws_getCoefficients(AVCOL_SPC_BT709), - outFull, - brightness, - contrast, - saturation); } - } - FFmpegRead::~FFmpegRead() - { - if (_swsContext) + const OIIO::ImageSpec& Read::getSpec() { - sws_freeContext(_swsContext); + return _spec; } - if (_avFrame2) - { - av_frame_free(&_avFrame2); - } - if (_avFrame) - { - av_frame_free(&_avFrame); - } - for (auto i : _avCodecContext) - { - avcodec_close(i.second); - avcodec_free_context(&i.second); - } - for (auto i : _avCodecParameters) - { - avcodec_parameters_free(&i.second); - } - if (_avIOContext) - { - avio_context_free(&_avIOContext); - } - //! \bug Free'd by avio_context_free()? - //if (_avIOContextBuffer) - //{ - // av_free(_avIOContextBuffer); - //} - if (_avFormatContext) - { - avformat_close_input(&_avFormatContext); - } - } - const OIIO::ImageSpec& FFmpegRead::getSpec() - { - return _spec; - } - - const OTIO_NS::TimeRange& FFmpegRead::getTimeRange() const - { - return _timeRange; - } - - OIIO::ImageBuf FFmpegRead::getImage(const OTIO_NS::RationalTime& time) - { - if (time != _currentTime) + const OTIO_NS::TimeRange& Read::getTimeRange() const { - _seek(time); + return _timeRange; } - return _read(); - } - void FFmpegRead::_seek(const OTIO_NS::RationalTime& time) - { - if (_avStream != -1) + OIIO::ImageBuf Read::getImage(const OTIO_NS::RationalTime& time) { - avcodec_flush_buffers(_avCodecContext[_avStream]); - if (av_seek_frame( - _avFormatContext, - _avStream, - av_rescale_q( - time.value() - _timeRange.start_time().value(), - swap(_avSpeed), - _avFormatContext->streams[_avStream]->time_base), - AVSEEK_FLAG_BACKWARD) < 0) + if (time != _currentTime) { - //! \todo How should this be handled? + _seek(time); } - _currentTime = time; + return _read(); } - _eof = false; - } - OIIO::ImageBuf FFmpegRead::_read() - { - OIIO::ImageBuf out; - if (_avStream != -1) + void Read::_seek(const OTIO_NS::RationalTime& time) { - Packet packet; - int decoding = 0; - while (0 == decoding) + if (_avStream != -1) { - if (!_eof) + avcodec_flush_buffers(_avCodecContext[_avStream]); + if (av_seek_frame( + _avFormatContext, + _avStream, + av_rescale_q( + time.value() - _timeRange.start_time().value(), + swap(_avSpeed), + _avFormatContext->streams[_avStream]->time_base), + AVSEEK_FLAG_BACKWARD) < 0) { - decoding = av_read_frame(_avFormatContext, packet.p); - if (AVERROR_EOF == decoding) - { - _eof = true; - decoding = 0; - } - else if (decoding < 0) - { - //! \todo How should this be handled? - break; - } + //! \todo How should this be handled? } - if ((_eof && _avStream != -1) || (_avStream == packet.p->stream_index)) + _currentTime = time; + } + _eof = false; + } + + OIIO::ImageBuf Read::_read() + { + OIIO::ImageBuf out; + if (_avStream != -1) + { + Packet packet; + int decoding = 0; + while (0 == decoding) { - decoding = avcodec_send_packet( - _avCodecContext[_avStream], - _eof ? nullptr : packet.p); - if (AVERROR_EOF == decoding) + if (!_eof) { - decoding = 0; - } - else if (decoding < 0) - { - //! \todo How should this be handled? - break; + decoding = av_read_frame(_avFormatContext, packet.p); + if (AVERROR_EOF == decoding) + { + _eof = true; + decoding = 0; + } + else if (decoding < 0) + { + //! \todo How should this be handled? + break; + } } - - while (0 == decoding) + if ((_eof && _avStream != -1) || (_avStream == packet.p->stream_index)) { - decoding = avcodec_receive_frame(_avCodecContext[_avStream], _avFrame); - if (decoding < 0) + decoding = avcodec_send_packet( + _avCodecContext[_avStream], + _eof ? nullptr : packet.p); + if (AVERROR_EOF == decoding) + { + decoding = 0; + } + else if (decoding < 0) { + //! \todo How should this be handled? break; } - const int64_t timestamp = _avFrame->pts != AV_NOPTS_VALUE ? _avFrame->pts : _avFrame->pkt_dts; - const OTIO_NS::RationalTime frameTime( - _timeRange.start_time().value() + - av_rescale_q( - timestamp, - _avFormatContext->streams[_avStream]->time_base, - swap(_avFormatContext->streams[_avStream]->r_frame_rate)), - _timeRange.duration().rate()); - - if (frameTime >= _currentTime) + while (0 == decoding) { - out = OIIO::ImageBuf(_spec); - - av_image_fill_arrays( - _avFrame2->data, - _avFrame2->linesize, - (const uint8_t*)out.localpixels(), - _avOutputPixelFormat, - _spec.width, - _spec.height, - 1); - sws_scale_frame(_swsContext, _avFrame2, _avFrame); - - _currentTime += OTIO_NS::RationalTime(1.0, _timeRange.duration().rate()); + decoding = avcodec_receive_frame(_avCodecContext[_avStream], _avFrame); + if (decoding < 0) + { + break; + } + const int64_t timestamp = _avFrame->pts != AV_NOPTS_VALUE ? _avFrame->pts : _avFrame->pkt_dts; + + const OTIO_NS::RationalTime frameTime( + _timeRange.start_time().value() + + av_rescale_q( + timestamp, + _avFormatContext->streams[_avStream]->time_base, + swap(_avFormatContext->streams[_avStream]->r_frame_rate)), + _timeRange.duration().rate()); + + if (frameTime >= _currentTime) + { + out = OIIO::ImageBuf(_spec); + + av_image_fill_arrays( + _avFrame2->data, + _avFrame2->linesize, + (const uint8_t*)out.localpixels(), + _avOutputPixelFormat, + _spec.width, + _spec.height, + 1); + sws_scale_frame(_swsContext, _avFrame2, _avFrame); + + _currentTime += OTIO_NS::RationalTime(1.0, _timeRange.duration().rate()); + + decoding = 1; + break; + } + } - decoding = 1; + if (AVERROR(EAGAIN) == decoding) + { + decoding = 0; + } + else if (AVERROR_EOF == decoding) + { + break; + } + else if (decoding < 0) + { + //! \todo How should this be handled? + break; + } + else if (1 == decoding) + { break; } } - - if (AVERROR(EAGAIN) == decoding) - { - decoding = 0; - } - else if (AVERROR_EOF == decoding) - { - break; - } - else if (decoding < 0) - { - //! \todo How should this be handled? - break; - } - else if (1 == decoding) + if (packet.p->buf) { - break; + av_packet_unref(packet.p); } } if (packet.p->buf) @@ -604,56 +576,54 @@ namespace toucan av_packet_unref(packet.p); } } - if (packet.p->buf) - { - av_packet_unref(packet.p); - } + return out; } - return out; - } - - FFmpegRead::AVIOBufferData::AVIOBufferData() - {} - FFmpegRead::AVIOBufferData::AVIOBufferData(const uint8_t* data, size_t size) : - data(data), - size(size) - {} + Read::AVIOBufferData::AVIOBufferData() + { + } - int FFmpegRead::_avIOBufferRead(void* opaque, uint8_t* buf, int bufSize) - { - AVIOBufferData* bufferData = static_cast(opaque); - - const int64_t remaining = bufferData->size - bufferData->offset; - int bufSizeClamped = std::min(std::max( - static_cast(bufSize), - static_cast(0)), - remaining); - if (!bufSizeClamped) + Read::AVIOBufferData::AVIOBufferData(const uint8_t* data, size_t size) : + data(data), + size(size) { - return AVERROR_EOF; } - memcpy(buf, bufferData->data + bufferData->offset, bufSizeClamped); - bufferData->offset += bufSizeClamped; + int Read::_avIOBufferRead(void* opaque, uint8_t* buf, int bufSize) + { + AVIOBufferData* bufferData = static_cast(opaque); + + const int64_t remaining = bufferData->size - bufferData->offset; + int bufSizeClamped = std::min(std::max( + static_cast(bufSize), + static_cast(0)), + remaining); + if (!bufSizeClamped) + { + return AVERROR_EOF; + } - return bufSizeClamped; - } + memcpy(buf, bufferData->data + bufferData->offset, bufSizeClamped); + bufferData->offset += bufSizeClamped; - int64_t FFmpegRead::_avIOBufferSeek(void* opaque, int64_t offset, int whence) - { - AVIOBufferData* bufferData = static_cast(opaque); + return bufSizeClamped; + } - if (whence & AVSEEK_SIZE) + int64_t Read::_avIOBufferSeek(void* opaque, int64_t offset, int whence) { - return bufferData->size; - } + AVIOBufferData* bufferData = static_cast(opaque); + + if (whence & AVSEEK_SIZE) + { + return bufferData->size; + } - bufferData->offset = std::min(std::max( - offset, - static_cast(0)), - static_cast(bufferData->size)); + bufferData->offset = std::min(std::max( + offset, + static_cast(0)), + static_cast(bufferData->size)); - return offset; + return offset; + } } } diff --git a/lib/toucan/FFmpegRead.h b/lib/toucan/FFmpegRead.h index c7b9896..549c792 100644 --- a/lib/toucan/FFmpegRead.h +++ b/lib/toucan/FFmpegRead.h @@ -3,9 +3,11 @@ #pragma once -#include "MemoryMap.h" +#include -#include +#include + +#include #include @@ -13,7 +15,6 @@ extern "C" { #include #include -#include #include } // extern "C" @@ -22,55 +23,58 @@ extern "C" namespace toucan { - class FFmpegRead : public std::enable_shared_from_this + namespace ffmpeg { - public: - FFmpegRead( - const std::filesystem::path&, - const MemoryReference& = {}); - - virtual ~FFmpegRead(); - - const OIIO::ImageSpec& getSpec(); - const OTIO_NS::TimeRange& getTimeRange() const; - - OIIO::ImageBuf getImage(const OTIO_NS::RationalTime&); - - private: - void _seek(const OTIO_NS::RationalTime&); - OIIO::ImageBuf _read(); - - std::filesystem::path _path; - MemoryReference _memoryReference; - OIIO::ImageSpec _spec; - OTIO_NS::TimeRange _timeRange; - OTIO_NS::RationalTime _currentTime; - - struct AVIOBufferData + class Read : public std::enable_shared_from_this { - AVIOBufferData(); - AVIOBufferData(const uint8_t*, size_t size); - - const uint8_t* data = nullptr; - size_t size = 0; - size_t offset = 0; + public: + Read( + const std::filesystem::path&, + const MemoryReference& = {}); + + virtual ~Read(); + + const OIIO::ImageSpec& getSpec(); + const OTIO_NS::TimeRange& getTimeRange() const; + + OIIO::ImageBuf getImage(const OTIO_NS::RationalTime&); + + private: + void _seek(const OTIO_NS::RationalTime&); + OIIO::ImageBuf _read(); + + std::filesystem::path _path; + MemoryReference _memoryReference; + OIIO::ImageSpec _spec; + OTIO_NS::TimeRange _timeRange; + OTIO_NS::RationalTime _currentTime; + + struct AVIOBufferData + { + AVIOBufferData(); + AVIOBufferData(const uint8_t*, size_t size); + + const uint8_t* data = nullptr; + size_t size = 0; + size_t offset = 0; + }; + static int _avIOBufferRead(void* opaque, uint8_t* buf, int bufSize); + static int64_t _avIOBufferSeek(void* opaque, int64_t offset, int whence); + + AVFormatContext* _avFormatContext = nullptr; + AVIOBufferData _avIOBufferData; + uint8_t* _avIOContextBuffer = nullptr; + AVIOContext* _avIOContext = nullptr; + AVRational _avSpeed = { 24, 1 }; + int _avStream = -1; + std::map _avCodecParameters; + std::map _avCodecContext; + AVFrame* _avFrame = nullptr; + AVFrame* _avFrame2 = nullptr; + AVPixelFormat _avInputPixelFormat = AV_PIX_FMT_NONE; + AVPixelFormat _avOutputPixelFormat = AV_PIX_FMT_NONE; + SwsContext* _swsContext = nullptr; + bool _eof = false; }; - static int _avIOBufferRead(void* opaque, uint8_t* buf, int bufSize); - static int64_t _avIOBufferSeek(void* opaque, int64_t offset, int whence); - - AVFormatContext* _avFormatContext = nullptr; - AVIOBufferData _avIOBufferData; - uint8_t* _avIOContextBuffer = nullptr; - AVIOContext* _avIOContext = nullptr; - AVRational _avSpeed = { 24, 1 }; - int _avStream = -1; - std::map _avCodecParameters; - std::map _avCodecContext; - AVFrame* _avFrame = nullptr; - AVFrame* _avFrame2 = nullptr; - AVPixelFormat _avInputPixelFormat = AV_PIX_FMT_NONE; - AVPixelFormat _avOutputPixelFormat = AV_PIX_FMT_NONE; - SwsContext* _swsContext = nullptr; - bool _eof = false; - }; + } } diff --git a/lib/toucan/FFmpegWrite.cpp b/lib/toucan/FFmpegWrite.cpp new file mode 100644 index 0000000..a86e9a7 --- /dev/null +++ b/lib/toucan/FFmpegWrite.cpp @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#include "FFmpegWrite.h" + +#include "Util.h" + +#include +#include + +extern "C" +{ +#include +#include +} + +namespace toucan +{ + namespace ffmpeg + { + Write::Write( + const std::filesystem::path& path, + const OIIO::ImageSpec& spec, + double rate) : + _path(path), + _spec(spec), + _rate(rate) + { + av_log_set_level(AV_LOG_QUIET); + //av_log_set_level(AV_LOG_VERBOSE); + //av_log_set_callback(log);AVCodecID avCodecID = AV_CODEC_ID_MPEG4; + + AVCodecID avCodecID = AV_CODEC_ID_MJPEG; + int avProfile = AV_PROFILE_UNKNOWN; + + int r = avformat_alloc_output_context2(&_avFormatContext, NULL, NULL, _path.string().c_str()); + if (r < 0) + { + throw std::runtime_error(getErrorLabel(r)); + } + const AVCodec* avCodec = avcodec_find_encoder(avCodecID); + if (!avCodec) + { + throw std::runtime_error("Cannot find encoder"); + } + _avCodecContext = avcodec_alloc_context3(avCodec); + if (!_avCodecContext) + { + throw std::runtime_error("Cannot allocate context"); + } + _avVideoStream = avformat_new_stream(_avFormatContext, avCodec); + if (!_avVideoStream) + { + throw std::runtime_error("Cannot allocate stream"); + } + if (!avCodec->pix_fmts) + { + throw std::runtime_error("No pixel formats available"); + } + + _avCodecContext->codec_id = avCodec->id; + _avCodecContext->codec_type = AVMEDIA_TYPE_VIDEO; + _avCodecContext->width = spec.width; + _avCodecContext->height = spec.height; + _avCodecContext->sample_aspect_ratio = AVRational({ 1, 1 }); + _avCodecContext->pix_fmt = avCodec->pix_fmts[0]; + const auto rational = toRational(rate); + _avCodecContext->time_base = { rational.second, rational.first }; + _avCodecContext->framerate = { rational.first, rational.second }; + _avCodecContext->profile = avProfile; + if (_avFormatContext->oformat->flags & AVFMT_GLOBALHEADER) + { + _avCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } + _avCodecContext->thread_count = 0; + _avCodecContext->thread_type = FF_THREAD_FRAME; + + r = avcodec_open2(_avCodecContext, avCodec, NULL); + if (r < 0) + { + throw std::runtime_error(getErrorLabel(r)); + } + + r = avcodec_parameters_from_context(_avVideoStream->codecpar, _avCodecContext); + if (r < 0) + { + throw std::runtime_error(getErrorLabel(r)); + } + + _avVideoStream->time_base = { rational.second, rational.first }; + _avVideoStream->avg_frame_rate = { rational.first, rational.second }; + + //av_dump_format(_avFormatContext, 0, _path.string().c_str(), 1); + + r = avio_open(&_avFormatContext->pb, _path.string().c_str(), AVIO_FLAG_WRITE); + if (r < 0) + { + throw std::runtime_error(getErrorLabel(r)); + } + + r = avformat_write_header(_avFormatContext, NULL); + if (r < 0) + { + throw std::runtime_error(getErrorLabel(r)); + } + + _avPacket = av_packet_alloc(); + if (!_avPacket) + { + throw std::runtime_error("Cannot allocate packet"); + } + + _avFrame = av_frame_alloc(); + if (!_avFrame) + { + throw std::runtime_error("Cannot allocate frame"); + } + _avFrame->format = _avVideoStream->codecpar->format; + _avFrame->width = _avVideoStream->codecpar->width; + _avFrame->height = _avVideoStream->codecpar->height; + r = av_frame_get_buffer(_avFrame, 0); + if (r < 0) + { + throw std::runtime_error(getErrorLabel(r)); + } + + _avFrame2 = av_frame_alloc(); + if (!_avFrame2) + { + throw std::runtime_error("Cannot allocate frame"); + } + + _opened = true; + } + + Write::~Write() + { + if (_opened) + { + _encodeVideo(nullptr); + av_write_trailer(_avFormatContext); + } + if (_swsContext) + { + sws_freeContext(_swsContext); + } + if (_avFrame2) + { + av_frame_free(&_avFrame2); + } + if (_avFrame) + { + av_frame_free(&_avFrame); + } + if (_avPacket) + { + av_packet_free(&_avPacket); + } + if (_avCodecContext) + { + avcodec_free_context(&_avCodecContext); + } + if (_avFormatContext && _avFormatContext->pb) + { + avio_closep(&_avFormatContext->pb); + } + if (_avFormatContext) + { + avformat_free_context(_avFormatContext); + } + } + + void Write::writeImage(const OIIO::ImageBuf& buf, const OTIO_NS::RationalTime& time) + { + const auto& spec = buf.spec(); + AVPixelFormat avPixelFormatIn = AV_PIX_FMT_NONE; + switch (spec.nchannels) + { + case 1: + switch (spec.format.basetype) + { + case OIIO::TypeDesc::UINT8: avPixelFormatIn = AV_PIX_FMT_GRAY8; break; + case OIIO::TypeDesc::UINT16: avPixelFormatIn = AV_PIX_FMT_GRAY16; break; + default: break; + } + break; + case 3: + switch (spec.format.basetype) + { + case OIIO::TypeDesc::UINT8: avPixelFormatIn = AV_PIX_FMT_RGB24; break; + case OIIO::TypeDesc::UINT16: avPixelFormatIn = AV_PIX_FMT_RGB48; break; + default: break; + } + break; + case 4: + switch (spec.format.basetype) + { + case OIIO::TypeDesc::UINT8: avPixelFormatIn = AV_PIX_FMT_RGBA; break; + case OIIO::TypeDesc::UINT16: avPixelFormatIn = AV_PIX_FMT_RGBA64; break; + default: break; + } + break; + default: break; + } + if (AV_PIX_FMT_NONE == avPixelFormatIn) + { + throw std::runtime_error("Incompatible pixel type"); + } + if (spec.width != _spec.width || + spec.height != _spec.height || + avPixelFormatIn != _avPixelFormatIn) + { + _avPixelFormatIn = avPixelFormatIn; + if (_swsContext) + { + sws_freeContext(_swsContext); + } + /*_swsContext = sws_getContext( + spec.width, + spec.height, + _avPixelFormatIn, + spec.width, + spec.height, + _avCodecContext->pix_fmt, + swsScaleFlags, + 0, + 0, + 0);*/ + _swsContext = sws_alloc_context(); + if (!_swsContext) + { + throw std::runtime_error("Cannot allocate context"); + } + av_opt_set_defaults(_swsContext); + int r = av_opt_set_int(_swsContext, "srcw", spec.width, AV_OPT_SEARCH_CHILDREN); + r = av_opt_set_int(_swsContext, "srch", spec.height, AV_OPT_SEARCH_CHILDREN); + r = av_opt_set_int(_swsContext, "src_format", _avPixelFormatIn, AV_OPT_SEARCH_CHILDREN); + r = av_opt_set_int(_swsContext, "dstw", spec.width, AV_OPT_SEARCH_CHILDREN); + r = av_opt_set_int(_swsContext, "dsth", spec.height, AV_OPT_SEARCH_CHILDREN); + r = av_opt_set_int(_swsContext, "dst_format", _avCodecContext->pix_fmt, AV_OPT_SEARCH_CHILDREN); + r = av_opt_set_int(_swsContext, "sws_flags", SWS_BICUBIC, AV_OPT_SEARCH_CHILDREN); + r = av_opt_set_int(_swsContext, "threads", 0, AV_OPT_SEARCH_CHILDREN); + r = sws_init_context(_swsContext, nullptr, nullptr); + if (r < 0) + { + throw std::runtime_error("Cannot initialize sws context"); + } + } + + av_image_fill_arrays( + _avFrame2->data, + _avFrame2->linesize, + reinterpret_cast(buf.localpixels()), + _avPixelFormatIn, + spec.width, + spec.height, + 1); + + sws_scale( + _swsContext, + (uint8_t const* const*)_avFrame2->data, + _avFrame2->linesize, + 0, + _spec.height, + _avFrame->data, + _avFrame->linesize); + + const auto timeRational = toRational(time.rate()); + _avFrame->pts = av_rescale_q( + time.value(), + { timeRational.second, timeRational.first }, + _avVideoStream->time_base); + _encodeVideo(_avFrame); + } + + void Write::_encodeVideo(AVFrame* frame) + { + int r = avcodec_send_frame(_avCodecContext, frame); + if (r < 0) + { + throw std::runtime_error("Cannot write frame"); + } + + while (r >= 0) + { + r = avcodec_receive_packet(_avCodecContext, _avPacket); + if (r == AVERROR(EAGAIN) || r == AVERROR_EOF) + { + return; + } + else if (r < 0) + { + throw std::runtime_error("Cannot write frame"); + } + r = av_interleaved_write_frame(_avFormatContext, _avPacket); + if (r < 0) + { + throw std::runtime_error("Cannot write frame"); + } + av_packet_unref(_avPacket); + } + } + } +} diff --git a/lib/toucan/FFmpegWrite.h b/lib/toucan/FFmpegWrite.h new file mode 100644 index 0000000..544dd64 --- /dev/null +++ b/lib/toucan/FFmpegWrite.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#pragma once + +#include + +#include + +#include + +extern "C" +{ +#include +#include +#include + +} // extern "C" + +#include + +namespace toucan +{ + namespace ffmpeg + { + class Write : public std::enable_shared_from_this + { + public: + Write( + const std::filesystem::path&, + const OIIO::ImageSpec&, + double rate); + + virtual ~Write(); + + void writeImage(const OIIO::ImageBuf&, const OTIO_NS::RationalTime&); + + private: + void _encodeVideo(AVFrame*); + + std::filesystem::path _path; + OIIO::ImageSpec _spec; + double _rate = 0.0; + AVFormatContext* _avFormatContext = nullptr; + AVCodecContext* _avCodecContext = nullptr; + AVStream* _avVideoStream = nullptr; + AVPacket* _avPacket = nullptr; + AVFrame* _avFrame = nullptr; + AVPixelFormat _avPixelFormatIn = AV_PIX_FMT_NONE; + AVFrame* _avFrame2 = nullptr; + SwsContext* _swsContext = nullptr; + bool _opened = false; + }; + } +} diff --git a/lib/toucan/Read.cpp b/lib/toucan/Read.cpp index 2c55e78..440b73b 100644 --- a/lib/toucan/Read.cpp +++ b/lib/toucan/Read.cpp @@ -22,7 +22,7 @@ namespace toucan // Open the file ane get information. try { - _ffRead = std::make_unique(path, memoryReference); + _ffRead = std::make_unique(path, memoryReference); _spec = _ffRead->getSpec(); _timeRange = _ffRead->getTimeRange(); } diff --git a/lib/toucan/Read.h b/lib/toucan/Read.h index f305277..b61ea98 100644 --- a/lib/toucan/Read.h +++ b/lib/toucan/Read.h @@ -35,7 +35,7 @@ namespace toucan private: std::filesystem::path _path; - std::unique_ptr _ffRead; + std::unique_ptr _ffRead; std::shared_ptr _memoryReader; std::unique_ptr _input; OIIO::ImageSpec _spec; diff --git a/lib/toucanView/ExportTool.cpp b/lib/toucanView/ExportTool.cpp index 5d9450a..d4ec44d 100644 --- a/lib/toucanView/ExportTool.cpp +++ b/lib/toucanView/ExportTool.cpp @@ -23,48 +23,88 @@ namespace toucan IWidget::_init(context, "toucan::ExportWidget", parent); _host = app->getHost(); - _formats = + _imageExtensions = { ".exr", ".tiff", ".png" }; + _movieCodecs = + { + "mjpeg" + }; _layout = dtk::VerticalLayout::create(context, shared_from_this()); - _layout->setMarginRole(dtk::SizeRole::MarginSmall); - _layout->setSpacingRole(dtk::SizeRole::SpacingSmall); + _layout->setSpacingRole(dtk::SizeRole::None); + + _outputLayout = dtk::VerticalLayout::create(context, _layout); + _outputLayout->setMarginRole(dtk::SizeRole::Margin); + auto label = dtk::Label::create(context, "Output directory:", _outputLayout); + _outputPathEdit = dtk::FileEdit::create(context, _outputLayout); + + auto divider = dtk::Divider::create(context, dtk::Orientation::Vertical, _layout); + + _tabWidget = dtk::TabWidget::create(context, _layout); - 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); + _imageLayout = dtk::VerticalLayout::create(context); + _imageLayout->setMarginRole(dtk::SizeRole::Margin); + _tabWidget->addTab("Images", _imageLayout); - auto gridLayout = dtk::GridLayout::create(context, _layout); - gridLayout->setSpacingRole(dtk::SizeRole::SpacingSmall); + auto gridLayout = dtk::GridLayout::create(context, _imageLayout); 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); + _imageBaseNameEdit = dtk::LineEdit::create(context, gridLayout); + _imageBaseNameEdit->setText("render."); + gridLayout->setGridPos(_imageBaseNameEdit, 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); + _imagePaddingEdit = dtk::IntEdit::create(context, gridLayout); + _imagePaddingEdit->setRange(dtk::RangeI(0, 9)); + gridLayout->setGridPos(_imagePaddingEdit, 1, 1); - label = dtk::Label::create(context, "File format:", gridLayout); + label = dtk::Label::create(context, "Extension:", gridLayout); gridLayout->setGridPos(label, 2, 0); - _formatComboBox = dtk::ComboBox::create(context, _formats, gridLayout); - gridLayout->setGridPos(_formatComboBox, 2, 1); + _imageExtensionComboBox = dtk::ComboBox::create(context, _imageExtensions, gridLayout); + gridLayout->setGridPos(_imageExtensionComboBox, 2, 1); - auto divider = dtk::Divider::create(context, dtk::Orientation::Vertical, _layout); + divider = dtk::Divider::create(context, dtk::Orientation::Vertical, _imageLayout); auto exportSequenceButton = dtk::PushButton::create( context, "Export Sequence", - _layout); + _imageLayout); + + auto exportCurrentButton = dtk::PushButton::create( + context, + "Export Current Frame", + _imageLayout); + + _movieLayout = dtk::VerticalLayout::create(context); + _movieLayout->setMarginRole(dtk::SizeRole::Margin); + _tabWidget->addTab("Movie", _movieLayout); + + gridLayout = dtk::GridLayout::create(context, _movieLayout); + + label = dtk::Label::create(context, "Base name:", gridLayout); + gridLayout->setGridPos(label, 0, 0); + _movieBaseNameEdit = dtk::LineEdit::create(context, gridLayout); + _movieBaseNameEdit->setText("render"); + gridLayout->setGridPos(_movieBaseNameEdit, 0, 1); + + label = dtk::Label::create(context, "Codec:", gridLayout); + gridLayout->setGridPos(label, 1, 0); + _movieCodecComboBox = dtk::ComboBox::create(context, _movieCodecs, gridLayout); + gridLayout->setGridPos(_movieCodecComboBox, 1, 1); + + divider = dtk::Divider::create(context, dtk::Orientation::Vertical, _movieLayout); + + auto exportMovieButton = dtk::PushButton::create( + context, + "Export Movie", + _movieLayout); + exportSequenceButton->setClickedCallback( [this] { @@ -72,10 +112,6 @@ namespace toucan _export(); }); - auto exportCurrentButton = dtk::PushButton::create( - context, - "Export Current Frame", - _layout); exportCurrentButton->setClickedCallback( [this] { @@ -85,6 +121,22 @@ namespace toucan _export(); }); + exportMovieButton->setClickedCallback( + [this] + { + _timeRange = _file->getPlaybackModel()->getInOutRange(); + const std::string extension = ".mov"; + const std::filesystem::path path = + _outputPathEdit->getPath() / + (_movieBaseNameEdit->getText() + extension); + const IMATH_NAMESPACE::V2d imageSize = _graph->getImageSize(); + _ffWrite = std::make_shared( + path, + OIIO::ImageSpec(imageSize.x, imageSize.y, 3), + _timeRange.duration().rate()); + _export(); + }); + _timer = dtk::Timer::create(context); _timer->setRepeating(true); @@ -105,7 +157,9 @@ namespace toucan _time = OTIO_NS::RationalTime(); _graph.reset(); } - setEnabled(_file.get()); + _outputLayout->setEnabled(_file.get()); + _imageLayout->setEnabled(_file.get()); + _movieLayout->setEnabled(_file.get()); }); } @@ -147,6 +201,7 @@ namespace toucan [this] { _timer->stop(); + _ffWrite.reset(); _dialog.reset(); }); _dialog->show(); @@ -158,13 +213,20 @@ namespace toucan 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); + if (_ffWrite) + { + _ffWrite->writeImage(buf, _time); + } + else + { + const std::string fileName = getSequenceFrame( + _outputPathEdit->getPath().string(), + _imageBaseNameEdit->getText(), + _time.to_frames(), + _imagePaddingEdit->getValue(), + _imageExtensions[_imageExtensionComboBox->getCurrentIndex()]); + buf.write(fileName); + } } const OTIO_NS::RationalTime end = _timeRange.end_time_inclusive(); diff --git a/lib/toucanView/ExportTool.h b/lib/toucanView/ExportTool.h index aa69d5e..f84bf26 100644 --- a/lib/toucanView/ExportTool.h +++ b/lib/toucanView/ExportTool.h @@ -5,6 +5,7 @@ #include "IToolWidget.h" +#include #include #include @@ -16,6 +17,7 @@ #include #include #include +#include #include namespace toucan @@ -51,14 +53,23 @@ namespace toucan OTIO_NS::TimeRange _timeRange; OTIO_NS::RationalTime _time; std::shared_ptr _graph; - std::vector _formats; + std::vector _imageExtensions; + std::vector _movieCodecs; + std::shared_ptr _ffWrite; std::shared_ptr _layout; + std::shared_ptr _outputLayout; std::shared_ptr _outputPathEdit; - std::shared_ptr _baseNameEdit; - std::shared_ptr _paddingEdit; - std::shared_ptr _formatComboBox; + std::shared_ptr _tabWidget; + std::shared_ptr _imageLayout; + std::shared_ptr _imageBaseNameEdit; + std::shared_ptr _imagePaddingEdit; + std::shared_ptr _imageExtensionComboBox; + std::shared_ptr _movieLayout; + std::shared_ptr _movieBaseNameEdit; + std::shared_ptr _movieCodecComboBox; std::shared_ptr _dialog; + std::shared_ptr _timer; std::shared_ptr > > _fileObserver; From 248c191272e923f5a9220d2c66d6eab07e1982f5 Mon Sep 17 00:00:00 2001 From: Darby Johnston Date: Tue, 3 Dec 2024 18:52:16 -0800 Subject: [PATCH 04/10] Refatoring Signed-off-by: Darby Johnston --- bin/CMakeLists.txt | 1 + bin/toucan-filmstrip/App.cpp | 195 ++++++++++++++++++ bin/toucan-filmstrip/App.h | 51 +++++ bin/toucan-filmstrip/CMakeLists.txt | 21 ++ bin/toucan-filmstrip/main.cpp | 28 +++ bin/toucan-render/App.cpp | 113 ++-------- bin/toucan-render/App.h | 5 +- bin/toucan-render/CMakeLists.txt | 9 +- bin/toucan-render/Util.cpp | 38 ---- bin/toucan-render/Util.h | 17 -- lib/toucan/CMakeLists.txt | 3 + {bin/toucan-render => lib/toucan}/CmdLine.cpp | 0 {bin/toucan-render => lib/toucan}/CmdLine.h | 0 .../toucan}/CmdLineInline.h | 0 lib/toucan/Util.cpp | 30 +++ lib/toucan/Util.h | 6 + lib/toucanView/ExportTool.cpp | 4 +- 17 files changed, 355 insertions(+), 166 deletions(-) create mode 100644 bin/toucan-filmstrip/App.cpp create mode 100644 bin/toucan-filmstrip/App.h create mode 100644 bin/toucan-filmstrip/CMakeLists.txt create mode 100644 bin/toucan-filmstrip/main.cpp delete mode 100644 bin/toucan-render/Util.cpp delete mode 100644 bin/toucan-render/Util.h rename {bin/toucan-render => lib/toucan}/CmdLine.cpp (100%) rename {bin/toucan-render => lib/toucan}/CmdLine.h (100%) rename {bin/toucan-render => lib/toucan}/CmdLineInline.h (100%) diff --git a/bin/CMakeLists.txt b/bin/CMakeLists.txt index 588f766..6df3cac 100644 --- a/bin/CMakeLists.txt +++ b/bin/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(toucan-render) +add_subdirectory(toucan-filmstrip) if(toucan_VIEW) add_subdirectory(toucan-view) endif() diff --git a/bin/toucan-filmstrip/App.cpp b/bin/toucan-filmstrip/App.cpp new file mode 100644 index 0000000..dbe1419 --- /dev/null +++ b/bin/toucan-filmstrip/App.cpp @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#include "App.h" + +#include + +#include + +namespace toucan +{ + App::App(std::vector& argv) + { + _exe = argv.front(); + argv.erase(argv.begin()); + + _args.list.push_back(std::make_shared >( + _args.input, + "input", + "Input .otio file.")); + auto outArg = std::make_shared >( + _args.output, + "output", + "Output image file."); + _args.list.push_back(outArg); + + _options.list.push_back(std::make_shared( + _options.verbose, + std::vector{ "-v" }, + "Print verbose output.")); + _options.list.push_back(std::make_shared( + _options.help, + std::vector{ "-h" }, + "Print help.")); + + if (!argv.empty()) + { + for (const auto& option : _options.list) + { + option->parse(argv); + } + if (!_options.help) + { + for (const auto& arg : _args.list) + { + arg->parse(argv); + } + if (argv.size()) + { + _options.help = true; + } + } + } + else + { + _options.help = true; + } + } + + App::~App() + {} + + int App::run() + { + if (_options.help) + { + _printHelp(); + return 1; + } + + const std::filesystem::path parentPath = std::filesystem::path(_exe).parent_path(); + const std::filesystem::path inputPath(_args.input); + const std::filesystem::path outputPath(_args.output); + const auto outputSplit = splitFileNameNumber(outputPath.stem().string()); + const int outputStartFrame = atoi(outputSplit.second.c_str()); + const size_t outputNumberPadding = getNumberPadding(outputSplit.second); + + // Open the timeline. + _timelineWrapper = std::make_shared(inputPath); + + // Get time values. + const OTIO_NS::TimeRange& timeRange = _timelineWrapper->getTimeRange(); + const OTIO_NS::RationalTime timeInc(1.0, timeRange.duration().rate()); + const int frames = timeRange.duration().value(); + + // Create the image graph. + std::shared_ptr log; + if (_options.verbose) + { + log = std::make_shared(); + } + ImageGraphOptions imageGraphOptions; + imageGraphOptions.log = log; + _graph = std::make_shared( + inputPath.parent_path(), + _timelineWrapper, + imageGraphOptions); + const IMATH_NAMESPACE::V2d imageSize = _graph->getImageSize(); + + // Create the image host. + std::vector searchPath; + searchPath.push_back(parentPath); +#if defined(_WINDOWS) + searchPath.push_back(parentPath / ".." / ".." / ".."); +#else // _WINDOWS + searchPath.push_back(parentPath / ".." / ".."); +#endif // _WINDOWS + ImageEffectHostOptions imageHostOptions; + imageHostOptions.log = log; + _host = std::make_shared( + searchPath, + imageHostOptions); + + // Initialize the filmstrip. + OIIO::ImageBuf filmstripBuf; + const int thumbnailWidth = 360; + const int thumbnailSpacing = 0; + IMATH_NAMESPACE::V2d thumbnailSize; + if (imageSize.x > 0 && imageSize.y > 0) + { + thumbnailSize = IMATH_NAMESPACE::V2d( + thumbnailWidth, + thumbnailWidth / static_cast(imageSize.x / static_cast(imageSize.y))); + const IMATH_NAMESPACE::V2d filmstripSize( + thumbnailSize.x * frames + thumbnailSpacing * (frames - 1), + thumbnailSize.y); + filmstripBuf = OIIO::ImageBufAlgo::fill( + { 0.F, 0.F, 0.F, 0.F }, + OIIO::ROI(0, filmstripSize.x, 0, filmstripSize.y, 0, 1, 0, 4)); + } + + // Render the timeline frames. + int filmstripX = 0; + for (OTIO_NS::RationalTime time = timeRange.start_time(); + time <= timeRange.end_time_inclusive(); + time += timeInc) + { + std::cout << (time - timeRange.start_time()).value() << "/" << + timeRange.duration().value() << std::endl; + + if (auto node = _graph->exec(_host, time)) + { + // Execute the graph. + const auto buf = node->exec(); + + // Append the image. + const auto thumbnailBuf = OIIO::ImageBufAlgo::resize( + buf, + "", + 0.0, + OIIO::ROI(0, thumbnailSize.x, 0, thumbnailSize.y, 0, 1, 0, 4)); + OIIO::ImageBufAlgo::paste( + filmstripBuf, + filmstripX, + 0, + 0, + 0, + thumbnailBuf); + filmstripX += thumbnailSize.x + thumbnailSpacing; + } + } + + // Write the image. + filmstripBuf.write(outputPath.string()); + + return 0; + } + + void App::_printHelp() + { + std::cout << "Usage:" << std::endl; + std::cout << std::endl; + std::cout << " toucan-filmstrip (input) (output) [options...]" << std::endl; + std::cout << std::endl; + std::cout << "Arguments:" << std::endl; + std::cout << std::endl; + for (const auto& arg : _args.list) + { + std::cout << " " << arg->getName() << " - " << arg->getHelp() << std::endl; + std::cout << std::endl; + } + std::cout << std::endl; + std::cout << "Options:" << std::endl; + std::cout << std::endl; + for (const auto& option : _options.list) + { + for (const auto& line : option->getHelp()) + { + std::cout << " " << line << std::endl; + } + std::cout << std::endl; + } + } +} + diff --git a/bin/toucan-filmstrip/App.h b/bin/toucan-filmstrip/App.h new file mode 100644 index 0000000..3191cb2 --- /dev/null +++ b/bin/toucan-filmstrip/App.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#pragma once + + +#include +#include +#include +#include + +#include + +namespace toucan +{ + class App : public std::enable_shared_from_this + { + public: + App(std::vector&); + + ~App(); + + int run(); + + private: + void _printHelp(); + + std::string _exe; + + struct Args + { + std::string input; + std::string output; + std::vector > list; + }; + Args _args; + + struct Options + { + bool verbose = false; + bool help = false; + std::vector > list; + }; + Options _options; + + std::shared_ptr _timelineWrapper; + std::shared_ptr _graph; + std::shared_ptr _host; + }; +} + diff --git a/bin/toucan-filmstrip/CMakeLists.txt b/bin/toucan-filmstrip/CMakeLists.txt new file mode 100644 index 0000000..2222bcc --- /dev/null +++ b/bin/toucan-filmstrip/CMakeLists.txt @@ -0,0 +1,21 @@ +set(HEADERS + App.h) +set(SOURCE + App.cpp + main.cpp) + +add_executable(toucan-filmstrip ${HEADERS} ${SOURCE}) +target_link_libraries(toucan-filmstrip toucan) +set_target_properties(toucan-filmstrip PROPERTIES FOLDER bin) +add_dependencies(toucan-filmstrip ${TOUCAN_PLUGINS}) + +install( + TARGETS toucan-filmstrip + RUNTIME DESTINATION bin) + +foreach(OTIO CompositeTracks Draw Filter Gap Generator LinearTimeWarp Transition Transition2 Transform) + add_test( + toucan-filmstrip-${OTIO} + ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/toucan-filmstrip${CMAKE_EXECUTABLE_SUFFIX} + ${PROJECT_SOURCE_DIR}/data/${OTIO}.otio ${OTIO}.png) +endforeach() diff --git a/bin/toucan-filmstrip/main.cpp b/bin/toucan-filmstrip/main.cpp new file mode 100644 index 0000000..44739a1 --- /dev/null +++ b/bin/toucan-filmstrip/main.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Contributors to the toucan project. + +#include "App.h" + +#include + +using namespace toucan; + +int main(int argc, char** argv) +{ + int out = 1; + std::vector args; + for (int i = 0; i < argc; ++i) + { + args.push_back(argv[i]); + } + try + { + auto app = std::make_shared(args); + out = app->run(); + } + catch (const std::exception& e) + { + std::cout << "ERROR: " << e.what() << std::endl; + } + return out; +} diff --git a/bin/toucan-render/App.cpp b/bin/toucan-render/App.cpp index 8e3293f..7dcb756 100644 --- a/bin/toucan-render/App.cpp +++ b/bin/toucan-render/App.cpp @@ -3,8 +3,6 @@ #include "App.h" -#include "Util.h" - #include #include @@ -95,14 +93,6 @@ namespace toucan "y4m format to send to stdout.", _options.y4m, join(y4mList, ", "))); - _options.list.push_back(std::make_shared( - _options.filmstrip, - std::vector{ "-filmstrip" }, - "Render the frames to a single output image as thumbnails in a row.")); - _options.list.push_back(std::make_shared( - _options.graph, - std::vector{ "-graph" }, - "Write a Graphviz graph for each frame.")); _options.list.push_back(std::make_shared( _options.verbose, std::vector{ "-v" }, @@ -241,24 +231,6 @@ namespace toucan searchPath, imageHostOptions); - // Initialize the filmstrip. - OIIO::ImageBuf filmstripBuf; - const int thumbnailWidth = 360; - const int thumbnailSpacing = 0; - IMATH_NAMESPACE::V2d thumbnailSize; - if (_options.filmstrip && imageSize.x > 0 && imageSize.y > 0) - { - thumbnailSize = IMATH_NAMESPACE::V2d( - thumbnailWidth, - thumbnailWidth / static_cast(imageSize.x / static_cast(imageSize.y))); - const IMATH_NAMESPACE::V2d filmstripSize( - thumbnailSize.x * frames + thumbnailSpacing * (frames - 1), - thumbnailSize.y); - filmstripBuf = OIIO::ImageBufAlgo::fill( - { 0.F, 0.F, 0.F, 0.F }, - OIIO::ROI(0, filmstripSize.x, 0, filmstripSize.y, 0, 1, 0, 4)); - } - // Open the movie file. std::shared_ptr ffWrite; if (ffmpeg::isExtension(outputPath.extension().string())) @@ -274,7 +246,6 @@ namespace toucan { _writeY4mHeader(); } - int filmstripX = 0; for (OTIO_NS::RationalTime time = timeRange.start_time(); time <= timeRange.end_time_inclusive(); time += timeInc) @@ -291,87 +262,33 @@ namespace toucan const auto buf = node->exec(); // Save the image. - if (!_options.filmstrip) + if (!_args.outputRaw) { - if (!_args.outputRaw) + if (ffWrite) { - if (ffWrite) - { - ffWrite->writeImage(buf, time); - } - else - { - const std::string fileName = getSequenceFrame( - outputPath.parent_path().string(), - outputSplit.first, - outputStartFrame + time.to_frames(), - outputNumberPadding, - outputPath.extension().string()); - buf.write(fileName); - } + ffWrite->writeImage(buf, time); } - else if (!_options.raw.empty()) + else { - _writeRawFrame(buf); - } - else if (!_options.y4m.empty()) - { - _writeY4mFrame(buf); + const std::string fileName = getSequenceFrame( + outputPath.parent_path().string(), + outputSplit.first, + outputStartFrame + time.to_frames(), + outputNumberPadding, + outputPath.extension().string()); + buf.write(fileName); } } - else + else if (!_options.raw.empty()) { - const auto thumbnailBuf = OIIO::ImageBufAlgo::resize( - buf, - "", - 0.0, - OIIO::ROI(0, thumbnailSize.x, 0, thumbnailSize.y, 0, 1, 0, 4)); - OIIO::ImageBufAlgo::paste( - filmstripBuf, - filmstripX, - 0, - 0, - 0, - thumbnailBuf); - filmstripX += thumbnailSize.x + thumbnailSpacing; + _writeRawFrame(buf); } - - // Write the graph. - if (_options.graph) + else if (!_options.y4m.empty()) { - const std::string fileName = getSequenceFrame( - outputPath.parent_path().string(), - outputSplit.first, - outputStartFrame + time.to_frames(), - outputNumberPadding, - ".dot"); - const std::vector lines = node->graph(inputPath.stem().string()); - if (FILE* f = fopen(fileName.c_str(), "w")) - { - for (const auto& line : lines) - { - fprintf(f, "%s\n", line.c_str()); - } - fclose(f); - } + _writeY4mFrame(buf); } } } - if (_options.filmstrip) - { - if (!_args.outputRaw) - { - filmstripBuf.write(outputPath.string()); - } - else if (!_options.raw.empty()) - { - _writeRawFrame(filmstripBuf); - } - else if (!_options.y4m.empty()) - { - _writeY4mFrame(filmstripBuf); - } - } return 0; } diff --git a/bin/toucan-render/App.h b/bin/toucan-render/App.h index 4714601..7c7d2b2 100644 --- a/bin/toucan-render/App.h +++ b/bin/toucan-render/App.h @@ -3,8 +3,7 @@ #pragma once -#include "CmdLine.h" - +#include #include #include #include @@ -54,8 +53,6 @@ namespace toucan bool printSize = false; std::string raw; std::string y4m; - bool filmstrip = false; - bool graph = false; bool verbose = false; bool help = false; std::vector > list; diff --git a/bin/toucan-render/CMakeLists.txt b/bin/toucan-render/CMakeLists.txt index 1217699..c6837d4 100644 --- a/bin/toucan-render/CMakeLists.txt +++ b/bin/toucan-render/CMakeLists.txt @@ -1,12 +1,7 @@ set(HEADERS - App.h - CmdLine.h - CmdLineInline.h - Util.h) + App.h) set(SOURCE App.cpp - CmdLine.cpp - Util.cpp main.cpp) add_executable(toucan-render ${HEADERS} ${SOURCE}) @@ -22,6 +17,6 @@ foreach(OTIO CompositeTracks Draw Filter Gap Generator LinearTimeWarp Transition add_test( toucan-render-${OTIO} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/toucan-render${CMAKE_EXECUTABLE_SUFFIX} - ${PROJECT_SOURCE_DIR}/data/${OTIO}.otio ${OTIO}.png -filmstrip) + ${PROJECT_SOURCE_DIR}/data/${OTIO}.otio ${OTIO}.png) endforeach() diff --git a/bin/toucan-render/Util.cpp b/bin/toucan-render/Util.cpp deleted file mode 100644 index f680b73..0000000 --- a/bin/toucan-render/Util.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Contributors to the toucan project. - -#include "Util.h" - -namespace toucan -{ - std::string join(const std::vector& values, char delimeter) - { - std::string out; - const std::size_t size = values.size(); - for (std::size_t i = 0; i < size; ++i) - { - out += values[i]; - if (i < size - 1) - { - out += delimeter; - } - } - return out; - } - - std::string join(const std::vector& values, const std::string& delimeter) - { - std::string out; - const std::size_t size = values.size(); - for (std::size_t i = 0; i < size; ++i) - { - out += values[i]; - if (i < size - 1) - { - out += delimeter; - } - } - return out; - } -} - diff --git a/bin/toucan-render/Util.h b/bin/toucan-render/Util.h deleted file mode 100644 index 9b92448..0000000 --- a/bin/toucan-render/Util.h +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Contributors to the toucan project. - -#pragma once - -#include -#include - -namespace toucan -{ - //! Join a list of strings. - std::string join(const std::vector&, char delimeter); - - //! Join a list of strings. - std::string join(const std::vector&, const std::string& delimeter); -} - diff --git a/lib/toucan/CMakeLists.txt b/lib/toucan/CMakeLists.txt index c692dbb..301f2c9 100644 --- a/lib/toucan/CMakeLists.txt +++ b/lib/toucan/CMakeLists.txt @@ -1,4 +1,6 @@ set(HEADERS + CmdLine.h + CmdLineInline.h Comp.h FFmpeg.h FFmpegRead.h @@ -21,6 +23,7 @@ set(HEADERS set(HEADERS_PRIVATE ImageEffect_p.h) set(SOURCE + CmdLine.cpp Comp.cpp FFmpeg.cpp FFmpegRead.cpp diff --git a/bin/toucan-render/CmdLine.cpp b/lib/toucan/CmdLine.cpp similarity index 100% rename from bin/toucan-render/CmdLine.cpp rename to lib/toucan/CmdLine.cpp diff --git a/bin/toucan-render/CmdLine.h b/lib/toucan/CmdLine.h similarity index 100% rename from bin/toucan-render/CmdLine.h rename to lib/toucan/CmdLine.h diff --git a/bin/toucan-render/CmdLineInline.h b/lib/toucan/CmdLineInline.h similarity index 100% rename from bin/toucan-render/CmdLineInline.h rename to lib/toucan/CmdLineInline.h diff --git a/lib/toucan/Util.cpp b/lib/toucan/Util.cpp index 5afd9e7..0fb062a 100644 --- a/lib/toucan/Util.cpp +++ b/lib/toucan/Util.cpp @@ -117,6 +117,36 @@ namespace toucan return out; } + std::string join(const std::vector& values, char delimeter) + { + std::string out; + const std::size_t size = values.size(); + for (std::size_t i = 0; i < size; ++i) + { + out += values[i]; + if (i < size - 1) + { + out += delimeter; + } + } + return out; + } + + std::string join(const std::vector& values, const std::string& delimeter) + { + std::string out; + const std::size_t size = values.size(); + for (std::size_t i = 0; i < size; ++i) + { + out += values[i]; + if (i < size - 1) + { + out += delimeter; + } + } + return out; + } + std::string getSequenceFrame( const std::filesystem::path& path, const std::string& namePrefix, diff --git a/lib/toucan/Util.h b/lib/toucan/Util.h index b14ee03..db3d9af 100644 --- a/lib/toucan/Util.h +++ b/lib/toucan/Util.h @@ -34,6 +34,12 @@ namespace toucan //! Split the URL protocol. std::pair splitURLProtocol(const std::string&); + //! Join a list of strings. + std::string join(const std::vector&, char delimeter); + + //! Join a list of strings. + std::string join(const std::vector&, const std::string& delimeter); + //! Get an image sequence file name. std::string getSequenceFrame( const std::filesystem::path&, diff --git a/lib/toucanView/ExportTool.cpp b/lib/toucanView/ExportTool.cpp index d4ec44d..bc33c4f 100644 --- a/lib/toucanView/ExportTool.cpp +++ b/lib/toucanView/ExportTool.cpp @@ -25,9 +25,9 @@ namespace toucan _host = app->getHost(); _imageExtensions = { - ".exr", ".tiff", - ".png" + ".png", + ".exr" }; _movieCodecs = { From f13b6d9c724be6422d0b6d8c279760b42407f58d Mon Sep 17 00:00:00 2001 From: Darby Johnston Date: Wed, 4 Dec 2024 15:49:41 -0800 Subject: [PATCH 05/10] Try adding SVT-AV1 support Signed-off-by: Darby Johnston --- bin/toucan-render/App.cpp | 2 +- cmake/SuperBuild/BuildFFmpeg.cmake | 5 ++++- cmake/SuperBuild/Buildsvt-av1.cmake | 16 +++++++++++++++ cmake/SuperBuild/CMakeLists.txt | 6 +++++- legal/LICENSE_svt-av1.md | 32 +++++++++++++++++++++++++++++ lib/toucan/FFmpegWrite.cpp | 16 +++++++-------- lib/toucan/FFmpegWrite.h | 4 ++-- lib/toucanView/ExportTool.cpp | 2 +- 8 files changed, 69 insertions(+), 14 deletions(-) create mode 100644 cmake/SuperBuild/Buildsvt-av1.cmake create mode 100644 legal/LICENSE_svt-av1.md diff --git a/bin/toucan-render/App.cpp b/bin/toucan-render/App.cpp index 7dcb756..cd8f8e0 100644 --- a/bin/toucan-render/App.cpp +++ b/bin/toucan-render/App.cpp @@ -238,7 +238,7 @@ namespace toucan ffWrite = std::make_shared( outputPath, OIIO::ImageSpec(imageSize.x, imageSize.y, 3), - timeRange.duration().rate()); + timeRange); } // Render the timeline frames. diff --git a/cmake/SuperBuild/BuildFFmpeg.cmake b/cmake/SuperBuild/BuildFFmpeg.cmake index 4f8b362..ce83688 100644 --- a/cmake/SuperBuild/BuildFFmpeg.cmake +++ b/cmake/SuperBuild/BuildFFmpeg.cmake @@ -154,6 +154,7 @@ if(toucan_FFmpeg_MINIMAL) --disable-encoders --enable-encoder=aac --enable-encoder=ac3 + --enable-encoder=av1 --enable-encoder=dnxhd --enable-encoder=eac3 --enable-encoder=mjpeg @@ -244,6 +245,7 @@ if(toucan_FFmpeg_MINIMAL) --disable-muxers --enable-muxer=ac3 --enable-muxer=aiff + --enable-muxer=av1 --enable-muxer=dnxhd --enable-muxer=dts --enable-muxer=eac3 @@ -303,7 +305,8 @@ if(toucan_FFmpeg_MINIMAL) --enable-protocol=https --enable-protocol=md5 --enable-protocol=pipe - --enable-protocol=tls) + --enable-protocol=tls + --disable-filters) endif() if(NOT WIN32) list(APPEND FFmpeg_CONFIGURE_ARGS diff --git a/cmake/SuperBuild/Buildsvt-av1.cmake b/cmake/SuperBuild/Buildsvt-av1.cmake new file mode 100644 index 0000000..cb40ed0 --- /dev/null +++ b/cmake/SuperBuild/Buildsvt-av1.cmake @@ -0,0 +1,16 @@ +include(ExternalProject) + +set(svt-av1_GIT_REPOSITORY "https://gitlab.com/AOMediaCodec/SVT-AV1.git") +set(svt-av1_GIT_TAG "v2.3.0") + +set(svt-av1_ARGS + ${toucan_EXTERNAL_PROJECT_ARGS} + -DBUILD_APPS=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON) + +ExternalProject_Add( + svt-av1 + PREFIX ${CMAKE_CURRENT_BINARY_DIR}/svt-av1 + GIT_REPOSITORY ${svt-av1_GIT_REPOSITORY} + GIT_TAG ${svt-av1_GIT_TAG} + CMAKE_ARGS ${svt-av1_ARGS}) diff --git a/cmake/SuperBuild/CMakeLists.txt b/cmake/SuperBuild/CMakeLists.txt index 03fdfab..4db7b1a 100644 --- a/cmake/SuperBuild/CMakeLists.txt +++ b/cmake/SuperBuild/CMakeLists.txt @@ -15,6 +15,7 @@ set(toucan_JPEG ON CACHE BOOL "Build JPEG") set(toucan_TIFF ON CACHE BOOL "Build TIFF") set(toucan_Imath ON CACHE BOOL "Build Imath") set(toucan_OpenEXR ON CACHE BOOL "Build OpenEXR") +set(toucan_svt-av1 ON CACHE BOOL "Build SVT-AV1") set(toucan_FFmpeg ON CACHE BOOL "Build FFmpeg") set(toucan_FFmpeg_MINIMAL ON CACHE BOOL "Build a minimal set of FFmpeg codecs") set(toucan_OpenColorIO ON CACHE BOOL "Build OpenColorIO") @@ -83,7 +84,10 @@ if(toucan_Imath) include(BuildImath) endif() if(toucan_OpenEXR) -include(BuildOpenEXR) + include(BuildOpenEXR) +endif() +if(toucan_svt-av1) + include(Buildsvt-av1.cmake) endif() if(toucan_FFmpeg) include(BuildFFmpeg) diff --git a/legal/LICENSE_svt-av1.md b/legal/LICENSE_svt-av1.md new file mode 100644 index 0000000..aff96d1 --- /dev/null +++ b/legal/LICENSE_svt-av1.md @@ -0,0 +1,32 @@ +BSD 3-Clause Clear License +The Clear BSD License + +Copyright (c) 2021, Alliance for Open Media + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted (subject to the limitations in the disclaimer below) +provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + +3. Neither the name of the Alliance for Open Media nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/toucan/FFmpegWrite.cpp b/lib/toucan/FFmpegWrite.cpp index a86e9a7..e2c95d6 100644 --- a/lib/toucan/FFmpegWrite.cpp +++ b/lib/toucan/FFmpegWrite.cpp @@ -21,10 +21,10 @@ namespace toucan Write::Write( const std::filesystem::path& path, const OIIO::ImageSpec& spec, - double rate) : + const OTIO_NS::TimeRange& timeRange) : _path(path), _spec(spec), - _rate(rate) + _timeRange(timeRange) { av_log_set_level(AV_LOG_QUIET); //av_log_set_level(AV_LOG_VERBOSE); @@ -64,7 +64,7 @@ namespace toucan _avCodecContext->height = spec.height; _avCodecContext->sample_aspect_ratio = AVRational({ 1, 1 }); _avCodecContext->pix_fmt = avCodec->pix_fmts[0]; - const auto rational = toRational(rate); + const auto rational = toRational(timeRange.duration().rate()); _avCodecContext->time_base = { rational.second, rational.first }; _avCodecContext->framerate = { rational.first, rational.second }; _avCodecContext->profile = avProfile; @@ -243,7 +243,7 @@ namespace toucan r = sws_init_context(_swsContext, nullptr, nullptr); if (r < 0) { - throw std::runtime_error("Cannot initialize sws context"); + throw std::runtime_error(getErrorLabel(r)); } } @@ -267,7 +267,7 @@ namespace toucan const auto timeRational = toRational(time.rate()); _avFrame->pts = av_rescale_q( - time.value(), + (time - _timeRange.start_time()).value(), { timeRational.second, timeRational.first }, _avVideoStream->time_base); _encodeVideo(_avFrame); @@ -278,7 +278,7 @@ namespace toucan int r = avcodec_send_frame(_avCodecContext, frame); if (r < 0) { - throw std::runtime_error("Cannot write frame"); + throw std::runtime_error(getErrorLabel(r)); } while (r >= 0) @@ -290,12 +290,12 @@ namespace toucan } else if (r < 0) { - throw std::runtime_error("Cannot write frame"); + throw std::runtime_error(getErrorLabel(r)); } r = av_interleaved_write_frame(_avFormatContext, _avPacket); if (r < 0) { - throw std::runtime_error("Cannot write frame"); + throw std::runtime_error(getErrorLabel(r)); } av_packet_unref(_avPacket); } diff --git a/lib/toucan/FFmpegWrite.h b/lib/toucan/FFmpegWrite.h index 544dd64..1431aa2 100644 --- a/lib/toucan/FFmpegWrite.h +++ b/lib/toucan/FFmpegWrite.h @@ -29,7 +29,7 @@ namespace toucan Write( const std::filesystem::path&, const OIIO::ImageSpec&, - double rate); + const OTIO_NS::TimeRange&); virtual ~Write(); @@ -40,7 +40,7 @@ namespace toucan std::filesystem::path _path; OIIO::ImageSpec _spec; - double _rate = 0.0; + OTIO_NS::TimeRange _timeRange; AVFormatContext* _avFormatContext = nullptr; AVCodecContext* _avCodecContext = nullptr; AVStream* _avVideoStream = nullptr; diff --git a/lib/toucanView/ExportTool.cpp b/lib/toucanView/ExportTool.cpp index bc33c4f..95e8529 100644 --- a/lib/toucanView/ExportTool.cpp +++ b/lib/toucanView/ExportTool.cpp @@ -133,7 +133,7 @@ namespace toucan _ffWrite = std::make_shared( path, OIIO::ImageSpec(imageSize.x, imageSize.y, 3), - _timeRange.duration().rate()); + _timeRange); _export(); }); From ba06172905ff99efae09b97154216e533070081a Mon Sep 17 00:00:00 2001 From: Darby Johnston Date: Wed, 4 Dec 2024 17:33:22 -0800 Subject: [PATCH 06/10] AV1 fixes Signed-off-by: Darby Johnston --- cmake/SuperBuild/BuildFFmpeg.cmake | 16 +++++++++++++--- cmake/SuperBuild/BuildJPEG.cmake | 2 +- cmake/SuperBuild/Buildsvt-av1.cmake | 11 +++++++++++ .../svt-av1-patch/Source/Lib/pkg-config.pc.in | 12 ++++++++++++ 4 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 cmake/SuperBuild/svt-av1-patch/Source/Lib/pkg-config.pc.in diff --git a/cmake/SuperBuild/BuildFFmpeg.cmake b/cmake/SuperBuild/BuildFFmpeg.cmake index ce83688..ebbe5f6 100644 --- a/cmake/SuperBuild/BuildFFmpeg.cmake +++ b/cmake/SuperBuild/BuildFFmpeg.cmake @@ -6,6 +6,9 @@ if(WIN32) endif() set(FFmpeg_DEPS ZLIB) +if(toucan_svt-av1) + list(APPEND FFmpeg_DEPS svt-av1) +endif() if(toucan_NET) list(APPEND FFmpeg_DEPS OpenSSL) endif() @@ -42,12 +45,14 @@ if(APPLE AND CMAKE_OSX_DEPLOYMENT_TARGET) list(APPEND FFmpeg_OBJCFLAGS "--extra-objcflags=-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") list(APPEND FFmpeg_LDFLAGS "--extra-ldflags=-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") endif() + if(FFmpeg_DEBUG) list(APPEND FFmpeg_CFLAGS "--extra-cflags=-g") list(APPEND FFmpeg_CXXFLAGS "--extra-cxxflags=-g") list(APPEND FFmpeg_OBJCFLAGS "--extra-objcflags=-g") list(APPEND FFmpeg_LDFLAGS "--extra-ldflags=-g") endif() + set(FFmpeg_CONFIGURE_ARGS --prefix=${CMAKE_INSTALL_PREFIX} --disable-doc @@ -154,9 +159,9 @@ if(toucan_FFmpeg_MINIMAL) --disable-encoders --enable-encoder=aac --enable-encoder=ac3 - --enable-encoder=av1 --enable-encoder=dnxhd --enable-encoder=eac3 + --enable-encoder=libsvtav1 --enable-encoder=mjpeg --enable-encoder=mpeg2video --enable-encoder=mpeg4 @@ -245,7 +250,6 @@ if(toucan_FFmpeg_MINIMAL) --disable-muxers --enable-muxer=ac3 --enable-muxer=aiff - --enable-muxer=av1 --enable-muxer=dnxhd --enable-muxer=dts --enable-muxer=eac3 @@ -312,6 +316,11 @@ if(NOT WIN32) list(APPEND FFmpeg_CONFIGURE_ARGS --x86asmexe=${CMAKE_INSTALL_PREFIX}/bin/nasm) endif() +if(toucan_svt-av1) + list(APPEND FFmpeg_CONFIGURE_ARGS + --enable-libsvtav1 + --pkg-config-flags=--with-path=${CMAKE_INSTALL_PREFIX}/lib/pkgconfig) +endif() if(toucan_NET) list(APPEND FFmpeg_CONFIGURE_ARGS --enable-openssl) @@ -328,6 +337,7 @@ if(FFmpeg_DEBUG) --enable-debug=3 --assert-level=2) endif() + include(ProcessorCount) ProcessorCount(FFmpeg_BUILD_JOBS) if(WIN32) @@ -404,7 +414,7 @@ ExternalProject_Add( FFmpeg PREFIX ${CMAKE_CURRENT_BINARY_DIR}/FFmpeg DEPENDS ${FFmpeg_DEPS} - URL https://ffmpeg.org/releases/ffmpeg-7.0.1.tar.bz2 + URL https://ffmpeg.org/releases/ffmpeg-7.1.tar.bz2 CONFIGURE_COMMAND ${FFmpeg_CONFIGURE} BUILD_COMMAND ${FFmpeg_BUILD} INSTALL_COMMAND ${FFmpeg_INSTALL} diff --git a/cmake/SuperBuild/BuildJPEG.cmake b/cmake/SuperBuild/BuildJPEG.cmake index 6c7a91b..f37d6a0 100644 --- a/cmake/SuperBuild/BuildJPEG.cmake +++ b/cmake/SuperBuild/BuildJPEG.cmake @@ -5,7 +5,7 @@ set(libjpeg-turbo_GIT_TAG "3.0.0") set(libjpeg-turbo_DEPS ZLIB) if(NOT WIN32) - set(libjpeg-turbo_DEPS ${libjpeg-turbo_DEPS} NASM) + list(APPEND libjpeg-turbo_DEPS NASM) endif() set(libjpeg-turbo_ENABLE_SHARED ON) diff --git a/cmake/SuperBuild/Buildsvt-av1.cmake b/cmake/SuperBuild/Buildsvt-av1.cmake index cb40ed0..818d36e 100644 --- a/cmake/SuperBuild/Buildsvt-av1.cmake +++ b/cmake/SuperBuild/Buildsvt-av1.cmake @@ -3,6 +3,15 @@ include(ExternalProject) set(svt-av1_GIT_REPOSITORY "https://gitlab.com/AOMediaCodec/SVT-AV1.git") set(svt-av1_GIT_TAG "v2.3.0") +set(svt-av1_DEPS) +if(NOT WIN32) + list(APPEND svt-av1_DEPS NASM) +endif() + +set(svt-av1_PATCH ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/svt-av1-patch/Source/Lib/pkg-config.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/svt-av1/src/svt-av1/Source/Lib/pkg-config.pc.in) + set(svt-av1_ARGS ${toucan_EXTERNAL_PROJECT_ARGS} -DBUILD_APPS=OFF @@ -11,6 +20,8 @@ set(svt-av1_ARGS ExternalProject_Add( svt-av1 PREFIX ${CMAKE_CURRENT_BINARY_DIR}/svt-av1 + DEPENDS ${svt-av1_DEPS} GIT_REPOSITORY ${svt-av1_GIT_REPOSITORY} GIT_TAG ${svt-av1_GIT_TAG} + PATCH_COMMAND ${svt-av1_PATCH} CMAKE_ARGS ${svt-av1_ARGS}) diff --git a/cmake/SuperBuild/svt-av1-patch/Source/Lib/pkg-config.pc.in b/cmake/SuperBuild/svt-av1-patch/Source/Lib/pkg-config.pc.in new file mode 100644 index 0000000..a55ce28 --- /dev/null +++ b/cmake/SuperBuild/svt-av1-patch/Source/Lib/pkg-config.pc.in @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +includedir=@SVT_AV1_INCLUDEDIR@ +libdir=@SVT_AV1_LIBDIR@ + +Name: SvtAv1Enc +Description: SVT (Scalable Video Technology) for AV1 encoder library +Version: @ENC_VERSION_MAJOR@.@ENC_VERSION_MINOR@.@ENC_VERSION_PATCH@ +Libs: -L${libdir} -lSvtAv1Enc @LIBS_PRIVATE@ +Libs.private: @LIBS_PRIVATE@ +Cflags: -I${includedir}/svt-av1@ENC_PKG_CONFIG_EXTRA_CFLAGS@ +Cflags.private: -UEB_DLL From fd64f4dfc035b302a997ed11459722e9578b8e78 Mon Sep 17 00:00:00 2001 From: Darby Johnston Date: Thu, 5 Dec 2024 14:23:52 -0800 Subject: [PATCH 07/10] Refactoring Signed-off-by: Darby Johnston --- .gitignore | 2 + bin/toucan-render/App.cpp | 13 ++- bin/toucan-render/App.h | 1 + cmake/SuperBuild/BuildFFmpeg.cmake | 2 +- lib/toucan/FFmpeg.cpp | 134 ++++++++++++++++++++++++----- lib/toucan/FFmpeg.h | 43 +++++++-- lib/toucan/FFmpegWrite.cpp | 9 +- lib/toucan/FFmpegWrite.h | 4 +- lib/toucanView/ExportTool.cpp | 106 ++++++++++++++--------- lib/toucanView/ExportTool.h | 6 ++ lib/toucanView/JSONTool.cpp | 4 - lib/toucanView/JSONTool.h | 1 - lib/toucanView/MainWindow.cpp | 20 ++--- lib/toucanView/MainWindow.h | 2 - lib/toucanView/MenuBar.cpp | 24 +++--- lib/toucanView/TimelineWidget.cpp | 1 + lib/toucanView/ToolBar.cpp | 41 +++++++++ lib/toucanView/ToolBar.h | 5 +- lib/toucanView/WindowModel.cpp | 12 +-- lib/toucanView/WindowModel.h | 6 +- 20 files changed, 315 insertions(+), 121 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5acb669 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +.vscode diff --git a/bin/toucan-render/App.cpp b/bin/toucan-render/App.cpp index cd8f8e0..5e3ba05 100644 --- a/bin/toucan-render/App.cpp +++ b/bin/toucan-render/App.cpp @@ -65,6 +65,12 @@ namespace toucan { y4mList.push_back(spec.first); } + _options.list.push_back(std::make_shared >( + _options.videoCodec, + std::vector{ "-vcodec" }, + "Set the video codec.", + _options.videoCodec, + join(ffmpeg::getVideoCodecStrings(), ", "))); _options.list.push_back(std::make_shared( _options.printStart, std::vector{ "-print_start" }, @@ -233,12 +239,15 @@ namespace toucan // Open the movie file. std::shared_ptr ffWrite; - if (ffmpeg::isExtension(outputPath.extension().string())) + if (ffmpeg::hasVideoExtension(outputPath.extension().string())) { + ffmpeg::VideoCodec videoCodec = ffmpeg::VideoCodec::First; + ffmpeg::fromString(_options.videoCodec, videoCodec); ffWrite = std::make_shared( outputPath, OIIO::ImageSpec(imageSize.x, imageSize.y, 3), - timeRange); + timeRange, + videoCodec); } // Render the timeline frames. diff --git a/bin/toucan-render/App.h b/bin/toucan-render/App.h index 7c7d2b2..820c1b0 100644 --- a/bin/toucan-render/App.h +++ b/bin/toucan-render/App.h @@ -47,6 +47,7 @@ namespace toucan struct Options { + std::string videoCodec = "mjpeg"; bool printStart = false; bool printDuration = false; bool printRate = false; diff --git a/cmake/SuperBuild/BuildFFmpeg.cmake b/cmake/SuperBuild/BuildFFmpeg.cmake index ebbe5f6..0f14bd9 100644 --- a/cmake/SuperBuild/BuildFFmpeg.cmake +++ b/cmake/SuperBuild/BuildFFmpeg.cmake @@ -415,7 +415,7 @@ ExternalProject_Add( PREFIX ${CMAKE_CURRENT_BINARY_DIR}/FFmpeg DEPENDS ${FFmpeg_DEPS} URL https://ffmpeg.org/releases/ffmpeg-7.1.tar.bz2 - CONFIGURE_COMMAND ${FFmpeg_CONFIGURE} + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env PKG_CONFIG_PATH=${CMAKE_INSTALL_PREFIX}/lib/pkgconfig ${FFmpeg_CONFIGURE} BUILD_COMMAND ${FFmpeg_BUILD} INSTALL_COMMAND ${FFmpeg_INSTALL} BUILD_IN_SOURCE 1) diff --git a/lib/toucan/FFmpeg.cpp b/lib/toucan/FFmpeg.cpp index 554016c..498aa90 100644 --- a/lib/toucan/FFmpeg.cpp +++ b/lib/toucan/FFmpeg.cpp @@ -7,47 +7,119 @@ extern "C" { +#include #include } +#include #include namespace toucan { namespace ffmpeg { - void log(void*, int level, const char* fmt, va_list vl) + AVRational swap(AVRational value) { - switch (level) + return AVRational({ value.den, value.num }); + } + + std::vector getVideoExtensions() + { + return std::vector({ ".mov", ".mp4", ".m4v" }); + } + + bool hasVideoExtension(const std::string& value) + { + const std::vector extensions = getVideoExtensions(); + const auto i = std::find(extensions.begin(), extensions.end(), value); + return i != extensions.end(); + } + + namespace + { + std::vector > _getVideoCodecs() { - case AV_LOG_PANIC: - case AV_LOG_FATAL: - case AV_LOG_ERROR: - case AV_LOG_WARNING: - case AV_LOG_INFO: + std::vector > out; + const AVCodec* avCodec = nullptr; + void* avCodecIterate = nullptr; + std::vector codecNames; + while ((avCodec = av_codec_iterate(&avCodecIterate))) + { + if (av_codec_is_encoder(avCodec) && + AVMEDIA_TYPE_VIDEO == avcodec_get_type(avCodec->id)) + { + out.push_back({ avCodec->id, avCodec->name }); + } + } + return out; + } + + const std::vector videoCodecStrings = { - char buf[4096]; - vsnprintf(buf, 4096, fmt, vl); - std::cout << buf; + "MJPEG", + "AV1" + }; + + const std::vector videoCodecIds = + { + AV_CODEC_ID_MJPEG, + AV_CODEC_ID_AV1 + }; + + const std::vector videoCodecProfiles = + { + AV_PROFILE_UNKNOWN, + AV_PROFILE_AV1_MAIN + }; + } + + std::vector getVideoCodecs() + { + std::vector out; + for (const auto& i : _getVideoCodecs()) + { + for (size_t j = 0; j < videoCodecIds.size(); ++j) + { + if (i.first == videoCodecIds[j]) + { + out.push_back(static_cast(j)); + } + } } - break; - case AV_LOG_VERBOSE: - default: break; + return out; + } + + std::vector getVideoCodecStrings() + { + std::vector out; + for (const auto& i : getVideoCodecs()) + { + out.push_back(toString(i)); } + return out; } - AVRational swap(AVRational value) + std::string toString(VideoCodec value) { - return AVRational({ value.den, value.num }); + return videoCodecStrings[static_cast(value)]; + } + + void fromString(const std::string& s, VideoCodec& value) + { + const auto i = std::find(videoCodecStrings.begin(), videoCodecStrings.end(), s); + value = i != videoCodecStrings.end() ? + static_cast(i - videoCodecStrings.begin()) : + VideoCodec::First; + } + + AVCodecID getVideoCodecId(VideoCodec value) + { + return videoCodecIds[static_cast(value)]; } - bool isExtension(const std::string& extension) + int getVideoCodecProfile(VideoCodec value) { - const std::string lower = toLower(extension); - return - lower == ".mp4" || - lower == ".m4v" || - lower == ".mov"; + return videoCodecProfiles[static_cast(value)]; } std::string getErrorLabel(int r) @@ -56,5 +128,25 @@ namespace toucan av_strerror(r, buf, 4096); return std::string(buf); } + + void log(void*, int level, const char* fmt, va_list vl) + { + switch (level) + { + case AV_LOG_PANIC: + case AV_LOG_FATAL: + case AV_LOG_ERROR: + case AV_LOG_WARNING: + case AV_LOG_INFO: + { + char buf[4096]; + vsnprintf(buf, 4096, fmt, vl); + std::cout << buf; + } + break; + case AV_LOG_VERBOSE: + default: break; + } + } } } diff --git a/lib/toucan/FFmpeg.h b/lib/toucan/FFmpeg.h index 43f27d6..d28d16f 100644 --- a/lib/toucan/FFmpeg.h +++ b/lib/toucan/FFmpeg.h @@ -4,9 +4,11 @@ #pragma once #include +#include extern "C" { +#include #include #include } @@ -15,14 +17,45 @@ namespace toucan { namespace ffmpeg { - //! FFmpeg log callback. - void log(void*, int level, const char* fmt, va_list vl); - //! Swap the numerator and denominator. AVRational swap(AVRational); - //! Check whether the given extension is compatible. - bool isExtension(const std::string&); + //! Get a list of supported video extensions. + std::vector getVideoExtensions(); + + //! Check if the given extension is supported. + bool hasVideoExtension(const std::string&); + + //! Video codecs. + enum class VideoCodec + { + MJPEG, + AV1, + + Count, + First = MJPEG + }; + + //! Get a list of video codecs. + std::vector getVideoCodecs(); + + //! Get a list of video codec strings. + std::vector getVideoCodecStrings(); + + //! Convert a video codec to a string. + std::string toString(VideoCodec); + + //! Convert a string to a video codec. + void fromString(const std::string&, VideoCodec&); + + //! Get a video codec ID. + AVCodecID getVideoCodecId(VideoCodec); + + //! Get a video codec profile. + int getVideoCodecProfile(VideoCodec); + + //! FFmpeg log callback. + void log(void*, int level, const char* fmt, va_list vl); //! Get an error label. std::string getErrorLabel(int); diff --git a/lib/toucan/FFmpegWrite.cpp b/lib/toucan/FFmpegWrite.cpp index e2c95d6..2fa9da9 100644 --- a/lib/toucan/FFmpegWrite.cpp +++ b/lib/toucan/FFmpegWrite.cpp @@ -21,17 +21,18 @@ namespace toucan Write::Write( const std::filesystem::path& path, const OIIO::ImageSpec& spec, - const OTIO_NS::TimeRange& timeRange) : + const OTIO_NS::TimeRange& timeRange, + VideoCodec videoCodec) : _path(path), _spec(spec), _timeRange(timeRange) { av_log_set_level(AV_LOG_QUIET); //av_log_set_level(AV_LOG_VERBOSE); - //av_log_set_callback(log);AVCodecID avCodecID = AV_CODEC_ID_MPEG4; + //av_log_set_callback(log); - AVCodecID avCodecID = AV_CODEC_ID_MJPEG; - int avProfile = AV_PROFILE_UNKNOWN; + AVCodecID avCodecID = getVideoCodecId(videoCodec); + int avProfile = getVideoCodecProfile(videoCodec); int r = avformat_alloc_output_context2(&_avFormatContext, NULL, NULL, _path.string().c_str()); if (r < 0) diff --git a/lib/toucan/FFmpegWrite.h b/lib/toucan/FFmpegWrite.h index 1431aa2..05d234d 100644 --- a/lib/toucan/FFmpegWrite.h +++ b/lib/toucan/FFmpegWrite.h @@ -11,7 +11,6 @@ extern "C" { -#include #include #include @@ -29,7 +28,8 @@ namespace toucan Write( const std::filesystem::path&, const OIIO::ImageSpec&, - const OTIO_NS::TimeRange&); + const OTIO_NS::TimeRange&, + VideoCodec); virtual ~Write(); diff --git a/lib/toucanView/ExportTool.cpp b/lib/toucanView/ExportTool.cpp index 95e8529..959e856 100644 --- a/lib/toucanView/ExportTool.cpp +++ b/lib/toucanView/ExportTool.cpp @@ -9,8 +9,8 @@ #include +#include #include -#include #include namespace toucan @@ -29,10 +29,8 @@ namespace toucan ".png", ".exr" }; - _movieCodecs = - { - "mjpeg" - }; + _movieExtensions = ffmpeg::getVideoExtensions(); + _movieCodecs = ffmpeg::getVideoCodecStrings(); _layout = dtk::VerticalLayout::create(context, shared_from_this()); _layout->setSpacingRole(dtk::SizeRole::None); @@ -71,14 +69,14 @@ namespace toucan divider = dtk::Divider::create(context, dtk::Orientation::Vertical, _imageLayout); - auto exportSequenceButton = dtk::PushButton::create( + _exportSequenceButton = dtk::PushButton::create( context, "Export Sequence", _imageLayout); - auto exportCurrentButton = dtk::PushButton::create( + _exportStillButton = dtk::PushButton::create( context, - "Export Current Frame", + "Export Still Frame", _imageLayout); _movieLayout = dtk::VerticalLayout::create(context); @@ -93,26 +91,31 @@ namespace toucan _movieBaseNameEdit->setText("render"); gridLayout->setGridPos(_movieBaseNameEdit, 0, 1); - label = dtk::Label::create(context, "Codec:", gridLayout); + label = dtk::Label::create(context, "Extension:", gridLayout); gridLayout->setGridPos(label, 1, 0); + _movieExtensionComboBox = dtk::ComboBox::create(context, _movieExtensions, gridLayout); + gridLayout->setGridPos(_movieExtensionComboBox, 1, 1); + + label = dtk::Label::create(context, "Codec:", gridLayout); + gridLayout->setGridPos(label, 2, 0); _movieCodecComboBox = dtk::ComboBox::create(context, _movieCodecs, gridLayout); - gridLayout->setGridPos(_movieCodecComboBox, 1, 1); + gridLayout->setGridPos(_movieCodecComboBox, 2, 1); divider = dtk::Divider::create(context, dtk::Orientation::Vertical, _movieLayout); - auto exportMovieButton = dtk::PushButton::create( + _exportMovieButton = dtk::PushButton::create( context, "Export Movie", _movieLayout); - exportSequenceButton->setClickedCallback( + _exportSequenceButton->setClickedCallback( [this] { _timeRange = _file->getPlaybackModel()->getInOutRange(); _export(); }); - exportCurrentButton->setClickedCallback( + _exportStillButton->setClickedCallback( [this] { _timeRange = OTIO_NS::TimeRange( @@ -121,20 +124,30 @@ namespace toucan _export(); }); - exportMovieButton->setClickedCallback( + _exportMovieButton->setClickedCallback( [this] { _timeRange = _file->getPlaybackModel()->getInOutRange(); - const std::string extension = ".mov"; - const std::filesystem::path path = - _outputPathEdit->getPath() / - (_movieBaseNameEdit->getText() + extension); + const std::string baseName = _movieBaseNameEdit->getText(); + const std::string extension = _movieExtensions[_movieExtensionComboBox->getCurrentIndex()]; + const std::filesystem::path path = _outputPathEdit->getPath() / (baseName + extension); const IMATH_NAMESPACE::V2d imageSize = _graph->getImageSize(); - _ffWrite = std::make_shared( - path, - OIIO::ImageSpec(imageSize.x, imageSize.y, 3), - _timeRange); - _export(); + ffmpeg::VideoCodec videoCodec = ffmpeg::VideoCodec::First; + ffmpeg::fromString(_movieCodecs[_movieCodecComboBox->getCurrentIndex()], videoCodec); + try + { + _ffWrite = std::make_shared( + path, + OIIO::ImageSpec(imageSize.x, imageSize.y, 3), + _timeRange, + videoCodec); + _export(); + } + catch (const std::exception& e) + { + auto dialogSystem = getContext()->getSystem(); + dialogSystem->message("ERROR", e.what(), getWindow()); + } }); _timer = dtk::Timer::create(context); @@ -157,9 +170,9 @@ namespace toucan _time = OTIO_NS::RationalTime(); _graph.reset(); } - _outputLayout->setEnabled(_file.get()); - _imageLayout->setEnabled(_file.get()); - _movieLayout->setEnabled(_file.get()); + _exportSequenceButton->setEnabled(_file.get()); + _exportStillButton->setEnabled(_file.get()); + _exportMovieButton->setEnabled(_file.get()); }); } @@ -215,7 +228,19 @@ namespace toucan const auto buf = node->exec(); if (_ffWrite) { - _ffWrite->writeImage(buf, _time); + try + { + _ffWrite->writeImage(buf, _time); + } + catch (const std::exception& e) + { + if (_dialog) + { + _dialog->close(); + } + auto dialogSystem = getContext()->getSystem(); + dialogSystem->message("ERROR", e.what(), getWindow()); + } } else { @@ -229,19 +254,22 @@ namespace toucan } } - 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 + if (_dialog) { - _dialog->close(); + 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(); + } } }); } diff --git a/lib/toucanView/ExportTool.h b/lib/toucanView/ExportTool.h index f84bf26..2aba2e8 100644 --- a/lib/toucanView/ExportTool.h +++ b/lib/toucanView/ExportTool.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,7 @@ namespace toucan OTIO_NS::RationalTime _time; std::shared_ptr _graph; std::vector _imageExtensions; + std::vector _movieExtensions; std::vector _movieCodecs; std::shared_ptr _ffWrite; @@ -65,9 +67,13 @@ namespace toucan std::shared_ptr _imageBaseNameEdit; std::shared_ptr _imagePaddingEdit; std::shared_ptr _imageExtensionComboBox; + std::shared_ptr _exportSequenceButton; + std::shared_ptr _exportStillButton; std::shared_ptr _movieLayout; std::shared_ptr _movieBaseNameEdit; + std::shared_ptr _movieExtensionComboBox; std::shared_ptr _movieCodecComboBox; + std::shared_ptr _exportMovieButton; std::shared_ptr _dialog; std::shared_ptr _timer; diff --git a/lib/toucanView/JSONTool.cpp b/lib/toucanView/JSONTool.cpp index 60bbf24..79a17d6 100644 --- a/lib/toucanView/JSONTool.cpp +++ b/lib/toucanView/JSONTool.cpp @@ -112,9 +112,6 @@ 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); @@ -183,7 +180,6 @@ 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 b5bbdd8..e341184 100644 --- a/lib/toucanView/JSONTool.h +++ b/lib/toucanView/JSONTool.h @@ -84,7 +84,6 @@ namespace toucan std::shared_ptr _scrollWidget; std::shared_ptr _scrollLayout; std::vector > _widgets; - std::shared_ptr _nothingSelectedLabel; std::shared_ptr _bottomLayout; std::shared_ptr > > _fileObserver; diff --git a/lib/toucanView/MainWindow.cpp b/lib/toucanView/MainWindow.cpp index 360666d..c47113f 100644 --- a/lib/toucanView/MainWindow.cpp +++ b/lib/toucanView/MainWindow.cpp @@ -91,12 +91,12 @@ namespace toucan _playbackBar = PlaybackBar::create(context, app, _bottomLayout); - _timelineDivider = dtk::Divider::create(context, dtk::Orientation::Vertical, _bottomLayout); + auto divider = dtk::Divider::create(context, dtk::Orientation::Vertical, _bottomLayout); _timelineWidget = TimelineWidget::create(context, app, _bottomLayout); _timelineWidget->setVStretch(dtk::Stretch::Expanding); - _infoDivider = dtk::Divider::create(context, dtk::Orientation::Vertical, _layout); + divider = dtk::Divider::create(context, dtk::Orientation::Vertical, _layout); _infoBar = InfoBar::create(context, app, _layout); @@ -174,19 +174,11 @@ namespace toucan _toolBar->setVisible(i->second); _toolBarDivider->setVisible(i->second); - i = value.find(WindowComponent::PlaybackBar); - auto j = value.find(WindowComponent::TimelineWidget); - _bottomLayout->setVisible(i->second || j->second); - _playbackBar->setVisible(i->second); - _timelineWidget->setVisible(j->second); - _timelineDivider->setVisible(i->second && j->second); - - auto k = value.find(WindowComponent::InfoBar); - _infoBar->setVisible(k->second); - _infoDivider->setVisible(k->second); - - i = value.find(WindowComponent::Tools); + i = value.find(WindowComponent::ToolsPanel); _toolWidget->setVisible(i->second); + + i = value.find(WindowComponent::PlaybackPanel); + _bottomLayout->setVisible(i->second); }); _tooltipsObserver = dtk::ValueObserver::create( diff --git a/lib/toucanView/MainWindow.h b/lib/toucanView/MainWindow.h index d834d56..550cb12 100644 --- a/lib/toucanView/MainWindow.h +++ b/lib/toucanView/MainWindow.h @@ -69,9 +69,7 @@ namespace toucan std::shared_ptr _bottomLayout; std::shared_ptr _playbackBar; std::shared_ptr _timelineWidget; - std::shared_ptr _timelineDivider; std::shared_ptr _infoBar; - std::shared_ptr _infoDivider; std::shared_ptr > > _filesObserver; std::shared_ptr > _addObserver; diff --git a/lib/toucanView/MenuBar.cpp b/lib/toucanView/MenuBar.cpp index a338e0e..1f2bee4 100644 --- a/lib/toucanView/MenuBar.cpp +++ b/lib/toucanView/MenuBar.cpp @@ -671,14 +671,14 @@ namespace toucan WindowComponent component; std::string action; std::string text; + std::string icon; + std::string tooltip; }; const std::vector components = { - { WindowComponent::ToolBar, "ToolBar", "Tool Bar" }, - { WindowComponent::PlaybackBar, "PlaybackBar", "Playback Bar" }, - { WindowComponent::TimelineWidget, "TimelineWidget", "Timeline Widget" }, - { WindowComponent::InfoBar, "InfoBar", "Information Bar" }, - { WindowComponent::Tools, "Tools", "Tools" } + { WindowComponent::ToolBar, "ToolBar", "Tool Bar", "", "" }, + { WindowComponent::ToolsPanel, "ToolsPanel", "Tools Panel", "PanelRight", "Toggle the tools panel" }, + { WindowComponent::PlaybackPanel, "PlaybackPanel", "Playback Panel", "PanelBottom", "Toggle the playback panel" } }; std::weak_ptr appWeak(app); for (const auto& component : components) @@ -686,6 +686,7 @@ namespace toucan const std::string actionName = dtk::Format("Window/{0}").arg(component.action); _actions[actionName] = std::make_shared( component.text, + component.icon, [appWeak, component](bool value) { if (auto app = appWeak.lock()) @@ -693,6 +694,7 @@ namespace toucan app->getWindowModel()->setComponent(component.component, value); } }); + _actions[actionName]->toolTip = component.tooltip; _menus["Window"]->addItem(_actions[actionName]); } @@ -803,14 +805,10 @@ namespace toucan { auto i = value.find(WindowComponent::ToolBar); _menus["Window"]->setItemChecked(_actions["Window/ToolBar"], i->second); - i = value.find(WindowComponent::PlaybackBar); - _menus["Window"]->setItemChecked(_actions["Window/PlaybackBar"], i->second); - i = value.find(WindowComponent::TimelineWidget); - _menus["Window"]->setItemChecked(_actions["Window/TimelineWidget"], i->second); - i = value.find(WindowComponent::InfoBar); - _menus["Window"]->setItemChecked(_actions["Window/InfoBar"], i->second); - i = value.find(WindowComponent::Tools); - _menus["Window"]->setItemChecked(_actions["Window/Tools"], i->second); + i = value.find(WindowComponent::ToolsPanel); + _menus["Window"]->setItemChecked(_actions["Window/ToolsPanel"], i->second); + i = value.find(WindowComponent::PlaybackPanel); + _menus["Window"]->setItemChecked(_actions["Window/PlaybackPanel"], i->second); }); _displayScaleObserver = dtk::ValueObserver::create( diff --git a/lib/toucanView/TimelineWidget.cpp b/lib/toucanView/TimelineWidget.cpp index 25e71da..8bd3ad9 100644 --- a/lib/toucanView/TimelineWidget.cpp +++ b/lib/toucanView/TimelineWidget.cpp @@ -33,6 +33,7 @@ namespace toucan _scrollWidget = dtk::ScrollWidget::create(context, dtk::ScrollType::Both, shared_from_this()); _scrollWidget->setScrollEventsEnabled(false); _scrollWidget->setBorder(false); + _scrollWidget->setScrollBarsVisible(false); auto appWeak = std::weak_ptr(app); _fileObserver = dtk::ValueObserver >::create( diff --git a/lib/toucanView/ToolBar.cpp b/lib/toucanView/ToolBar.cpp index a7b8422..8b2fe36 100644 --- a/lib/toucanView/ToolBar.cpp +++ b/lib/toucanView/ToolBar.cpp @@ -95,6 +95,7 @@ namespace toucan hLayout = dtk::HorizontalLayout::create(context, _layout); hLayout->setSpacingRole(dtk::SizeRole::SpacingTool); + i = actions.find("Window/FullScreen"); button = dtk::ToolButton::create(context, hLayout); button->setIcon(i->second->icon); @@ -110,6 +111,36 @@ namespace toucan }); _buttons["Window/FullScreen"] = button; + i = actions.find("Window/ToolsPanel"); + button = dtk::ToolButton::create(context, hLayout); + button->setIcon(i->second->icon); + button->setCheckable(true); + button->setTooltip(i->second->toolTip); + button->setCheckedCallback( + [i](bool value) + { + if (i->second->checkedCallback) + { + i->second->checkedCallback(value); + } + }); + _buttons["Window/ToolsPanel"] = button; + + i = actions.find("Window/PlaybackPanel"); + button = dtk::ToolButton::create(context, hLayout); + button->setIcon(i->second->icon); + button->setCheckable(true); + button->setTooltip(i->second->toolTip); + button->setCheckedCallback( + [i](bool value) + { + if (i->second->checkedCallback) + { + i->second->checkedCallback(value); + } + }); + _buttons["Window/PlaybackPanel"] = button; + _widgetUpdate(); _filesObserver = dtk::ListObserver >::create( @@ -134,6 +165,16 @@ namespace toucan { _buttons["Window/FullScreen"]->setChecked(value); }); + + _componentObserver = dtk::MapObserver::create( + app->getWindowModel()->observeComponents(), + [this](const std::map value) + { + auto i = value.find(WindowComponent::ToolsPanel); + _buttons["Window/ToolsPanel"]->setChecked(i != value.end() ? i->second : false); + i = value.find(WindowComponent::PlaybackPanel); + _buttons["Window/PlaybackPanel"]->setChecked(i != value.end() ? i->second : false); + }); } ToolBar::~ToolBar() diff --git a/lib/toucanView/ToolBar.h b/lib/toucanView/ToolBar.h index 93ec9de..dd151b5 100644 --- a/lib/toucanView/ToolBar.h +++ b/lib/toucanView/ToolBar.h @@ -3,6 +3,8 @@ #pragma once +#include "WindowModel.h" + #include #include #include @@ -51,8 +53,9 @@ namespace toucan std::shared_ptr > > _filesObserver; std::shared_ptr > > _fileObserver; - std::shared_ptr > _fullScreenObserver; std::shared_ptr > _frameViewObserver; + std::shared_ptr > _fullScreenObserver; + std::shared_ptr > _componentObserver; }; } diff --git a/lib/toucanView/WindowModel.cpp b/lib/toucanView/WindowModel.cpp index 538017f..903495b 100644 --- a/lib/toucanView/WindowModel.cpp +++ b/lib/toucanView/WindowModel.cpp @@ -16,10 +16,8 @@ namespace toucan DTK_ENUM_IMPL( WindowComponent, "ToolBar", - "PlaybackBar", - "TimelineWidget", - "InfoBar", - "Tools"); + "ToolsPanel", + "PlaybackPanel"); WindowModel::WindowModel(const std::shared_ptr& context) { @@ -28,10 +26,8 @@ namespace toucan std::map components = { { WindowComponent::ToolBar, true }, - { WindowComponent::PlaybackBar, true }, - { WindowComponent::TimelineWidget, true }, - { WindowComponent::InfoBar, true }, - { WindowComponent::Tools, true } + { WindowComponent::ToolsPanel, true }, + { WindowComponent::PlaybackPanel, true } }; bool tooltips = true; try diff --git a/lib/toucanView/WindowModel.h b/lib/toucanView/WindowModel.h index e671d09..2263cc1 100644 --- a/lib/toucanView/WindowModel.h +++ b/lib/toucanView/WindowModel.h @@ -13,10 +13,8 @@ namespace toucan enum class WindowComponent { ToolBar, - PlaybackBar, - TimelineWidget, - InfoBar, - Tools, + ToolsPanel, + PlaybackPanel, Count, First = ToolBar From 62b0a6ca5eb58cc6084dbe86f93b25d870cdb785 Mon Sep 17 00:00:00 2001 From: Darby Johnston Date: Thu, 5 Dec 2024 15:19:07 -0800 Subject: [PATCH 08/10] Refactoring Signed-off-by: Darby Johnston --- bin/toucan-render/App.cpp | 2 +- bin/toucan-render/App.h | 2 +- lib/toucan/FFmpeg.cpp | 12 +++ lib/toucan/FFmpeg.h | 4 + lib/toucanView/ExportTool.cpp | 169 ++++++++++++++++++++++++++++++---- lib/toucanView/ExportTool.h | 9 +- 6 files changed, 174 insertions(+), 24 deletions(-) diff --git a/bin/toucan-render/App.cpp b/bin/toucan-render/App.cpp index 5e3ba05..8802969 100644 --- a/bin/toucan-render/App.cpp +++ b/bin/toucan-render/App.cpp @@ -52,7 +52,7 @@ namespace toucan auto outArg = std::make_shared >( _args.output, "output", - "Output image file. Use a dash ('-') to write raw frames or y4m to stdout."); + "Output image or movie file. Use a dash ('-') to write raw frames or y4m to stdout."); _args.list.push_back(outArg); std::vector rawList; diff --git a/bin/toucan-render/App.h b/bin/toucan-render/App.h index 820c1b0..3036ce1 100644 --- a/bin/toucan-render/App.h +++ b/bin/toucan-render/App.h @@ -47,7 +47,7 @@ namespace toucan struct Options { - std::string videoCodec = "mjpeg"; + std::string videoCodec = "MJPEG"; bool printStart = false; bool printDuration = false; bool printRate = false; diff --git a/lib/toucan/FFmpeg.cpp b/lib/toucan/FFmpeg.cpp index 498aa90..0fb1498 100644 --- a/lib/toucan/FFmpeg.cpp +++ b/lib/toucan/FFmpeg.cpp @@ -57,17 +57,29 @@ namespace toucan const std::vector videoCodecStrings = { "MJPEG", + "V210", + "V308", + "V408", + "V410", "AV1" }; const std::vector videoCodecIds = { AV_CODEC_ID_MJPEG, + AV_CODEC_ID_V210, + AV_CODEC_ID_V308, + AV_CODEC_ID_V408, + AV_CODEC_ID_V410, AV_CODEC_ID_AV1 }; const std::vector videoCodecProfiles = { + AV_PROFILE_UNKNOWN, + AV_PROFILE_UNKNOWN, + AV_PROFILE_UNKNOWN, + AV_PROFILE_UNKNOWN, AV_PROFILE_UNKNOWN, AV_PROFILE_AV1_MAIN }; diff --git a/lib/toucan/FFmpeg.h b/lib/toucan/FFmpeg.h index d28d16f..5f2207f 100644 --- a/lib/toucan/FFmpeg.h +++ b/lib/toucan/FFmpeg.h @@ -30,6 +30,10 @@ namespace toucan enum class VideoCodec { MJPEG, + V210, + V308, + V408, + V410, AV1, Count, diff --git a/lib/toucanView/ExportTool.cpp b/lib/toucanView/ExportTool.cpp index 959e856..ab7fd02 100644 --- a/lib/toucanView/ExportTool.cpp +++ b/lib/toucanView/ExportTool.cpp @@ -11,7 +11,13 @@ #include #include +#include #include +#include + +#include + +#include namespace toucan { @@ -23,15 +29,64 @@ namespace toucan IWidget::_init(context, "toucan::ExportWidget", parent); _host = app->getHost(); - _imageExtensions = - { - ".tiff", - ".png", - ".exr" - }; - _movieExtensions = ffmpeg::getVideoExtensions(); _movieCodecs = ffmpeg::getVideoCodecStrings(); + std::string outputPath; + int currentTab = 0; + std::string imageBaseName = "render."; + int imagePadding = 0; + std::string imageExtension = ".tiff"; + std::string movieBaseName = "render"; + std::string movieExtension = ".mov"; + std::string movieCodec = "MJPEG"; + try + { + auto settings = context->getSystem(); + const auto json = std::any_cast(settings->get("ExportWidget")); + auto i = json.find("OutputPath"); + if (i != json.end() && i->is_string()) + { + outputPath = i->get(); + } + i = json.find("CurrentTab"); + if (i != json.end() && i->is_number()) + { + currentTab = i->get(); + } + i = json.find("ImageBaseName"); + if (i != json.end() && i->is_string()) + { + imageBaseName = i->get(); + } + i = json.find("ImagePadding"); + if (i != json.end() && i->is_number()) + { + imagePadding = i->get(); + } + i = json.find("ImageExtension"); + if (i != json.end() && i->is_string()) + { + imageExtension = i->get(); + } + i = json.find("MovieBaseName"); + if (i != json.end() && i->is_string()) + { + movieBaseName = i->get(); + } + i = json.find("MovieExtension"); + if (i != json.end() && i->is_string()) + { + movieExtension = i->get(); + } + i = json.find("MovieCodec"); + if (i != json.end() && i->is_string()) + { + movieCodec = i->get(); + } + } + catch (const std::exception&) + {} + _layout = dtk::VerticalLayout::create(context, shared_from_this()); _layout->setSpacingRole(dtk::SizeRole::None); @@ -39,6 +94,7 @@ namespace toucan _outputLayout->setMarginRole(dtk::SizeRole::Margin); auto label = dtk::Label::create(context, "Output directory:", _outputLayout); _outputPathEdit = dtk::FileEdit::create(context, _outputLayout); + _outputPathEdit->setPath(outputPath); auto divider = dtk::Divider::create(context, dtk::Orientation::Vertical, _layout); @@ -53,19 +109,26 @@ namespace toucan label = dtk::Label::create(context, "Base name:", gridLayout); gridLayout->setGridPos(label, 0, 0); _imageBaseNameEdit = dtk::LineEdit::create(context, gridLayout); - _imageBaseNameEdit->setText("render."); + _imageBaseNameEdit->setText(imageBaseName); gridLayout->setGridPos(_imageBaseNameEdit, 0, 1); label = dtk::Label::create(context, "Number padding:", gridLayout); gridLayout->setGridPos(label, 1, 0); _imagePaddingEdit = dtk::IntEdit::create(context, gridLayout); _imagePaddingEdit->setRange(dtk::RangeI(0, 9)); + _imagePaddingEdit->setValue(imagePadding); gridLayout->setGridPos(_imagePaddingEdit, 1, 1); label = dtk::Label::create(context, "Extension:", gridLayout); gridLayout->setGridPos(label, 2, 0); - _imageExtensionComboBox = dtk::ComboBox::create(context, _imageExtensions, gridLayout); - gridLayout->setGridPos(_imageExtensionComboBox, 2, 1); + _imageExtensionEdit = dtk::LineEdit::create(context, gridLayout); + _imageExtensionEdit->setText(imageExtension); + gridLayout->setGridPos(_imageExtensionEdit, 2, 1); + + label = dtk::Label::create(context, "Filename:", gridLayout); + gridLayout->setGridPos(label, 3, 0); + _imageFilenameLabel = dtk::Label::create(context, gridLayout); + gridLayout->setGridPos(_imageFilenameLabel, 3, 1); divider = dtk::Divider::create(context, dtk::Orientation::Vertical, _imageLayout); @@ -82,24 +145,34 @@ namespace toucan _movieLayout = dtk::VerticalLayout::create(context); _movieLayout->setMarginRole(dtk::SizeRole::Margin); _tabWidget->addTab("Movie", _movieLayout); + _tabWidget->setCurrentTab(currentTab); gridLayout = dtk::GridLayout::create(context, _movieLayout); label = dtk::Label::create(context, "Base name:", gridLayout); gridLayout->setGridPos(label, 0, 0); _movieBaseNameEdit = dtk::LineEdit::create(context, gridLayout); - _movieBaseNameEdit->setText("render"); + _movieBaseNameEdit->setText(movieBaseName); gridLayout->setGridPos(_movieBaseNameEdit, 0, 1); label = dtk::Label::create(context, "Extension:", gridLayout); gridLayout->setGridPos(label, 1, 0); - _movieExtensionComboBox = dtk::ComboBox::create(context, _movieExtensions, gridLayout); - gridLayout->setGridPos(_movieExtensionComboBox, 1, 1); + _movieExtensionEdit = dtk::LineEdit::create(context, gridLayout); + _movieExtensionEdit->setText(movieExtension); + gridLayout->setGridPos(_movieExtensionEdit, 1, 1); - label = dtk::Label::create(context, "Codec:", gridLayout); + label = dtk::Label::create(context, "Filename:", gridLayout); gridLayout->setGridPos(label, 2, 0); + _movieFilenameLabel = dtk::Label::create(context, gridLayout); + gridLayout->setGridPos(_movieFilenameLabel, 2, 1); + + label = dtk::Label::create(context, "Codec:", gridLayout); + gridLayout->setGridPos(label, 3, 0); _movieCodecComboBox = dtk::ComboBox::create(context, _movieCodecs, gridLayout); - gridLayout->setGridPos(_movieCodecComboBox, 2, 1); + ffmpeg::VideoCodec ffmpegVideoCodec = ffmpeg::VideoCodec::First; + ffmpeg::fromString(movieCodec, ffmpegVideoCodec); + _movieCodecComboBox->setCurrentIndex(static_cast(ffmpegVideoCodec)); + gridLayout->setGridPos(_movieCodecComboBox, 3, 1); divider = dtk::Divider::create(context, dtk::Orientation::Vertical, _movieLayout); @@ -107,6 +180,38 @@ namespace toucan context, "Export Movie", _movieLayout); + + _widgetUpdate(); + + _imageBaseNameEdit->setTextChangedCallback( + [this](const std::string&) + { + _widgetUpdate(); + }); + + _imagePaddingEdit->setCallback( + [this](int) + { + _widgetUpdate(); + }); + + _imageExtensionEdit->setTextChangedCallback( + [this](const std::string&) + { + _widgetUpdate(); + }); + + _movieBaseNameEdit->setTextChangedCallback( + [this](const std::string&) + { + _widgetUpdate(); + }); + + _movieExtensionEdit->setTextChangedCallback( + [this](const std::string&) + { + _widgetUpdate(); + }); _exportSequenceButton->setClickedCallback( [this] @@ -129,7 +234,7 @@ namespace toucan { _timeRange = _file->getPlaybackModel()->getInOutRange(); const std::string baseName = _movieBaseNameEdit->getText(); - const std::string extension = _movieExtensions[_movieExtensionComboBox->getCurrentIndex()]; + const std::string extension = _movieExtensionEdit->getText(); const std::filesystem::path path = _outputPathEdit->getPath() / (baseName + extension); const IMATH_NAMESPACE::V2d imageSize = _graph->getImageSize(); ffmpeg::VideoCodec videoCodec = ffmpeg::VideoCodec::First; @@ -177,7 +282,21 @@ namespace toucan } ExportWidget::~ExportWidget() - {} + { + nlohmann::json json; + json["OutputPath"] = _outputPathEdit->getPath().string(); + json["CurrentTab"] = _tabWidget->getCurrentTab(); + json["ImageBaseName"] = _imageBaseNameEdit->getText(); + json["ImagePadding"] = _imagePaddingEdit->getValue(); + json["ImageExtension"] = _imageExtensionEdit->getText(); + json["MovieBaseName"] = _movieBaseNameEdit->getText(); + json["MovieExtension"] = _movieExtensionEdit->getText(); + json["MovieCodec"] = ffmpeg::toString( + static_cast(_movieCodecComboBox->getCurrentIndex())); + auto context = getContext(); + auto settings = context->getSystem(); + settings->set("ExportWidget", json); + } std::shared_ptr ExportWidget::create( const std::shared_ptr& context, @@ -249,7 +368,7 @@ namespace toucan _imageBaseNameEdit->getText(), _time.to_frames(), _imagePaddingEdit->getValue(), - _imageExtensions[_imageExtensionComboBox->getCurrentIndex()]); + _imageExtensionEdit->getText()); buf.write(fileName); } } @@ -274,6 +393,20 @@ namespace toucan }); } + void ExportWidget::_widgetUpdate() + { + _imageFilenameLabel->setText(getSequenceFrame( + _outputPathEdit->getPath().string(), + _imageBaseNameEdit->getText(), + 0, + _imagePaddingEdit->getValue(), + _imageExtensionEdit->getText())); + + _movieFilenameLabel->setText( + _movieBaseNameEdit->getText() + + _movieExtensionEdit->getText()); + } + void ExportTool::_init( const std::shared_ptr& context, const std::shared_ptr& app, diff --git a/lib/toucanView/ExportTool.h b/lib/toucanView/ExportTool.h index 2aba2e8..1d5dd08 100644 --- a/lib/toucanView/ExportTool.h +++ b/lib/toucanView/ExportTool.h @@ -48,14 +48,13 @@ namespace toucan private: void _export(); + void _widgetUpdate(); std::shared_ptr _host; std::shared_ptr _file; OTIO_NS::TimeRange _timeRange; OTIO_NS::RationalTime _time; std::shared_ptr _graph; - std::vector _imageExtensions; - std::vector _movieExtensions; std::vector _movieCodecs; std::shared_ptr _ffWrite; @@ -66,13 +65,15 @@ namespace toucan std::shared_ptr _imageLayout; std::shared_ptr _imageBaseNameEdit; std::shared_ptr _imagePaddingEdit; - std::shared_ptr _imageExtensionComboBox; + std::shared_ptr _imageExtensionEdit; + std::shared_ptr _imageFilenameLabel; std::shared_ptr _exportSequenceButton; std::shared_ptr _exportStillButton; std::shared_ptr _movieLayout; std::shared_ptr _movieBaseNameEdit; - std::shared_ptr _movieExtensionComboBox; + std::shared_ptr _movieExtensionEdit; std::shared_ptr _movieCodecComboBox; + std::shared_ptr _movieFilenameLabel; std::shared_ptr _exportMovieButton; std::shared_ptr _dialog; From 75156d36b0a26679d3a5b78afccf05128458d2dd Mon Sep 17 00:00:00 2001 From: Darby Johnston Date: Thu, 5 Dec 2024 15:19:19 -0800 Subject: [PATCH 09/10] Documentation updates Signed-off-by: Darby Johnston --- README.md | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 4ab879f..3f96f29 100644 --- a/README.md +++ b/README.md @@ -2,24 +2,31 @@ toucan + Toucan ====== Toucan is a software renderer for OpenTimelineIO files. Toucan can render an OpenTimelineIO file with multiple tracks, clips, transitions, and effects into an image sequence or movie file. -The project currently consists of: +The project consists of: * C++ library for rendering timelines * Collection of OpenFX image effect plugins * Command line renderer * Interactive viewer * Example .otio files -Current limitations: -* Audio is not yet supported -* Nested timelines are not yet supported -* Exporting movie files currently relies on the FFmpeg command line program -(see below: FFmpeg Encoding) +OpenFX Plugins: +* Generators: Checkers, Fill, Gradient, Noise +* Drawing: Box, Line, Text +* Filters: Blur, Color Map, Invert, Power, Saturate, Unsharp Mask +* Transforms: Crop, Flip, Flop, Resize, Rotate +* Transitions: Dissolve, Horizontal Wipe, Vertical Wipe +* Color space: Color Convert, Premultiply Alpha, Un-Premultiply Alpha + +TODO: +* Audio support +* Nested timeline support Toucan relies on the following libraries: * [OpenTimelineIO](https://github.com/PixarAnimationStudios/OpenTimelineIO) @@ -31,15 +38,6 @@ Toucan relies on the following libraries: Supported VFX platforms: 2024, 2023, 2022 -OpenFX Plugins -============== -The OpenFX image effect plugins include: -* Generators: Checkers, Fill, Gradient, Noise -* Drawing: Box, Line, Text -* Filters: Blur, Color Map, Invert, Power, Saturate, Unsharp Mask -* Transforms: Crop, Flip, Flop, Resize, Rotate -* Transitions: Dissolve, Horizontal Wipe, Vertical Wipe -* Color spaces: Color Convert, Premultiply Alpha, Un-Premultiply Alpha Example Renders =============== @@ -91,11 +89,18 @@ Multiple effects on clips, tracks, and stacks: ![Track Effects](images/MultipleEffects.png) + FFmpeg Encoding =============== -Toucan can send rendered images to the FFmpeg command line program for encoding. -The images can be sent as either the y4m format or raw video. The images are -piped directly to FFmpeg without the overhead of disk I/O. +Toucan can write movies with FFmpeg directly, or send raw images to the FFmpeg +command line program over a pipe. + +Example command line writing a movie directly: +``` +toucan-render Transition.otio Transition.mov -vcodec MJPEG +``` + +Raw images can be sent to FFmpeg as either the y4m format or raw video. Example command line using the y4m format: ``` @@ -127,6 +132,7 @@ can be found by running `toucan-render` with the `-print_size` option. * `-i pipe:`: Read from standard input instead of a file. * `output.mov`: The output movie file. + Building ======== From 329f939fa3713b2bf411a0a01bd8dfd1103bd40e Mon Sep 17 00:00:00 2001 From: Darby Johnston Date: Thu, 5 Dec 2024 16:03:36 -0800 Subject: [PATCH 10/10] Build fixes Signed-off-by: Darby Johnston --- cmake/Package.cmake | 24 ++++++++++++------------ cmake/SuperBuild/BuildFFmpeg.cmake | 22 +++++++++++----------- cmake/SuperBuild/CMakeLists.txt | 6 +++++- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/cmake/Package.cmake b/cmake/Package.cmake index a2a21db..4577e12 100644 --- a/cmake/Package.cmake +++ b/cmake/Package.cmake @@ -25,22 +25,22 @@ elseif(APPLE) #set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) set(INSTALL_DYLIBS - ${CMAKE_INSTALL_PREFIX}/lib/libavcodec.61.3.100.dylib + ${CMAKE_INSTALL_PREFIX}/lib/libavcodec.61.19.100.dylib ${CMAKE_INSTALL_PREFIX}/lib/libavcodec.61.dylib ${CMAKE_INSTALL_PREFIX}/lib/libavcodec.dylib - ${CMAKE_INSTALL_PREFIX}/lib/libavdevice.61.1.100.dylib + ${CMAKE_INSTALL_PREFIX}/lib/libavdevice.61.3.100.dylib ${CMAKE_INSTALL_PREFIX}/lib/libavdevice.61.dylib ${CMAKE_INSTALL_PREFIX}/lib/libavdevice.dylib - ${CMAKE_INSTALL_PREFIX}/lib/libavformat.61.1.100.dylib + ${CMAKE_INSTALL_PREFIX}/lib/libavformat.61.7.100.dylib ${CMAKE_INSTALL_PREFIX}/lib/libavformat.61.dylib ${CMAKE_INSTALL_PREFIX}/lib/libavformat.dylib - ${CMAKE_INSTALL_PREFIX}/lib/libavutil.59.8.100.dylib + ${CMAKE_INSTALL_PREFIX}/lib/libavutil.59.39.100.dylib ${CMAKE_INSTALL_PREFIX}/lib/libavutil.59.dylib ${CMAKE_INSTALL_PREFIX}/lib/libavutil.dylib - ${CMAKE_INSTALL_PREFIX}/lib/libswresample.5.1.100.dylib + ${CMAKE_INSTALL_PREFIX}/lib/libswresample.5.3.100.dylib ${CMAKE_INSTALL_PREFIX}/lib/libswresample.5.dylib ${CMAKE_INSTALL_PREFIX}/lib/libswresample.dylib - ${CMAKE_INSTALL_PREFIX}/lib/libswscale.8.1.100.dylib + ${CMAKE_INSTALL_PREFIX}/lib/libswscale.8.3.100.dylib ${CMAKE_INSTALL_PREFIX}/lib/libswscale.8.dylib ${CMAKE_INSTALL_PREFIX}/lib/libswscale.dylib) @@ -55,22 +55,22 @@ else() set(INSTALL_LIBS ${CMAKE_INSTALL_PREFIX}/lib/libavcodec.so ${CMAKE_INSTALL_PREFIX}/lib/libavcodec.so.61 - ${CMAKE_INSTALL_PREFIX}/lib/libavcodec.so.61.3.100 + ${CMAKE_INSTALL_PREFIX}/lib/libavcodec.so.61.19.100 ${CMAKE_INSTALL_PREFIX}/lib/libavdevice.so ${CMAKE_INSTALL_PREFIX}/lib/libavdevice.so.61 - ${CMAKE_INSTALL_PREFIX}/lib/libavdevice.so.61.1.100 + ${CMAKE_INSTALL_PREFIX}/lib/libavdevice.so.61.3.100 ${CMAKE_INSTALL_PREFIX}/lib/libavformat.so ${CMAKE_INSTALL_PREFIX}/lib/libavformat.so.61 - ${CMAKE_INSTALL_PREFIX}/lib/libavformat.so.61.1.100 + ${CMAKE_INSTALL_PREFIX}/lib/libavformat.so.61.7.100 ${CMAKE_INSTALL_PREFIX}/lib/libavutil.so ${CMAKE_INSTALL_PREFIX}/lib/libavutil.so.59 - ${CMAKE_INSTALL_PREFIX}/lib/libavutil.so.59.8.100 + ${CMAKE_INSTALL_PREFIX}/lib/libavutil.so.59.39.100 ${CMAKE_INSTALL_PREFIX}/lib/libswresample.so ${CMAKE_INSTALL_PREFIX}/lib/libswresample.so.5 - ${CMAKE_INSTALL_PREFIX}/lib/libswresample.so.5.1.100 + ${CMAKE_INSTALL_PREFIX}/lib/libswresample.so.5.3.100 ${CMAKE_INSTALL_PREFIX}/lib/libswscale.so ${CMAKE_INSTALL_PREFIX}/lib/libswscale.so.8 - ${CMAKE_INSTALL_PREFIX}/lib/libswscale.so.8.1.100) + ${CMAKE_INSTALL_PREFIX}/lib/libswscale.so.8.3.100) install( FILES ${INSTALL_LIBS} diff --git a/cmake/SuperBuild/BuildFFmpeg.cmake b/cmake/SuperBuild/BuildFFmpeg.cmake index 0f14bd9..072afee 100644 --- a/cmake/SuperBuild/BuildFFmpeg.cmake +++ b/cmake/SuperBuild/BuildFFmpeg.cmake @@ -379,34 +379,34 @@ else() set(FFmpeg_INSTALL make install) if(APPLE) list(APPEND FFmpeg_INSTALL - COMMAND install_name_tool -id @rpath/libavcodec.61.3.100.dylib ${CMAKE_INSTALL_PREFIX}/lib/libavcodec.61.dylib - COMMAND install_name_tool -id @rpath/libavdevice.61.1.100.dylib ${CMAKE_INSTALL_PREFIX}/lib/libavdevice.61.dylib - COMMAND install_name_tool -id @rpath/libavformat.61.1.100.dylib ${CMAKE_INSTALL_PREFIX}/lib/libavformat.61.dylib - COMMAND install_name_tool -id @rpath/libavutil.59.8.100.dylib ${CMAKE_INSTALL_PREFIX}/lib/libavutil.59.dylib - COMMAND install_name_tool -id @rpath/libswresample.5.1.100.dylib ${CMAKE_INSTALL_PREFIX}/lib/libswresample.5.dylib - COMMAND install_name_tool -id @rpath/libswscale.8.1.100.dylib ${CMAKE_INSTALL_PREFIX}/lib/libswscale.8.dylib + COMMAND install_name_tool -id @rpath/libavcodec.61.19.100.dylib ${CMAKE_INSTALL_PREFIX}/lib/libavcodec.61.dylib + COMMAND install_name_tool -id @rpath/libavdevice.61.3.100.dylib ${CMAKE_INSTALL_PREFIX}/lib/libavdevice.61.dylib + COMMAND install_name_tool -id @rpath/libavformat.61.7.100.dylib ${CMAKE_INSTALL_PREFIX}/lib/libavformat.61.dylib + COMMAND install_name_tool -id @rpath/libavutil.59.39.100.dylib ${CMAKE_INSTALL_PREFIX}/lib/libavutil.59.dylib + COMMAND install_name_tool -id @rpath/libswresample.5.3.100.dylib ${CMAKE_INSTALL_PREFIX}/lib/libswresample.5.dylib + COMMAND install_name_tool -id @rpath/libswscale.8.3.100.dylib ${CMAKE_INSTALL_PREFIX}/lib/libswscale.8.dylib COMMAND install_name_tool -change ${CMAKE_INSTALL_PREFIX}/lib/libswresample.5.dylib @rpath/libswresample.5.dylib -change ${CMAKE_INSTALL_PREFIX}/lib/libavutil.59.dylib @rpath/libavutil.59.dylib - ${CMAKE_INSTALL_PREFIX}/lib/libavcodec.61.3.100.dylib + ${CMAKE_INSTALL_PREFIX}/lib/libavcodec.61.19.100.dylib COMMAND install_name_tool -change ${CMAKE_INSTALL_PREFIX}/lib/libswscale.8.dylib @rpath/libswscale.8.dylib -change ${CMAKE_INSTALL_PREFIX}/lib/libavformat.61.dylib @rpath/libavformat.61.dylib -change ${CMAKE_INSTALL_PREFIX}/lib/libavcodec.61.dylib @rpath/libavcodec.61.dylib -change ${CMAKE_INSTALL_PREFIX}/lib/libswresample.5.dylib @rpath/libswresample.5.dylib -change ${CMAKE_INSTALL_PREFIX}/lib/libavutil.59.dylib @rpath/libavutil.59.dylib - ${CMAKE_INSTALL_PREFIX}/lib/libavdevice.61.1.100.dylib + ${CMAKE_INSTALL_PREFIX}/lib/libavdevice.61.3.100.dylib COMMAND install_name_tool -change ${CMAKE_INSTALL_PREFIX}/lib/libavcodec.61.dylib @rpath/libavcodec.61.dylib -change ${CMAKE_INSTALL_PREFIX}/lib/libswresample.5.dylib @rpath/libswresample.5.dylib -change ${CMAKE_INSTALL_PREFIX}/lib/libavutil.59.dylib @rpath/libavutil.59.dylib - ${CMAKE_INSTALL_PREFIX}/lib/libavformat.61.1.100.dylib + ${CMAKE_INSTALL_PREFIX}/lib/libavformat.61.7.100.dylib COMMAND install_name_tool -change ${CMAKE_INSTALL_PREFIX}/lib/libavutil.59.dylib @rpath/libavutil.59.dylib - ${CMAKE_INSTALL_PREFIX}/lib/libswresample.5.1.100.dylib + ${CMAKE_INSTALL_PREFIX}/lib/libswresample.5.3.100.dylib COMMAND install_name_tool -change ${CMAKE_INSTALL_PREFIX}/lib/libavutil.59.dylib @rpath/libavutil.59.dylib - ${CMAKE_INSTALL_PREFIX}/lib/libswscale.8.1.100.dylib) + ${CMAKE_INSTALL_PREFIX}/lib/libswscale.8.3.100.dylib) endif() endif() diff --git a/cmake/SuperBuild/CMakeLists.txt b/cmake/SuperBuild/CMakeLists.txt index 4db7b1a..2372537 100644 --- a/cmake/SuperBuild/CMakeLists.txt +++ b/cmake/SuperBuild/CMakeLists.txt @@ -15,7 +15,11 @@ set(toucan_JPEG ON CACHE BOOL "Build JPEG") set(toucan_TIFF ON CACHE BOOL "Build TIFF") set(toucan_Imath ON CACHE BOOL "Build Imath") set(toucan_OpenEXR ON CACHE BOOL "Build OpenEXR") -set(toucan_svt-av1 ON CACHE BOOL "Build SVT-AV1") +set(toucan_svt-av1_DEFAULT OFF) +if(NOT WIN32 AND NOT APPLE) + set(toucan_svt-av1_DEFAULT ON) +endif() +set(toucan_svt-av1 ${toucan_svt-av1_DEFAULT} CACHE BOOL "Build SVT-AV1") set(toucan_FFmpeg ON CACHE BOOL "Build FFmpeg") set(toucan_FFmpeg_MINIMAL ON CACHE BOOL "Build a minimal set of FFmpeg codecs") set(toucan_OpenColorIO ON CACHE BOOL "Build OpenColorIO")