From bb6d5505f50d0430563adf95710902d3705991bd Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 25 Sep 2020 16:32:18 +1000 Subject: [PATCH] otio: began early functionality to export to OTIO --- CMakeLists.txt | 6 +- app/CMakeLists.txt | 19 ++- app/common/filefunctions.cpp | 17 +++ app/common/filefunctions.h | 11 ++ app/common/rational.cpp | 8 ++ app/common/rational.h | 8 ++ app/core.cpp | 24 +++- app/core.h | 7 ++ app/task/project/CMakeLists.txt | 1 + app/task/project/saveotio/CMakeLists.txt | 22 ++++ .../saveotio/projectsaveasotiotask.cpp | 111 ++++++++++++++++++ .../project/saveotio/projectsaveasotiotask.h | 45 +++++++ app/window/mainwindow/mainmenu.cpp | 13 +- app/window/mainwindow/mainmenu.h | 6 +- 14 files changed, 288 insertions(+), 10 deletions(-) create mode 100644 app/task/project/saveotio/CMakeLists.txt create mode 100644 app/task/project/saveotio/projectsaveasotiotask.cpp create mode 100644 app/task/project/saveotio/projectsaveasotiotask.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 06a282d31..c37dc260f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,7 +69,11 @@ find_package(FFMPEG 3.0 REQUIRED swresample ) -find_package(OpenTimelineIO REQUIRED) +find_package(OpenTimelineIO) + +if (NOT OpenTimelineIO_FOUND) + message(" OpenTimelineIO interchange will be disabled.") +endif() find_package(GoogleCrashpad) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 089a41f04..e0e98d7f9 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -123,7 +123,6 @@ target_include_directories( ${FFMPEG_INCLUDE_DIRS} ${OCIO_INCLUDE_DIRS} ${OIIO_INCLUDE_DIRS} - ${OTIO_INCLUDE_DIRS} ${OPENEXR_INCLUDES} # HACK: Brew on macOS separates OpenEXR and IlmBase into two folders even though they seem to @@ -152,7 +151,6 @@ target_link_libraries( FFMPEG::swresample ${OCIO_LIBRARIES} ${OIIO_LIBRARIES} - ${OTIO_LIBRARIES} ${OPENEXR_LIBRARIES} ) @@ -170,6 +168,23 @@ elseif (APPLE) ) endif() +# Enable OTIO if found +if (OpenTimelineIO_FOUND) + set(OLIVE_DEFINITIONS ${OLIVE_DEFINITIONS} USE_OTIO) + + target_include_directories( + ${OLIVE_TARGET} + PRIVATE + ${OTIO_INCLUDE_DIRS} + ) + + target_link_libraries( + ${OLIVE_TARGET} + PRIVATE + ${OTIO_LIBRARIES} + ) +endif() + # Enable Crashpad if found if (GoogleCrashpad_FOUND) set(OLIVE_DEFINITIONS ${OLIVE_DEFINITIONS} USE_CRASHPAD) diff --git a/app/common/filefunctions.cpp b/app/common/filefunctions.cpp index f087dd90b..23f3abf99 100644 --- a/app/common/filefunctions.cpp +++ b/app/common/filefunctions.cpp @@ -170,4 +170,21 @@ bool FileFunctions::DirectoryIsValid(const QString &dir, bool try_to_create) return false; } +QString FileFunctions::EnsureFilenameExtension(QString fn, const QString &extension) +{ + // No-op if either input is empty + if (!fn.isEmpty() && !extension.isEmpty()) { + QString extension_with_dot; + + extension_with_dot.append('.'); + extension_with_dot.append(extension); + + if (!fn.endsWith(extension_with_dot, Qt::CaseInsensitive)) { + fn.append(extension_with_dot); + } + } + + return fn; +} + OLIVE_NAMESPACE_EXIT diff --git a/app/common/filefunctions.h b/app/common/filefunctions.h index fcfb875be..87bcc6375 100644 --- a/app/common/filefunctions.h +++ b/app/common/filefunctions.h @@ -54,6 +54,17 @@ public: static bool DirectoryIsValid(const QString& dir, bool try_to_create); + /** + * @brief Ensures a given filename has a certain extension + * + * Checks if the filename has the extension provided and appends it if not. The extension is + * checked case-insensitive. The extension should be provided with no dot (e.g. "ove" rather than + * ".ove"). + + * @return The filename provided either untouched or with the extension appended to it. + */ + static QString EnsureFilenameExtension(QString fn, const QString& extension); + }; diff --git a/app/common/rational.cpp b/app/common/rational.cpp index 30463fa7b..2160e3f0e 100644 --- a/app/common/rational.cpp +++ b/app/common/rational.cpp @@ -102,6 +102,14 @@ AVRational rational::toAVRational() const return r; } +#ifdef USE_OTIO +opentime::RationalTime rational::toRationalTime() const +{ + // Is this the best way of doing this? + return opentime::RationalTime::from_seconds(toDouble()); +} +#endif + rational rational::flipped() const { return rational(denom_, numer_); diff --git a/app/common/rational.h b/app/common/rational.h index 9cd3d861a..358ea9697 100644 --- a/app/common/rational.h +++ b/app/common/rational.h @@ -7,6 +7,10 @@ #define RATIONAL_H #include +#ifdef USE_OTIO +#include +#endif + #include #include @@ -96,6 +100,10 @@ public: AVRational toAVRational() const; +#ifdef USE_OTIO + opentime::RationalTime toRationalTime() const; +#endif + // Produce "flipped" version rational flipped() const; diff --git a/app/core.cpp b/app/core.cpp index 5ba51ee45..05b636db9 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -54,6 +54,7 @@ #include "task/project/import/importerrordialog.h" #include "task/project/load/load.h" #include "task/project/save/save.h" +#include "task/project/saveotio/projectsaveasotiotask.h" #include "task/taskmanager.h" #include "ui/style/style.h" #include "undo/undostack.h" @@ -265,6 +266,24 @@ void Core::CreateNewProject() AddOpenProject(std::make_shared()); } +#ifdef USE_OTIO +void Core::ExportActiveSequenceAsOTIO() +{ + QString fn = QFileDialog::getSaveFileName(main_window_, + tr("Export as OpenTimelineIO"), + QString(), + tr("OpenTimelineIO (*.otio)")); + + if (!fn.isEmpty()) { + fn = FileFunctions::EnsureFilenameExtension(fn, QStringLiteral("otio")); + + ProjectSaveAsOTIOTask* task = new ProjectSaveAsOTIOTask(); + TaskDialog* dialog = new TaskDialog(task, tr("Export Sequence"), main_window_); + dialog->open(); + } +} +#endif + const bool &Core::snapping() const { return snapping_; @@ -858,10 +877,7 @@ bool Core::SaveProjectAs(ProjectPtr p) GetProjectFilter()); if (!fn.isEmpty()) { - QString extension(QStringLiteral(".ove")); - if (!fn.endsWith(extension, Qt::CaseInsensitive)) { - fn.append(extension); - } + fn = FileFunctions::EnsureFilenameExtension(fn, QStringLiteral("ove")); p->set_filename(fn); diff --git a/app/core.h b/app/core.h index 71fa43b75..bccc1c81d 100644 --- a/app/core.h +++ b/app/core.h @@ -378,6 +378,13 @@ public slots: */ void CreateNewProject(); +#ifdef USE_OTIO + /** + * @brief Exports the active sequence to OpenTimelineIO + */ + void ExportActiveSequenceAsOTIO(); +#endif + signals: /** * @brief Signal emitted when a project is opened diff --git a/app/task/project/CMakeLists.txt b/app/task/project/CMakeLists.txt index 5f4dcd4c0..4b7bab31f 100644 --- a/app/task/project/CMakeLists.txt +++ b/app/task/project/CMakeLists.txt @@ -17,6 +17,7 @@ add_subdirectory(import) add_subdirectory(load) add_subdirectory(save) +add_subdirectory(saveotio) set(OLIVE_SOURCES ${OLIVE_SOURCES} diff --git a/app/task/project/saveotio/CMakeLists.txt b/app/task/project/saveotio/CMakeLists.txt new file mode 100644 index 000000000..033c7457e --- /dev/null +++ b/app/task/project/saveotio/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2019 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + task/project/saveotio/projectsaveasotiotask.h + task/project/saveotio/projectsaveasotiotask.cpp + PARENT_SCOPE +) diff --git a/app/task/project/saveotio/projectsaveasotiotask.cpp b/app/task/project/saveotio/projectsaveasotiotask.cpp new file mode 100644 index 000000000..cc52260a0 --- /dev/null +++ b/app/task/project/saveotio/projectsaveasotiotask.cpp @@ -0,0 +1,111 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "projectsaveasotiotask.h" + +#include +#include +#include +#include + +#include "node/block/transition/transition.h" + +OLIVE_NAMESPACE_ENTER + +ProjectSaveAsOTIOTask::ProjectSaveAsOTIOTask(Sequence *sequence) : + sequence_(sequence) +{ + SetTitle(tr("Saving '%1' as OpenTimelineIO").arg(sequence->name())); +} + +bool ProjectSaveAsOTIOTask::Run() +{ + auto otio_timeline = new opentimelineio::v1_0::Timeline(); + + ViewerOutput* viewer = sequence_->viewer_output(); + + opentimelineio::v1_0::ErrorStatus es; + + foreach (TrackOutput* track, viewer->track_list(Timeline::kTrackTypeVideo)->GetTracks()) { + auto otio_track = new opentimelineio::v1_0::Track(); + + otio_track->set_kind("Video"); + + foreach (Block* block, track->Blocks()) { + opentimelineio::v1_0::Composable* otio_block = nullptr; + + switch (block->type()) { + case Block::kClip: + { + auto otio_clip = new opentimelineio::v1_0::Clip(); + + otio_clip->set_source_range(opentimelineio::v1_0::TimeRange(block->in().toRationalTime(), + block->length().toRationalTime())); + + otio_block = otio_clip; + break; + } + case Block::kGap: + { + auto otio_gap = new opentimelineio::v1_0::Gap(); + + otio_gap->set_source_range(opentimelineio::v1_0::TimeRange(block->in().toRationalTime(), + block->length().toRationalTime())); + + otio_block = otio_gap; + break; + } + case Block::kTransition: + { + auto otio_transition = new opentimelineio::v1_0::Transition(); + + TransitionBlock* our_transition = static_cast(block); + + otio_transition->set_in_offset(our_transition->in_offset().toRationalTime()); + otio_transition->set_out_offset(our_transition->out_offset().toRationalTime()); + + otio_block = new opentimelineio::v1_0::Transition(); + break; + } + } + + if (!otio_block) { + // We shouldn't ever get here, but catch without crashing if we ever do + return false; + } + + otio_track->append_child(otio_block, &es); + + if (es != opentimelineio::v1_0::ErrorStatus::OK) { + return false; + } + } + + otio_timeline->tracks()->append_child(otio_track, &es); + + if (es != opentimelineio::v1_0::ErrorStatus::OK) { + return false; + } + } + + return true; +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/task/project/saveotio/projectsaveasotiotask.h b/app/task/project/saveotio/projectsaveasotiotask.h new file mode 100644 index 000000000..b64495c1f --- /dev/null +++ b/app/task/project/saveotio/projectsaveasotiotask.h @@ -0,0 +1,45 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef PROJECTSAVEASOTIOTASK_H +#define PROJECTSAVEASOTIOTASK_H + +#include "project/item/sequence/sequence.h" +#include "task/task.h" + +OLIVE_NAMESPACE_ENTER + +class ProjectSaveAsOTIOTask : public Task +{ + Q_OBJECT +public: + ProjectSaveAsOTIOTask(Sequence* sequence); + +protected: + virtual bool Run() override; + +private: + Sequence* sequence_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // PROJECTSAVEASOTIOTASK_H diff --git a/app/window/mainwindow/mainmenu.cpp b/app/window/mainwindow/mainmenu.cpp index 8b6a0d6a0..97963afff 100644 --- a/app/window/mainwindow/mainmenu.cpp +++ b/app/window/mainwindow/mainmenu.cpp @@ -60,7 +60,12 @@ MainMenu::MainMenu(MainWindow *parent) : file_menu_->addSeparator(); file_import_item_ = file_menu_->AddItem("import", Core::instance(), &Core::DialogImportShow, "Ctrl+I"); file_menu_->addSeparator(); - file_export_item_ = file_menu_->AddItem("export", Core::instance(), &Core::DialogExportShow, "Ctrl+M"); + file_export_menu_ = new Menu(file_menu_); + file_export_media_item_ = file_export_menu_->AddItem("export", Core::instance(), &Core::DialogExportShow, "Ctrl+M"); +#ifdef USE_OTIO + file_export_menu_->addSeparator(); + file_export_otio_item_ = file_export_menu_->AddItem("exportotio", Core::instance(), &Core::DialogExportShow); +#endif file_menu_->addSeparator(); file_project_properties_item_ = file_menu_->AddItem("projectproperties", Core::instance(), &Core::DialogProjectPropertiesShow, "Shift+F10"); file_menu_->addSeparator(); @@ -642,7 +647,11 @@ void MainMenu::Retranslate() file_open_recent_clear_item_->setText(tr("&Clear Recent List")); file_save_all_item_->setText(tr("Sa&ve All Projects")); file_import_item_->setText(tr("&Import...")); - file_export_item_->setText(tr("&Export...")); + file_export_menu_->setTitle(tr("&Export")); + file_export_media_item_->setText(tr("&Media...")); +#ifdef USE_OTIO + file_export_otio_item_->setText(tr("&OpenTimelineIO...")); +#endif file_project_properties_item_->setText(tr("&Project Properties...")); file_close_all_projects_item_->setText(tr("Close All Projects")); file_exit_item_->setText(tr("E&xit")); diff --git a/app/window/mainwindow/mainmenu.h b/app/window/mainwindow/mainmenu.h index f7b4c0ad3..b74ebf0af 100644 --- a/app/window/mainwindow/mainmenu.h +++ b/app/window/mainwindow/mainmenu.h @@ -198,7 +198,11 @@ private: QAction* file_save_as_item_; QAction* file_save_all_item_; QAction* file_import_item_; - QAction* file_export_item_; + Menu* file_export_menu_; + QAction* file_export_media_item_; +#ifdef USE_OTIO + QAction* file_export_otio_item_; +#endif QAction* file_project_properties_item_; QAction* file_close_project_item_; QAction* file_close_all_projects_item_;