Merge branch 'master' into rational_slider
This commit is contained in:
@@ -351,13 +351,6 @@ jobs:
|
||||
$DOWNLOAD_TOOL https://github.com/arl/macdeployqtfix/raw/master/macdeployqtfix.py
|
||||
python2 macdeployqtfix.py $BUNDLE_NAME/Contents/MacOS/Olive $DEP_LOCATION
|
||||
|
||||
# Crashpad symbols
|
||||
$DEP_LOCATION/bin/dump_syms $BUNDLE_NAME/Contents/MacOS/Olive > Olive.sym
|
||||
SYM_HEADER=($(head -n 1 Olive.sym)) # Read first line of symbol file
|
||||
SYM_DIR=$BUNDLE_NAME/Contents/Resources/symbols/Olive/${SYM_HEADER[3]}
|
||||
mkdir -p "$SYM_DIR"
|
||||
mv Olive.sym "$SYM_DIR"
|
||||
|
||||
# Manual fixes
|
||||
cp $DEP_LOCATION/lib/libopentimelineio.dylib $BUNDLE_NAME/Contents/Frameworks
|
||||
cp $DEP_LOCATION/lib/libopentime.dylib $BUNDLE_NAME/Contents/Frameworks
|
||||
@@ -367,6 +360,13 @@ jobs:
|
||||
install_name_tool -change libmodplug.dylib @rpath/libmodplug.dylib $BUNDLE_NAME/Contents/Frameworks/libavformat.*
|
||||
install_name_tool -change libmodplug.dylib @rpath/libmodplug.dylib $BUNDLE_NAME/Contents/Frameworks/libavfilter.*
|
||||
|
||||
# Crashpad symbols
|
||||
$DEP_LOCATION/bin/dump_syms $BUNDLE_NAME/Contents/MacOS/Olive > Olive.sym
|
||||
SYM_HEADER=($(head -n 1 Olive.sym)) # Read first line of symbol file
|
||||
SYM_DIR=$BUNDLE_NAME/Contents/Resources/symbols/Olive/${SYM_HEADER[3]}
|
||||
mkdir -p "$SYM_DIR"
|
||||
mv Olive.sym "$SYM_DIR"
|
||||
|
||||
- name: Deploy Packages
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
shell: bash
|
||||
|
||||
@@ -28,6 +28,18 @@ namespace olive {
|
||||
|
||||
AudioManager* AudioManager::instance_ = nullptr;
|
||||
|
||||
QString AudioManager::GetAudioBackendName(AudioManager::Backend b)
|
||||
{
|
||||
switch (b) {
|
||||
case kAudioBackendQt:
|
||||
return tr("Qt");
|
||||
case kAudioBackendCount:
|
||||
break;
|
||||
}
|
||||
|
||||
return tr("Unknown");
|
||||
}
|
||||
|
||||
void AudioManager::CreateInstance()
|
||||
{
|
||||
if (instance_ == nullptr) {
|
||||
|
||||
@@ -44,6 +44,13 @@ class AudioManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Backend {
|
||||
kAudioBackendQt,
|
||||
kAudioBackendCount
|
||||
};
|
||||
|
||||
static QString GetAudioBackendName(Backend b);
|
||||
|
||||
static void CreateInstance();
|
||||
static void DestroyInstance();
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ set(OLIVE_SOURCES
|
||||
common/functiontimer.h
|
||||
common/lerp.h
|
||||
common/memorypool.cpp
|
||||
common/otioutils.h
|
||||
common/memorypool.h
|
||||
common/ocioutils.cpp
|
||||
common/ocioutils.h
|
||||
|
||||
@@ -34,11 +34,11 @@ CommandLineParser::~CommandLineParser()
|
||||
}
|
||||
}
|
||||
|
||||
const CommandLineParser::Option *CommandLineParser::AddOption(const QStringList &strings, const QString &description, bool takes_arg, const QString &arg_placeholder)
|
||||
const CommandLineParser::Option *CommandLineParser::AddOption(const QStringList &strings, const QString &description, bool takes_arg, const QString &arg_placeholder, bool hidden)
|
||||
{
|
||||
Option* o = new Option();
|
||||
|
||||
options_.append({strings, description, o, takes_arg, arg_placeholder});
|
||||
options_.append({strings, description, o, takes_arg, arg_placeholder, hidden});
|
||||
|
||||
return o;
|
||||
}
|
||||
@@ -140,6 +140,10 @@ void CommandLineParser::PrintHelp(const char* filename)
|
||||
|
||||
printf("Usage: %s [options] %s\n\n", basename, positional_args.toUtf8().constData());
|
||||
foreach (const KnownOption& o, options_) {
|
||||
if (o.hidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString all_args;
|
||||
|
||||
for (int i=0; i<o.args.size(); i++) {
|
||||
|
||||
@@ -26,6 +26,16 @@
|
||||
|
||||
#include "common/define.h"
|
||||
|
||||
/**
|
||||
* @brief Command-line argument parser
|
||||
*
|
||||
* You may be wondering why we don't use QCommandLineParser instead of a custom implmentation like
|
||||
* this. The reason why is because QCommandLineParser requires a QApplication object of some kind
|
||||
* to already have been created before it can parse anything, but we need to be able to control
|
||||
* whether a QApplication (GUI-mode) or a QCoreApplication (CLI-mode) is created which is set by
|
||||
* the user as a command line argument. Therefore we needed a custom implementation that could
|
||||
* parse arguments without the need for a Q(Core)Application to be present already.
|
||||
*/
|
||||
class CommandLineParser
|
||||
{
|
||||
public:
|
||||
@@ -77,7 +87,7 @@ public:
|
||||
|
||||
CommandLineParser() = default;
|
||||
|
||||
const Option* AddOption(const QStringList& strings, const QString& description, bool takes_arg = false, const QString& arg_placeholder = QString());
|
||||
const Option* AddOption(const QStringList& strings, const QString& description, bool takes_arg = false, const QString& arg_placeholder = QString(), bool hidden = false);
|
||||
|
||||
const PositionalArgument* AddPositionalArgument(const QString& name, const QString& description, bool required = false);
|
||||
|
||||
@@ -92,6 +102,7 @@ private:
|
||||
Option* option;
|
||||
bool takes_arg;
|
||||
QString arg_placeholder;
|
||||
bool hidden;
|
||||
};
|
||||
|
||||
struct KnownPositionalArgument {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OTIOUTILS_H
|
||||
#define OTIOUTILS_H
|
||||
|
||||
#ifdef USE_OTIO
|
||||
#include <opentimelineio/version.h>
|
||||
namespace OTIO = opentimelineio::OPENTIMELINEIO_VERSION;
|
||||
#endif
|
||||
|
||||
#endif // OTIOUTILS
|
||||
@@ -103,10 +103,12 @@ AVRational rational::toAVRational() const
|
||||
}
|
||||
|
||||
#ifdef USE_OTIO
|
||||
opentime::RationalTime rational::toRationalTime() const
|
||||
opentime::RationalTime rational::toRationalTime(double framerate) const
|
||||
{
|
||||
// Is this the best way of doing this?
|
||||
return opentime::RationalTime::from_seconds(toDouble());
|
||||
// Olive can store rationals as 0/0 which causes errors in OTIO
|
||||
opentime::RationalTime time = opentime::RationalTime(numer_, denom_ == 0 ? 1 : denom_);
|
||||
return time.rescaled_to(framerate);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -101,7 +101,8 @@ public:
|
||||
AVRational toAVRational() const;
|
||||
|
||||
#ifdef USE_OTIO
|
||||
opentime::RationalTime toRationalTime() const;
|
||||
// Convert Olive ratioanls to opentime rationals with the given framerate (defaults to 24)
|
||||
opentime::RationalTime toRationalTime(double framerate = 24) const;
|
||||
#endif
|
||||
|
||||
// Produce "flipped" version
|
||||
|
||||
+16
-8
@@ -741,14 +741,22 @@ void Core::SaveProjectInternal(Project* project, const QString& override_filenam
|
||||
}
|
||||
}
|
||||
|
||||
TaskDialog* task_dialog = new TaskDialog(psm, tr("Save Project"), main_window_);
|
||||
|
||||
if (override_filename.isEmpty()) {
|
||||
// Default behavior: set as not modified and push to top of "Open Recent" dialog
|
||||
connect(task_dialog, &TaskDialog::TaskSucceeded, this, &Core::ProjectSaveSucceeded);
|
||||
// We don't use a TaskDialog here because a model save dialog is annoying, particularly when
|
||||
// saving auto-recoveries that the user can't anticipate. Doing this in the main thread will
|
||||
// cause a brief (but often unnoticeable) pause in the GUI, which, while not ideal, is not that
|
||||
// different from what already happened (modal dialog preventing use of the GUI) and in many ways
|
||||
// less annoying (doesn't disrupt any current actions or pull focus from elsewhere).
|
||||
//
|
||||
// Ideally we could do this in a background thread and show progress in the status bar like
|
||||
// Microsoft Word, but that would be far more complex. If it becomes necessary in the future,
|
||||
// we will look into an approach like that.
|
||||
if (psm->Start()) {
|
||||
if (override_filename.isEmpty()) {
|
||||
ProjectSaveSucceeded(psm);
|
||||
}
|
||||
}
|
||||
|
||||
task_dialog->open();
|
||||
psm->deleteLater();
|
||||
}
|
||||
|
||||
ViewerOutput* Core::GetSequenceToExport()
|
||||
@@ -1496,8 +1504,8 @@ bool Core::ValidateFootageInLoadedProject(Project* project, const QString& proje
|
||||
}
|
||||
}
|
||||
|
||||
// Heuristically compare footage to file
|
||||
if (Footage::CompareFootageToItsFilename(footage)) {
|
||||
if (QFileInfo::exists(footage->filename())) {
|
||||
// Assume valid
|
||||
footage->SetValid();
|
||||
} else {
|
||||
footage_we_couldnt_validate.append(footage);
|
||||
|
||||
@@ -148,9 +148,14 @@ void CrashHandlerDialog::ReplyFinished(QNetworkReply* reply)
|
||||
// Close dialog
|
||||
QDialog::accept();
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("Upload Failed"),
|
||||
tr("Failed to send error report. Please try again later."),
|
||||
QMessageBox::Ok);
|
||||
QMessageBox b(this);
|
||||
b.setIcon(QMessageBox::Critical);
|
||||
b.setWindowModality(Qt::WindowModal);
|
||||
b.setWindowTitle(tr("Upload Failed"));
|
||||
b.setText(tr("Failed to send error report. Please try again later."));
|
||||
b.addButton(QMessageBox::Ok);
|
||||
b.exec();
|
||||
|
||||
SetGUIObjectsEnabled(true);
|
||||
}
|
||||
}
|
||||
@@ -181,10 +186,15 @@ void CrashHandlerDialog::ReadProcessFinished()
|
||||
void CrashHandlerDialog::SendErrorReport()
|
||||
{
|
||||
if (summary_edit_->document()->isEmpty()) {
|
||||
if (QMessageBox::question(this,
|
||||
tr("No Crash Summary"),
|
||||
tr("Are you sure you want to send an error report with no crash summary?"),
|
||||
QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
|
||||
QMessageBox b(this);
|
||||
b.setIcon(QMessageBox::Question);
|
||||
b.setWindowModality(Qt::WindowModal);
|
||||
b.setWindowTitle(tr("No Crash Summary"));
|
||||
b.setText(tr("Are you sure you want to send an error report with no crash summary?"));
|
||||
b.addButton(QMessageBox::Yes);
|
||||
b.addButton(QMessageBox::No);
|
||||
|
||||
if (b.exec() == QMessageBox::No) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -201,21 +211,21 @@ void CrashHandlerDialog::SendErrorReport()
|
||||
|
||||
// Create description section
|
||||
QHttpPart desc_part;
|
||||
desc_part.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("text/plain"));
|
||||
desc_part.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("text/plain; charset=UTF-8"));
|
||||
desc_part.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"description\""));
|
||||
desc_part.setBody(summary_edit_->toPlainText().toUtf8());
|
||||
multipart->append(desc_part);
|
||||
|
||||
// Create report section
|
||||
QHttpPart report_part;
|
||||
report_part.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("text/plain"));
|
||||
report_part.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("text/plain; charset=UTF-8"));
|
||||
report_part.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"report\""));
|
||||
report_part.setBody(report_data_);
|
||||
multipart->append(report_part);
|
||||
|
||||
// Create commit section
|
||||
QHttpPart commit_part;
|
||||
commit_part.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("text/plain"));
|
||||
commit_part.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("text/plain; charset=UTF-8"));
|
||||
commit_part.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"commit\""));
|
||||
commit_part.setBody(GITHASH);
|
||||
multipart->append(commit_part);
|
||||
@@ -233,24 +243,41 @@ void CrashHandlerDialog::SendErrorReport()
|
||||
|
||||
// Find symbol file
|
||||
QDir symbol_dir(GetSymbolPath());
|
||||
#ifdef Q_OS_WINDOWS
|
||||
symbol_dir = QDir(symbol_dir.filePath(QStringLiteral("olive-editor.pdb")));
|
||||
|
||||
QString symbol_bin_name;
|
||||
#if defined(OS_WIN)
|
||||
symbol_bin_name = QStringLiteral("olive-editor.pdb");
|
||||
#elif defined(OS_APPLE)
|
||||
symbol_bin_name = QStringLiteral("Olive");
|
||||
#else
|
||||
symbol_dir = QDir(symbol_dir.filePath(QStringLiteral("olive-editor")));
|
||||
symbol_bin_name = QStringLiteral("olive-editor");
|
||||
#endif
|
||||
symbol_dir = QDir(symbol_dir.filePath(symbol_bin_name));
|
||||
|
||||
QStringList folders_in_symbol_path = symbol_dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
|
||||
if (folders_in_symbol_path.size() > 0) {
|
||||
symbol_dir = QDir(symbol_dir.filePath(folders_in_symbol_path.first()));
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("Failed to send report"), tr("Failed to find symbols necessary to send report. "
|
||||
"This is a packaging issue. Please notify "
|
||||
"the maintainers of this package."));
|
||||
QMessageBox b(this);
|
||||
b.setIcon(QMessageBox::Critical);
|
||||
b.setWindowModality(Qt::WindowModal);
|
||||
b.setWindowTitle(tr("Failed to send report"));
|
||||
b.setText(tr("Failed to find symbols necessary to send report. "
|
||||
"This is a packaging issue. Please notify "
|
||||
"the maintainers of this package."));
|
||||
b.addButton(QMessageBox::Ok);
|
||||
b.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create sym section
|
||||
QString symbol_filename = QStringLiteral("olive-editor.sym");
|
||||
QString symbol_filename;
|
||||
#if defined(OS_APPLE)
|
||||
symbol_filename = QStringLiteral("Olive.sym");
|
||||
#else
|
||||
symbol_filename = QStringLiteral("olive-editor.sym");
|
||||
#endif
|
||||
QString symbol_full_path = symbol_dir.filePath(symbol_filename);
|
||||
QHttpPart sym_part;
|
||||
sym_part.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/octet-stream"));
|
||||
@@ -259,8 +286,14 @@ void CrashHandlerDialog::SendErrorReport()
|
||||
QFile sym_file(symbol_full_path);
|
||||
|
||||
if (!sym_file.open(QFile::ReadOnly)) {
|
||||
QMessageBox::critical(this, tr("Failed to send report"), tr("Failed to open symbol file. You may not have "
|
||||
"permission to access it."));
|
||||
QMessageBox b(this);
|
||||
b.setIcon(QMessageBox::Critical);
|
||||
b.setWindowModality(Qt::WindowModal);
|
||||
b.setWindowTitle(tr("Failed to send report"));
|
||||
b.setText(tr("Failed to open symbol file. You may not have "
|
||||
"permission to access it."));
|
||||
b.addButton(QMessageBox::Ok);
|
||||
b.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -278,12 +311,16 @@ void CrashHandlerDialog::SendErrorReport()
|
||||
|
||||
void CrashHandlerDialog::closeEvent(QCloseEvent* e)
|
||||
{
|
||||
if (waiting_for_upload_
|
||||
&& QMessageBox::warning(this,
|
||||
tr("Confirm Close"),
|
||||
tr("Crash report is still uploading. Closing now may result in no "
|
||||
"report being sent. Are you sure you wish to close?"),
|
||||
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) {
|
||||
QMessageBox b(this);
|
||||
b.setIcon(QMessageBox::Warning);
|
||||
b.setWindowModality(Qt::WindowModal);
|
||||
b.setWindowTitle(tr("Confirm Close"));
|
||||
b.setText(tr("Crash report is still uploading. Closing now may result in no "
|
||||
"report being sent. Are you sure you wish to close?"));
|
||||
b.addButton(QMessageBox::Ok);
|
||||
b.addButton(QMessageBox::Cancel);
|
||||
|
||||
if (waiting_for_upload_ && b.exec() == QMessageBox::Cancel) {
|
||||
e->ignore();
|
||||
} else {
|
||||
e->accept();
|
||||
|
||||
@@ -114,13 +114,14 @@ void FootageRelinkDialog::BrowseForFootage()
|
||||
// Set new filename since this was set manually by the user
|
||||
f->set_filename(new_fn);
|
||||
|
||||
if (Footage::CompareFootageToItsFilename(f)) {
|
||||
// Set footage to valid and update icon
|
||||
f->SetValid();
|
||||
// Assume footage is valid here. We could do some decoder probing to ensure it's a usable file
|
||||
// but otherwise we assume the user knows what they're doing here.
|
||||
|
||||
// Update item visually
|
||||
UpdateFootageItem(index);
|
||||
}
|
||||
// Set footage to valid and update icon
|
||||
f->SetValid();
|
||||
|
||||
// Update item visually
|
||||
UpdateFootageItem(index);
|
||||
|
||||
// Check all other footage files for matches
|
||||
for (int it=0; it<footage_.size(); it++) {
|
||||
@@ -133,8 +134,7 @@ void FootageRelinkDialog::BrowseForFootage()
|
||||
QString absolute_to_new = new_dir.filePath(relative_to_original);
|
||||
|
||||
// Check if file exists
|
||||
if (QFileInfo::exists(absolute_to_new)
|
||||
&& Footage::CompareFootageToFile(other_footage, absolute_to_new)) {
|
||||
if (QFileInfo::exists(absolute_to_new)) {
|
||||
other_footage->set_filename(absolute_to_new);
|
||||
other_footage->SetValid();
|
||||
UpdateFootageItem(it);
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace olive {
|
||||
PreferencesAppearanceTab::PreferencesAppearanceTab()
|
||||
{
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
layout->setMargin(0);
|
||||
|
||||
QGridLayout* appearance_layout = new QGridLayout();
|
||||
layout->addLayout(appearance_layout);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "preferencesaudiotab.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
|
||||
#include "audio/audiomanager.h"
|
||||
@@ -30,65 +31,88 @@ namespace olive {
|
||||
|
||||
PreferencesAudioTab::PreferencesAudioTab()
|
||||
{
|
||||
QGridLayout* audio_tab_layout = new QGridLayout(this);
|
||||
audio_tab_layout->setMargin(0);
|
||||
QVBoxLayout* audio_tab_layout = new QVBoxLayout(this);
|
||||
|
||||
int row = 0;
|
||||
{
|
||||
// Backend Layout
|
||||
QGridLayout* main_layout = new QGridLayout();
|
||||
main_layout->setMargin(0);
|
||||
|
||||
// Audio -> Output Device
|
||||
audio_tab_layout->addWidget(new QLabel(tr("Output Device:")), row, 0);
|
||||
int row = 0;
|
||||
|
||||
audio_output_devices_ = new QComboBox();
|
||||
audio_tab_layout->addWidget(audio_output_devices_, row, 1);
|
||||
main_layout->addWidget(new QLabel(tr("Backend:")), row, 0);
|
||||
|
||||
row++;
|
||||
|
||||
// Audio -> Input Device
|
||||
audio_tab_layout->addWidget(new QLabel(tr("Input Device:")), row, 0);
|
||||
|
||||
audio_input_devices_ = new QComboBox();
|
||||
audio_tab_layout->addWidget(audio_input_devices_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
// Audio -> Sample Rate
|
||||
|
||||
audio_tab_layout->addWidget(new QLabel(tr("Sample Rate:")), row, 0);
|
||||
|
||||
audio_sample_rate_ = new QComboBox();
|
||||
/*combobox_audio_sample_rates(audio_sample_rate);
|
||||
for (int i=0;i<audio_sample_rate->count();i++) {
|
||||
if (audio_sample_rate->itemData(i).toInt() == olive::config.audio_rate) {
|
||||
audio_sample_rate->setCurrentIndex(i);
|
||||
break;
|
||||
audio_backend_combobox_ = new QComboBox();
|
||||
for (int i=0; i<AudioManager::kAudioBackendCount; i++) {
|
||||
audio_backend_combobox_->addItem(AudioManager::GetAudioBackendName(static_cast<AudioManager::Backend>(i)));
|
||||
}
|
||||
}*/
|
||||
main_layout->addWidget(audio_backend_combobox_, row, 1);
|
||||
|
||||
audio_tab_layout->addWidget(audio_sample_rate_, row, 1);
|
||||
audio_tab_layout->addLayout(main_layout);
|
||||
}
|
||||
|
||||
row++;
|
||||
{
|
||||
// Qt-Backend Layout
|
||||
QGroupBox* qt_groupbox = new QGroupBox();
|
||||
audio_tab_layout->addWidget(qt_groupbox);
|
||||
|
||||
// Audio -> Audio Recording
|
||||
audio_tab_layout->addWidget(new QLabel(tr("Audio Recording:"), this), row, 0);
|
||||
QVBoxLayout* qt_layout = new QVBoxLayout(qt_groupbox);
|
||||
|
||||
recording_combobox_ = new QComboBox();
|
||||
recording_combobox_->addItem(tr("Mono"));
|
||||
recording_combobox_->addItem(tr("Stereo"));
|
||||
// recordingComboBox->setCurrentIndex(olive::config.recording_mode - 1);
|
||||
audio_tab_layout->addWidget(recording_combobox_, row, 1);
|
||||
int row = 0;
|
||||
|
||||
row++;
|
||||
{
|
||||
// Output Group
|
||||
QGroupBox* qt_output_group = new QGroupBox();
|
||||
qt_output_group->setTitle(tr("Output"));
|
||||
qt_layout->addWidget(qt_output_group);
|
||||
|
||||
refresh_devices_btn_ = new QPushButton(tr("Refresh Devices"));
|
||||
audio_tab_layout->addWidget(refresh_devices_btn_, row, 1);
|
||||
QGridLayout* qt_output_layout = new QGridLayout(qt_output_group);
|
||||
|
||||
row++;
|
||||
qt_output_layout->addWidget(new QLabel(tr("Device:")), row, 0);
|
||||
|
||||
RetrieveDeviceLists();
|
||||
audio_output_devices_ = new QComboBox();
|
||||
qt_output_layout->addWidget(audio_output_devices_, row, 1);
|
||||
}
|
||||
|
||||
connect(refresh_devices_btn_, &QPushButton::clicked, this, &PreferencesAudioTab::RefreshDevices);
|
||||
connect(AudioManager::instance(), &AudioManager::OutputListReady, this, &PreferencesAudioTab::RetrieveOutputList);
|
||||
connect(AudioManager::instance(), &AudioManager::InputListReady, this, &PreferencesAudioTab::RetrieveInputList);
|
||||
row = 0;
|
||||
|
||||
{
|
||||
QGroupBox* qt_input_group = new QGroupBox();
|
||||
qt_input_group->setTitle(tr("Input"));
|
||||
qt_layout->addWidget(qt_input_group);
|
||||
|
||||
QGridLayout* qt_input_layout = new QGridLayout(qt_input_group);
|
||||
|
||||
qt_input_layout->addWidget(new QLabel(tr("Device:")), row, 0);
|
||||
|
||||
audio_input_devices_ = new QComboBox();
|
||||
qt_input_layout->addWidget(audio_input_devices_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
qt_input_layout->addWidget(new QLabel(tr("Recording Mode:"), this), row, 0);
|
||||
|
||||
recording_combobox_ = new QComboBox();
|
||||
recording_combobox_->addItem(tr("Mono"));
|
||||
recording_combobox_->addItem(tr("Stereo"));
|
||||
qt_input_layout->addWidget(recording_combobox_, row, 1);
|
||||
}
|
||||
|
||||
QHBoxLayout* qt_refresh_layout = new QHBoxLayout();
|
||||
qt_layout->addLayout(qt_refresh_layout);
|
||||
qt_refresh_layout->addStretch();
|
||||
|
||||
refresh_devices_btn_ = new QPushButton(tr("Refresh Devices"));
|
||||
qt_refresh_layout->addWidget(refresh_devices_btn_);
|
||||
|
||||
RetrieveDeviceLists();
|
||||
|
||||
connect(refresh_devices_btn_, &QPushButton::clicked, this, &PreferencesAudioTab::RefreshDevices);
|
||||
connect(AudioManager::instance(), &AudioManager::OutputListReady, this, &PreferencesAudioTab::RetrieveOutputList);
|
||||
connect(AudioManager::instance(), &AudioManager::InputListReady, this, &PreferencesAudioTab::RetrieveInputList);
|
||||
}
|
||||
|
||||
audio_tab_layout->addStretch();
|
||||
}
|
||||
|
||||
void PreferencesAudioTab::Accept(MultiUndoCommand *command)
|
||||
|
||||
@@ -38,6 +38,8 @@ public:
|
||||
virtual void Accept(MultiUndoCommand* command) override;
|
||||
|
||||
private:
|
||||
QComboBox* audio_backend_combobox_;
|
||||
|
||||
/**
|
||||
* @brief UI widget for selecting the output audio device
|
||||
*/
|
||||
@@ -48,11 +50,6 @@ private:
|
||||
*/
|
||||
QComboBox* audio_input_devices_;
|
||||
|
||||
/**
|
||||
* @brief UI widget for selecting the audio sampling rates
|
||||
*/
|
||||
QComboBox* audio_sample_rate_;
|
||||
|
||||
/**
|
||||
* @brief UI widget for editing the recording channels
|
||||
*/
|
||||
|
||||
@@ -30,7 +30,6 @@ namespace olive {
|
||||
PreferencesBehaviorTab::PreferencesBehaviorTab()
|
||||
{
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
layout->setMargin(0);
|
||||
|
||||
behavior_tree_ = new QTreeWidget();
|
||||
layout->addWidget(behavior_tree_);
|
||||
|
||||
@@ -35,7 +35,6 @@ namespace olive {
|
||||
PreferencesGeneralTab::PreferencesGeneralTab()
|
||||
{
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
layout->setMargin(0);
|
||||
|
||||
{
|
||||
QGroupBox* global_groupbox = new QGroupBox(tr("Locale"));
|
||||
|
||||
@@ -32,7 +32,6 @@ namespace olive {
|
||||
PreferencesKeyboardTab::PreferencesKeyboardTab(QMenuBar *menubar)
|
||||
{
|
||||
QVBoxLayout* shortcut_layout = new QVBoxLayout(this);
|
||||
shortcut_layout->setMargin(0);
|
||||
|
||||
QLineEdit* key_search_line = new QLineEdit();
|
||||
key_search_line->setPlaceholderText(tr("Search for action or shortcut"));
|
||||
|
||||
+25
-6
@@ -76,32 +76,51 @@ int main(int argc, char *argv[])
|
||||
|
||||
CommandLineParser parser;
|
||||
|
||||
const CommandLineParser::Option* help_option =
|
||||
// Our options
|
||||
auto help_option =
|
||||
parser.AddOption({QStringLiteral("h"), QStringLiteral("-help")},
|
||||
QCoreApplication::translate("main", "Show this help text"));
|
||||
|
||||
const CommandLineParser::Option* version_option =
|
||||
auto version_option =
|
||||
parser.AddOption({QStringLiteral("v"), QStringLiteral("-version")},
|
||||
QCoreApplication::translate("main", "Show application version"));
|
||||
|
||||
const CommandLineParser::Option* fullscreen_option =
|
||||
auto fullscreen_option =
|
||||
parser.AddOption({QStringLiteral("f"), QStringLiteral("-fullscreen")},
|
||||
QCoreApplication::translate("main", "Start in full-screen mode"));
|
||||
|
||||
const CommandLineParser::Option* export_option =
|
||||
auto export_option =
|
||||
parser.AddOption({QStringLiteral("x"), QStringLiteral("-export")},
|
||||
QCoreApplication::translate("main", "Export only (No GUI)"));
|
||||
|
||||
const CommandLineParser::Option* ts_option =
|
||||
auto ts_option =
|
||||
parser.AddOption({QStringLiteral("-ts")},
|
||||
QCoreApplication::translate("main", "Override language with file"),
|
||||
true,
|
||||
QCoreApplication::translate("main", "qm-file"));
|
||||
|
||||
const CommandLineParser::PositionalArgument* project_argument =
|
||||
auto project_argument =
|
||||
parser.AddPositionalArgument(QStringLiteral("project"),
|
||||
QCoreApplication::translate("main", "Project to open on startup"));
|
||||
|
||||
// Qt options re-implemented (add to this as necessary)
|
||||
//
|
||||
// Because we don't use QCommandLineParser, we must filter out Qt's arguments ourselves. Here,
|
||||
// we create them so they're recognized, but never use and also hide them in the "help" text.
|
||||
parser.AddOption({QStringLiteral("platform")}, QString(), true, QString(), true);
|
||||
parser.AddOption({QStringLiteral("platformpluginpath")}, QString(), true, QString(), true);
|
||||
parser.AddOption({QStringLiteral("platformtheme")}, QString(), true, QString(), true);
|
||||
parser.AddOption({QStringLiteral("plugin")}, QString(), true, QString(), true);
|
||||
parser.AddOption({QStringLiteral("qmljsdebugger")}, QString(), true, QString(), true);
|
||||
parser.AddOption({QStringLiteral("qwindowgeometry")}, QString(), true, QString(), true);
|
||||
parser.AddOption({QStringLiteral("qwindowicon")}, QString(), true, QString(), true);
|
||||
parser.AddOption({QStringLiteral("qwindowtitle")}, QString(), true, QString(), true);
|
||||
parser.AddOption({QStringLiteral("reverse")}, QString(), false, QString(), true);
|
||||
parser.AddOption({QStringLiteral("session")}, QString(), true, QString(), true);
|
||||
parser.AddOption({QStringLiteral("style")}, QString(), true, QString(), true);
|
||||
parser.AddOption({QStringLiteral("stylesheet")}, QString(), true, QString(), true);
|
||||
parser.AddOption({QStringLiteral("widgetcount")}, QString(), false, QString(), true);
|
||||
|
||||
parser.Process(argc, argv);
|
||||
|
||||
if (help_option->IsSet()) {
|
||||
|
||||
+45
-30
@@ -32,6 +32,7 @@ const QString Block::kLengthInput = QStringLiteral("length_in");
|
||||
const QString Block::kMediaInInput = QStringLiteral("media_in_in");
|
||||
const QString Block::kEnabledInput = QStringLiteral("enabled_in");
|
||||
const QString Block::kSpeedInput = QStringLiteral("speed_in");
|
||||
const QString Block::kReverseInput = QStringLiteral("reverse_in");
|
||||
|
||||
Block::Block() :
|
||||
previous_(nullptr),
|
||||
@@ -51,10 +52,14 @@ Block::Block() :
|
||||
|
||||
AddInput(kEnabledInput, NodeValue::kBoolean, true, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
|
||||
|
||||
AddInput(kSpeedInput, NodeValue::kFloat, 1.0);
|
||||
AddInput(kSpeedInput, NodeValue::kFloat, 1.0, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
|
||||
SetInputProperty(kSpeedInput, QStringLiteral("view"), FloatSlider::kPercentage);
|
||||
SetInputProperty(kSpeedInput, QStringLiteral("min"), 0.0);
|
||||
IgnoreHashingFrom(kSpeedInput);
|
||||
|
||||
AddInput(kReverseInput, NodeValue::kBoolean, false, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
|
||||
IgnoreHashingFrom(kReverseInput);
|
||||
|
||||
// A block's length must be greater than 0
|
||||
set_length_and_media_out(1);
|
||||
}
|
||||
@@ -77,6 +82,11 @@ void Block::set_length_and_media_out(const rational &length)
|
||||
return;
|
||||
}
|
||||
|
||||
if (reverse()) {
|
||||
// Calculate media_in adjustment
|
||||
set_media_in(SequenceToMediaTime(length - this->length(), true));
|
||||
}
|
||||
|
||||
set_length_internal(length);
|
||||
}
|
||||
|
||||
@@ -88,8 +98,10 @@ void Block::set_length_and_media_in(const rational &length)
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate media_in adjustment
|
||||
set_media_in(SequenceToMediaTime(this->length() - length));
|
||||
if (!reverse()) {
|
||||
// Calculate media_in adjustment
|
||||
set_media_in(SequenceToMediaTime(this->length() - length));
|
||||
}
|
||||
|
||||
// Set the length without setting media out
|
||||
set_length_internal(length);
|
||||
@@ -117,7 +129,7 @@ void Block::set_enabled(bool e)
|
||||
emit EnabledChanged();
|
||||
}
|
||||
|
||||
rational Block::SequenceToMediaTime(const rational &sequence_time) const
|
||||
rational Block::SequenceToMediaTime(const rational &sequence_time, bool ignore_reverse) const
|
||||
{
|
||||
// These constants are not considered "values" per se, so we don't modify them
|
||||
if (sequence_time == RATIONAL_MIN || sequence_time == RATIONAL_MAX) {
|
||||
@@ -126,22 +138,23 @@ rational Block::SequenceToMediaTime(const rational &sequence_time) const
|
||||
|
||||
rational local_time = sequence_time;
|
||||
|
||||
// FIXME: Doesn't handle reversing
|
||||
if (IsInputStatic(kSpeedInput)) {
|
||||
double speed_value = GetStandardValue(kSpeedInput).toDouble();
|
||||
double speed_value = speed();
|
||||
|
||||
if (qIsNull(speed_value)) {
|
||||
// Effectively holds the frame at the in point
|
||||
local_time = 0;
|
||||
} else if (!qFuzzyCompare(speed_value, 1.0)) {
|
||||
// Multiply time
|
||||
local_time = rational::fromDouble(local_time.toDouble() * speed_value);
|
||||
}
|
||||
} else {
|
||||
// FIXME: We'll need to calculate the speed hoo boy
|
||||
if (qIsNull(speed_value)) {
|
||||
// Effectively holds the frame at the in point
|
||||
local_time = 0;
|
||||
} else if (!qFuzzyCompare(speed_value, 1.0)) {
|
||||
// Multiply time
|
||||
local_time = rational::fromDouble(local_time.toDouble() * speed_value);
|
||||
}
|
||||
|
||||
return local_time + media_in();
|
||||
rational media_time = local_time + media_in();
|
||||
|
||||
if (reverse() && !ignore_reverse) {
|
||||
media_time = length() - media_time;
|
||||
}
|
||||
|
||||
return media_time;
|
||||
}
|
||||
|
||||
rational Block::MediaToSequenceTime(const rational &media_time) const
|
||||
@@ -151,21 +164,22 @@ rational Block::MediaToSequenceTime(const rational &media_time) const
|
||||
return media_time;
|
||||
}
|
||||
|
||||
rational sequence_time = media_time - media_in();
|
||||
rational sequence_time = media_time;
|
||||
|
||||
// FIXME: Doesn't handle reversing
|
||||
if (IsInputKeyframing(kSpeedInput) || IsInputConnected(kSpeedInput)) {
|
||||
// FIXME: We'll need to calculate the speed hoo boy
|
||||
} else {
|
||||
double speed_value = GetStandardValue(kSpeedInput).toDouble();
|
||||
if (reverse()) {
|
||||
sequence_time = length() - sequence_time;
|
||||
}
|
||||
|
||||
if (qIsNull(speed_value)) {
|
||||
// Effectively holds the frame at the in point, also prevents divide by zero
|
||||
sequence_time = 0;
|
||||
} else if (!qFuzzyCompare(speed_value, 1.0)) {
|
||||
// Multiply time
|
||||
sequence_time = rational::fromDouble(sequence_time.toDouble() / speed_value);
|
||||
}
|
||||
sequence_time -= media_in();
|
||||
|
||||
double speed_value = speed();
|
||||
|
||||
if (qIsNull(speed_value)) {
|
||||
// Effectively holds the frame at the in point, also prevents divide by zero
|
||||
sequence_time = 0;
|
||||
} else if (!qFuzzyCompare(speed_value, 1.0)) {
|
||||
// Multiply time
|
||||
sequence_time = rational::fromDouble(sequence_time.toDouble() / speed_value);
|
||||
}
|
||||
|
||||
return sequence_time;
|
||||
@@ -208,6 +222,7 @@ void Block::Retranslate()
|
||||
SetInputName(kMediaInInput, tr("Media In"));
|
||||
SetInputName(kEnabledInput, tr("Enabled"));
|
||||
SetInputName(kSpeedInput, tr("Speed"));
|
||||
SetInputName(kReverseInput, tr("Reverse"));
|
||||
}
|
||||
|
||||
void Block::Hash(const QString &, QCryptographicHash &, const rational &) const
|
||||
|
||||
+12
-1
@@ -151,12 +151,23 @@ public:
|
||||
return block_links_;
|
||||
}
|
||||
|
||||
double speed() const
|
||||
{
|
||||
return GetStandardValue(kSpeedInput).toDouble();
|
||||
}
|
||||
|
||||
bool reverse() const
|
||||
{
|
||||
return GetStandardValue(kReverseInput).toBool();
|
||||
}
|
||||
|
||||
virtual void Hash(const QString& output, QCryptographicHash &hash, const rational &time) const override;
|
||||
|
||||
static const QString kLengthInput;
|
||||
static const QString kMediaInInput;
|
||||
static const QString kEnabledInput;
|
||||
static const QString kSpeedInput;
|
||||
static const QString kReverseInput;
|
||||
|
||||
public slots:
|
||||
|
||||
@@ -166,7 +177,7 @@ signals:
|
||||
void LengthChanged();
|
||||
|
||||
protected:
|
||||
rational SequenceToMediaTime(const rational& sequence_time) const;
|
||||
rational SequenceToMediaTime(const rational& sequence_time, bool ignore_reverse = false) const;
|
||||
|
||||
rational MediaToSequenceTime(const rational& media_time) const;
|
||||
|
||||
|
||||
@@ -114,12 +114,15 @@ void ClipBlock::Retranslate()
|
||||
SetInputName(kBufferIn, tr("Buffer"));
|
||||
}
|
||||
|
||||
void ClipBlock::Hash(const QString &output, QCryptographicHash &hash, const rational &time) const
|
||||
void ClipBlock::Hash(const QString &out, QCryptographicHash &hash, const rational &time) const
|
||||
{
|
||||
Q_UNUSED(out)
|
||||
|
||||
if (IsInputConnected(kBufferIn)) {
|
||||
rational t = InputTimeAdjustment(kBufferIn, -1, TimeRange(time, time)).in();
|
||||
|
||||
GetConnectedNode(kBufferIn)->Hash(output, hash, t);
|
||||
NodeOutput output = GetConnectedOutput(kBufferIn);
|
||||
output.node()->Hash(output.output(), hash, t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
#include "project/folder/folder.h"
|
||||
#include "project/footage/footage.h"
|
||||
#include "project/sequence/sequence.h"
|
||||
#include "node/input/value/valuenode.h"
|
||||
|
||||
namespace olive {
|
||||
QList<Node*> NodeFactory::library_;
|
||||
@@ -234,6 +235,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
|
||||
return new Folder();
|
||||
case kProjectSequence:
|
||||
return new Sequence();
|
||||
case kValueNode:
|
||||
return new ValueNode();
|
||||
|
||||
case kInternalNodeCount:
|
||||
break;
|
||||
|
||||
@@ -56,6 +56,7 @@ public:
|
||||
kProjectFootage,
|
||||
kProjectFolder,
|
||||
kProjectSequence,
|
||||
kValueNode,
|
||||
|
||||
// Count value
|
||||
kInternalNodeCount
|
||||
|
||||
+8
-1
@@ -30,10 +30,17 @@ NodeGraph::NodeGraph()
|
||||
{
|
||||
}
|
||||
|
||||
NodeGraph::~NodeGraph()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
void NodeGraph::Clear()
|
||||
{
|
||||
// By deleting the last nodes first, we assume that nodes that are most important are deleted last
|
||||
// (e.g. Project's ColorManager or ProjectSettingsNode.
|
||||
while (!node_children_.isEmpty()) {
|
||||
delete node_children_.first();
|
||||
delete node_children_.last();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,11 @@ public:
|
||||
*/
|
||||
NodeGraph();
|
||||
|
||||
/**
|
||||
* @brief NodeGraph Destructor
|
||||
*/
|
||||
virtual ~NodeGraph() override;
|
||||
|
||||
/**
|
||||
* @brief Destructively destroys all nodes in the graph
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(time)
|
||||
add_subdirectory(value)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "multicamnode.h"
|
||||
|
||||
MultiCamNode::MultiCamNode()
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef MULTICAMNODE_H
|
||||
#define MULTICAMNODE_H
|
||||
|
||||
|
||||
class MultiCamNode
|
||||
{
|
||||
public:
|
||||
MultiCamNode();
|
||||
};
|
||||
|
||||
#endif // MULTICAMNODE_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/input/value/valuenode.h
|
||||
node/input/value/valuenode.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,81 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "valuenode.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
const QString ValueNode::kTypeInput = QStringLiteral("type_in");
|
||||
const QString ValueNode::kValueInput = QStringLiteral("value_in");
|
||||
const QVector<NodeValue::Type> ValueNode::kSupportedTypes = {
|
||||
NodeValue::kFloat,
|
||||
NodeValue::kInt,
|
||||
NodeValue::kRational,
|
||||
NodeValue::kVec2,
|
||||
NodeValue::kVec3,
|
||||
NodeValue::kVec4,
|
||||
NodeValue::kColor,
|
||||
NodeValue::kText,
|
||||
NodeValue::kMatrix,
|
||||
NodeValue::kFont,
|
||||
};
|
||||
|
||||
#define super Node
|
||||
|
||||
ValueNode::ValueNode()
|
||||
{
|
||||
AddInput(kTypeInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
|
||||
|
||||
AddInput(kValueInput, kSupportedTypes.first(), QVariant(), InputFlags(kInputFlagNotConnectable));
|
||||
}
|
||||
|
||||
void ValueNode::Retranslate()
|
||||
{
|
||||
SetInputName(kTypeInput, QStringLiteral("Type"));
|
||||
SetInputName(kValueInput, QStringLiteral("Value"));
|
||||
|
||||
QStringList type_names;
|
||||
type_names.reserve(kSupportedTypes.size());
|
||||
foreach (NodeValue::Type type, kSupportedTypes) {
|
||||
type_names.append(NodeValue::GetPrettyDataTypeName(type));
|
||||
}
|
||||
SetComboBoxStrings(kTypeInput, type_names);
|
||||
}
|
||||
|
||||
NodeValueTable ValueNode::Value(const QString &output, NodeValueDatabase &value) const
|
||||
{
|
||||
Q_UNUSED(output)
|
||||
|
||||
// Pop combobox value off table because no other node will need it
|
||||
value[kTypeInput].Take(NodeValue::kCombo);
|
||||
|
||||
return value.Merge();
|
||||
}
|
||||
|
||||
void ValueNode::InputValueChangedEvent(const QString &input, int element)
|
||||
{
|
||||
if (input == kTypeInput) {
|
||||
SetInputDataType(kValueInput, kSupportedTypes.at(GetStandardValue(kTypeInput).toInt()));
|
||||
}
|
||||
|
||||
super::InputValueChangedEvent(input, element);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef VALUENODE_H
|
||||
#define VALUENODE_H
|
||||
|
||||
#include "node/node.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class ValueNode : public Node
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ValueNode();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(ValueNode)
|
||||
|
||||
virtual Node* copy() const override
|
||||
{
|
||||
return new ValueNode();
|
||||
}
|
||||
|
||||
virtual QString Name() const override
|
||||
{
|
||||
return tr("Value");
|
||||
}
|
||||
|
||||
virtual QString id() const override
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.value");
|
||||
}
|
||||
|
||||
virtual QVector<CategoryID> Category() const override
|
||||
{
|
||||
return {kCategoryInput};
|
||||
}
|
||||
|
||||
virtual QString Description() const override
|
||||
{
|
||||
return tr("Create a single value that can be connected to various other inputs.");
|
||||
}
|
||||
|
||||
static const QString kTypeInput;
|
||||
static const QString kValueInput;
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
virtual NodeValueTable Value(const QString &output, NodeValueDatabase &value) const override;
|
||||
|
||||
protected:
|
||||
virtual void InputValueChangedEvent(const QString &input, int element) override;
|
||||
|
||||
private:
|
||||
static const QVector<NodeValue::Type> kSupportedTypes;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // VALUENODE_H
|
||||
@@ -36,7 +36,7 @@ bool NodeInputDragger::IsStarted() const
|
||||
return input_.IsValid();
|
||||
}
|
||||
|
||||
void NodeInputDragger::Start(const NodeKeyframeTrackReference &input, const rational &time)
|
||||
void NodeInputDragger::Start(const NodeKeyframeTrackReference &input, const rational &time, bool create_key_on_all_tracks)
|
||||
{
|
||||
Q_ASSERT(!IsStarted());
|
||||
|
||||
@@ -52,9 +52,8 @@ void NodeInputDragger::Start(const NodeKeyframeTrackReference &input, const rati
|
||||
// Determine whether we are creating a keyframe or not
|
||||
if (input_.input().IsKeyframing()) {
|
||||
dragging_key_ = node->GetKeyframeAtTimeOnTrack(input_, time);
|
||||
drag_created_key_ = !dragging_key_;
|
||||
|
||||
if (drag_created_key_) {
|
||||
if (!dragging_key_) {
|
||||
dragging_key_ = new NodeKeyframe(time,
|
||||
start_value_,
|
||||
node->GetBestKeyframeTypeForTimeOnTrack(input_, time),
|
||||
@@ -62,6 +61,19 @@ void NodeInputDragger::Start(const NodeKeyframeTrackReference &input, const rati
|
||||
input_.input().element(),
|
||||
input_.input().input(),
|
||||
node);
|
||||
created_keys_.append(dragging_key_);
|
||||
|
||||
if (create_key_on_all_tracks) {
|
||||
int nb_tracks = NodeValue::get_number_of_keyframe_tracks(input.input().node()->GetInputDataType(input.input().input()));
|
||||
for (int i=0; i<nb_tracks; i++) {
|
||||
if (i != input.track()) {
|
||||
NodeKeyframeTrackReference this_ref(input.input(), i);
|
||||
created_keys_.append(new NodeKeyframe(time, node->GetSplitValueAtTimeOnTrack(this_ref, time),
|
||||
node->GetBestKeyframeTypeForTimeOnTrack(this_ref, time),
|
||||
i, input.input().element(), input.input().input(), node));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,9 +124,9 @@ void NodeInputDragger::End()
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
|
||||
if (input_.input().node()->IsInputKeyframing(input_.input())) {
|
||||
if (drag_created_key_) {
|
||||
for (int i=0; i<created_keys_.size(); i++) {
|
||||
// We created a keyframe in this process
|
||||
command->add_child(new NodeParamInsertKeyframeCommand(input_.input().node(), dragging_key_));
|
||||
command->add_child(new NodeParamInsertKeyframeCommand(input_.input().node(), created_keys_.at(i)));
|
||||
}
|
||||
|
||||
// We just set a keyframe's value
|
||||
@@ -129,6 +141,7 @@ void NodeInputDragger::End()
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
|
||||
input_.Reset();
|
||||
created_keys_.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public:
|
||||
|
||||
bool IsStarted() const;
|
||||
|
||||
void Start(const NodeKeyframeTrackReference& input, const rational& time);
|
||||
void Start(const NodeKeyframeTrackReference& input, const rational& time, bool create_key_on_all_tracks = true);
|
||||
|
||||
void Drag(QVariant value);
|
||||
|
||||
@@ -50,8 +50,7 @@ private:
|
||||
QVariant end_value_;
|
||||
|
||||
NodeKeyframe* dragging_key_;
|
||||
|
||||
bool drag_created_key_;
|
||||
QVector<NodeKeyframe*> created_keys_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -27,14 +27,10 @@
|
||||
namespace olive {
|
||||
|
||||
NodeInputImmediate::NodeInputImmediate(NodeValue::Type type, const SplitValue &default_val) :
|
||||
default_value_(default_val),
|
||||
keyframing_(false)
|
||||
{
|
||||
int track_size = NodeValue::get_number_of_keyframe_tracks(type);
|
||||
|
||||
keyframe_tracks_.resize(track_size);
|
||||
standard_value_.resize(track_size);
|
||||
|
||||
set_split_standard_value(default_val);
|
||||
set_data_type(type);
|
||||
}
|
||||
|
||||
void NodeInputImmediate::set_standard_value_on_track(const QVariant &value, int track)
|
||||
@@ -179,6 +175,16 @@ bool NodeInputImmediate::has_keyframe_at_time(const rational &time) const
|
||||
return false;
|
||||
}
|
||||
|
||||
void NodeInputImmediate::set_data_type(NodeValue::Type type)
|
||||
{
|
||||
int track_size = NodeValue::get_number_of_keyframe_tracks(type);
|
||||
|
||||
keyframe_tracks_.resize(track_size);
|
||||
standard_value_.resize(track_size);
|
||||
|
||||
set_split_standard_value(default_value_);
|
||||
}
|
||||
|
||||
NodeKeyframe *NodeInputImmediate::get_earliest_keyframe() const
|
||||
{
|
||||
NodeKeyframe* earliest = nullptr;
|
||||
|
||||
@@ -151,12 +151,19 @@ public:
|
||||
return (!is_keyframing() || keyframe_tracks_.at(track).isEmpty());
|
||||
}
|
||||
|
||||
void set_data_type(NodeValue::Type type);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Non-keyframed value
|
||||
*/
|
||||
SplitValue standard_value_;
|
||||
|
||||
/**
|
||||
* @brief Default value
|
||||
*/
|
||||
SplitValue default_value_;
|
||||
|
||||
/**
|
||||
* @brief Internal keyframe array
|
||||
*
|
||||
|
||||
@@ -107,13 +107,16 @@ void MergeNode::Hash(const QString &output, QCryptographicHash &hash, const rati
|
||||
// connected node happens to return nothing (a gap for instance). Therefore we only add our
|
||||
// fingerprint if the base AND the blend change the hash. Otherwise, we assume it's a passthrough.
|
||||
|
||||
Q_UNUSED(output)
|
||||
|
||||
QByteArray current_result = hash.result();
|
||||
|
||||
bool base_changed_hash = false;
|
||||
bool blend_changed_hash = false;
|
||||
|
||||
if (IsInputConnected(kBaseIn)) {
|
||||
GetConnectedNode(kBaseIn)->Hash(output, hash, time);
|
||||
NodeOutput base_output = GetConnectedOutput(kBaseIn);
|
||||
base_output.node()->Hash(base_output.output(), hash, time);
|
||||
|
||||
QByteArray post_base_hash = hash.result();
|
||||
base_changed_hash = (post_base_hash != current_result);
|
||||
@@ -121,7 +124,8 @@ void MergeNode::Hash(const QString &output, QCryptographicHash &hash, const rati
|
||||
}
|
||||
|
||||
if(IsInputConnected(kBlendIn)) {
|
||||
GetConnectedNode(kBlendIn)->Hash(output, hash, time);
|
||||
NodeOutput blend_output = GetConnectedOutput(kBlendIn);
|
||||
blend_output.node()->Hash(blend_output.output(), hash, time);
|
||||
|
||||
blend_changed_hash = (hash.result() != current_result);
|
||||
}
|
||||
|
||||
+23
-9
@@ -48,7 +48,8 @@ Node::Node(bool create_default_output) :
|
||||
override_color_(-1),
|
||||
last_change_time_(0),
|
||||
folder_(nullptr),
|
||||
operation_stack_(0)
|
||||
operation_stack_(0),
|
||||
cache_result_(false)
|
||||
{
|
||||
if (create_default_output) {
|
||||
AddOutput();
|
||||
@@ -504,10 +505,15 @@ NodeValue::Type Node::GetInputDataType(const QString &id) const
|
||||
|
||||
void Node::SetInputDataType(const QString &id, const NodeValue::Type &type)
|
||||
{
|
||||
Input* i = GetInternalInputData(id);
|
||||
Input* input_meta = GetInternalInputData(id);
|
||||
|
||||
if (i) {
|
||||
i->type = type;
|
||||
if (input_meta) {
|
||||
input_meta->type = type;
|
||||
|
||||
int array_sz = InputArraySize(id);
|
||||
for (int i=-1; i<array_sz; i++) {
|
||||
GetImmediate(id, i)->set_data_type(type);
|
||||
}
|
||||
|
||||
emit InputDataTypeChanged(id, type);
|
||||
} else {
|
||||
@@ -692,7 +698,12 @@ SplitValue Node::GetSplitDefaultValue(const QString &input) const
|
||||
|
||||
QVariant Node::GetSplitDefaultValueOnTrack(const QString &input, int track) const
|
||||
{
|
||||
return GetSplitDefaultValue(input).at(track);
|
||||
SplitValue val = GetSplitDefaultValue(input);
|
||||
if (track < val.size()) {
|
||||
return val.at(track);
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
const QVector<NodeKeyframeTrack> &Node::GetKeyframeTracks(const QString &input, int element) const
|
||||
@@ -2285,12 +2296,15 @@ void NodeSetPositionAndShiftSurroundingsCommand::redo()
|
||||
foreach (Node* surrounding, node_->parent()->nodes()) {
|
||||
if (bounding_rect.contains(surrounding->GetPosition()) && surrounding != node_) {
|
||||
QPointF new_pos = surrounding->GetPosition();
|
||||
if (surrounding->GetPosition().y() > position_.y()) {
|
||||
new_pos.setY(new_pos.y() + 0.5);
|
||||
} else {
|
||||
new_pos.setY(new_pos.y() - 0.5);
|
||||
|
||||
qreal move_rate = 0.50;
|
||||
|
||||
if (surrounding->GetPosition().y() < position_.y()) {
|
||||
move_rate = -move_rate;
|
||||
}
|
||||
|
||||
new_pos.setY(new_pos.y() + move_rate);
|
||||
|
||||
auto sur_command = new NodeSetPositionAndShiftSurroundingsCommand(surrounding, new_pos, true);
|
||||
sur_command->redo();
|
||||
commands_.append(sur_command);
|
||||
|
||||
@@ -756,6 +756,16 @@ public:
|
||||
folder_ = folder;
|
||||
}
|
||||
|
||||
bool GetCacheTextures() const
|
||||
{
|
||||
return cache_result_;
|
||||
}
|
||||
|
||||
void SetCacheTextures(bool e)
|
||||
{
|
||||
cache_result_ = e;
|
||||
}
|
||||
|
||||
static const QString kDefaultOutput;
|
||||
|
||||
protected:
|
||||
@@ -1191,6 +1201,8 @@ private:
|
||||
|
||||
int operation_stack_;
|
||||
|
||||
bool cache_result_;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time
|
||||
|
||||
@@ -574,11 +574,13 @@ bool Track::IsLocked() const
|
||||
|
||||
void Track::Hash(const QString &output, QCryptographicHash &hash, const rational &time) const
|
||||
{
|
||||
Q_UNUSED(output)
|
||||
|
||||
Block* b = BlockAtTime(time);
|
||||
|
||||
// Defer to block at this time, don't add any of our own information to the hash
|
||||
if (b) {
|
||||
b->Hash(output, hash, TransformTimeForBlock(b, time));
|
||||
b->Hash(kDefaultOutput, hash, TransformTimeForBlock(b, time));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,8 @@ const uint64_t ViewerOutput::kVideoParamEditMask = VideoParamEdit::kWidthHeight
|
||||
|
||||
ViewerOutput::ViewerOutput(bool create_default_streams) :
|
||||
video_frame_cache_(this),
|
||||
audio_playback_cache_(this)
|
||||
audio_playback_cache_(this),
|
||||
cache_enabled_(true)
|
||||
{
|
||||
AddInput(kVideoParamsInput, NodeValue::kVideoParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray));
|
||||
SetInputProperty(kVideoParamsInput, QStringLiteral("mask"), QVariant::fromValue(kVideoParamEditMask));
|
||||
@@ -195,14 +196,18 @@ void ViewerOutput::set_default_parameters()
|
||||
|
||||
void ViewerOutput::ShiftVideoCache(const rational &from, const rational &to)
|
||||
{
|
||||
video_frame_cache_.Shift(from, to);
|
||||
if (cache_enabled_) {
|
||||
video_frame_cache_.Shift(from, to);
|
||||
}
|
||||
|
||||
ShiftVideoEvent(from, to);
|
||||
}
|
||||
|
||||
void ViewerOutput::ShiftAudioCache(const rational &from, const rational &to)
|
||||
{
|
||||
audio_playback_cache_.Shift(from, to);
|
||||
if (cache_enabled_) {
|
||||
audio_playback_cache_.Shift(from, to);
|
||||
}
|
||||
|
||||
ShiftAudioEvent(from, to);
|
||||
}
|
||||
@@ -217,16 +222,18 @@ void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from,
|
||||
{
|
||||
Q_UNUSED(element)
|
||||
|
||||
if (from == kTextureInput || from == kSamplesInput
|
||||
|| from == kVideoParamsInput || from == kAudioParamsInput) {
|
||||
TimeRange invalidated_range(qMax(rational(), range.in()),
|
||||
qMin(GetLength(), range.out()));
|
||||
if (cache_enabled_) {
|
||||
if (from == kTextureInput || from == kSamplesInput
|
||||
|| from == kVideoParamsInput || from == kAudioParamsInput) {
|
||||
TimeRange invalidated_range(qMax(rational(), range.in()),
|
||||
qMin(GetLength(), range.out()));
|
||||
|
||||
if (invalidated_range.in() != invalidated_range.out()) {
|
||||
if (from == kTextureInput || from == kVideoParamsInput) {
|
||||
video_frame_cache_.Invalidate(invalidated_range, job_time);
|
||||
} else {
|
||||
audio_playback_cache_.Invalidate(invalidated_range, job_time);
|
||||
if (invalidated_range.in() != invalidated_range.out()) {
|
||||
if (from == kTextureInput || from == kVideoParamsInput) {
|
||||
video_frame_cache_.Invalidate(invalidated_range, job_time);
|
||||
} else {
|
||||
audio_playback_cache_.Invalidate(invalidated_range, job_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,7 +314,9 @@ void ViewerOutput::VerifyLength()
|
||||
video_length = t.Get(NodeValue::kRational, QStringLiteral("length")).value<rational>();
|
||||
}
|
||||
|
||||
video_frame_cache_.SetLength(video_length);
|
||||
if (cache_enabled_) {
|
||||
video_frame_cache_.SetLength(video_length);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
@@ -318,7 +327,9 @@ void ViewerOutput::VerifyLength()
|
||||
audio_length = t.Get(NodeValue::kRational, QStringLiteral("length")).value<rational>();
|
||||
}
|
||||
|
||||
audio_playback_cache_.SetLength(audio_length);
|
||||
if (cache_enabled_) {
|
||||
audio_playback_cache_.SetLength(audio_length);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
@@ -392,7 +403,9 @@ void ViewerOutput::InputValueChangedEvent(const QString &input, int element)
|
||||
}
|
||||
|
||||
if (frame_rate_changed) {
|
||||
video_frame_cache_.SetTimebase(new_video_params.frame_rate_as_time_base());
|
||||
if (cache_enabled_) {
|
||||
video_frame_cache_.SetTimebase(new_video_params.frame_rate_as_time_base());
|
||||
}
|
||||
emit FrameRateChanged(new_video_params.frame_rate());
|
||||
}
|
||||
|
||||
@@ -412,7 +425,9 @@ void ViewerOutput::InputValueChangedEvent(const QString &input, int element)
|
||||
|
||||
emit AudioParamsChanged();
|
||||
|
||||
audio_playback_cache_.SetParameters(GetAudioParams());
|
||||
if (cache_enabled_) {
|
||||
audio_playback_cache_.SetParameters(GetAudioParams());
|
||||
}
|
||||
|
||||
cached_audio_params_ = new_audio_params;
|
||||
|
||||
@@ -514,6 +529,11 @@ int ViewerOutput::AddStream(Track::Type type, const QVariant& value)
|
||||
return index;
|
||||
}
|
||||
|
||||
void ViewerOutput::SetViewerCacheEnabled(bool e)
|
||||
{
|
||||
cache_enabled_ = e;
|
||||
}
|
||||
|
||||
void ViewerOutput::InputResized(const QString &input, int old_size, int new_size)
|
||||
{
|
||||
if (input == kVideoParamsInput || input == kAudioParamsInput) {
|
||||
|
||||
@@ -188,6 +188,8 @@ protected:
|
||||
|
||||
int AddStream(Track::Type type, const QVariant &value);
|
||||
|
||||
void SetViewerCacheEnabled(bool e);
|
||||
|
||||
private:
|
||||
rational last_length_;
|
||||
|
||||
@@ -203,6 +205,8 @@ private:
|
||||
|
||||
TimelinePoints timeline_points_;
|
||||
|
||||
bool cache_enabled_;
|
||||
|
||||
private slots:
|
||||
void InputResized(const QString& input, int old_size, int new_size);
|
||||
|
||||
|
||||
@@ -48,6 +48,9 @@ Footage::Footage(const QString &filename) :
|
||||
Clear();
|
||||
|
||||
set_filename(filename);
|
||||
|
||||
SetCacheTextures(true);
|
||||
SetViewerCacheEnabled(false);
|
||||
}
|
||||
|
||||
void Footage::Retranslate()
|
||||
@@ -298,66 +301,27 @@ QString Footage::DescribeAudioStream(const AudioParams ¶ms)
|
||||
QString::number(params.sample_rate()));
|
||||
}
|
||||
|
||||
bool Footage::CompareFootageToFile(Footage *footage, const QString &filename)
|
||||
{
|
||||
// Heuristic to determine if file has changed
|
||||
QFileInfo info(filename);
|
||||
|
||||
if (info.exists()) {
|
||||
/*if (info.lastModified().toMSecsSinceEpoch() == footage->timestamp()) {
|
||||
// Footage has not been modified and is where we expect
|
||||
return true;
|
||||
} else {
|
||||
// Footage may have changed and we'll have to re-probe it. It also may not have, in which
|
||||
// case nothing needs to change.
|
||||
DecoderPtr decoder = Decoder::CreateFromID(footage->decoder());
|
||||
|
||||
Streams probed_streams = decoder->Probe(filename, nullptr);
|
||||
|
||||
if (probed_streams == footage->streams_) {
|
||||
return true;
|
||||
}
|
||||
}*/
|
||||
Q_UNUSED(footage)
|
||||
|
||||
// Simplified, since our footage node is much more tolerant, we'll try this
|
||||
return true;
|
||||
}
|
||||
|
||||
// Footage file couldn't be found or resolved to something we didn't expect
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Footage::CompareFootageToItsFilename(Footage *footage)
|
||||
{
|
||||
return CompareFootageToFile(footage, footage->filename());
|
||||
}
|
||||
|
||||
void Footage::Hash(const QString& output, QCryptographicHash &hash, const rational &time) const
|
||||
{
|
||||
super::Hash(output, hash, time);
|
||||
|
||||
// Footage last modified date
|
||||
hash.addData(QString::number(timestamp()).toUtf8());
|
||||
|
||||
// Translate output ID to stream
|
||||
Track::Reference ref = Track::Reference::FromString(output);
|
||||
|
||||
QString fn = filename();
|
||||
|
||||
if (!fn.isEmpty()) {
|
||||
if (ref.type() == Track::kVideo) {
|
||||
VideoParams params = GetVideoParams(ref.index());
|
||||
|
||||
if (params.is_valid()) {
|
||||
// Add footage details to hash
|
||||
|
||||
// Footage filename
|
||||
hash.addData(filename().toUtf8());
|
||||
|
||||
// Footage last modified date
|
||||
hash.addData(QString::number(timestamp()).toUtf8());
|
||||
QString fn = filename();
|
||||
|
||||
// Footage stream
|
||||
hash.addData(QString::number(ref.index()).toUtf8());
|
||||
|
||||
if (ref.type() == Track::kVideo) {
|
||||
if (!fn.isEmpty()) {
|
||||
// Current color config and space
|
||||
hash.addData(project()->color_manager()->GetConfigFilename().toUtf8());
|
||||
hash.addData(GetColorspaceToUse(params).toUtf8());
|
||||
@@ -373,10 +337,11 @@ void Footage::Hash(const QString& output, QCryptographicHash &hash, const ration
|
||||
int64_t video_ts = Timecode::time_to_timestamp(time, params.time_base());
|
||||
|
||||
// Add timestamp in units of the video stream's timebase
|
||||
hash.addData(reinterpret_cast<const char*>(&video_ts), sizeof(int64_t));
|
||||
hash.addData(reinterpret_cast<const char*>(&video_ts), sizeof(video_ts));
|
||||
|
||||
// Add start time - used for both image sequences and video streams
|
||||
hash.addData(QString::number(params.start_time()).toUtf8());
|
||||
auto start_time = params.start_time();
|
||||
hash.addData(reinterpret_cast<const char*>(&start_time), sizeof(start_time));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -490,58 +455,6 @@ void Footage::UpdateTooltip()
|
||||
}
|
||||
}
|
||||
|
||||
/*void Footage::AddStreamAsInput(Track::Type type, int index, QVariant value)
|
||||
{
|
||||
QString input_id = GetInputIDOfIndex(type, index);
|
||||
|
||||
Track::Reference ref(type, index);
|
||||
|
||||
// Create input for parameters
|
||||
NodeValue::Type value_type;
|
||||
uint64_t param_mask = 0;
|
||||
|
||||
if (type == Track::kVideo) {
|
||||
VideoParams vp = value.value<VideoParams>();
|
||||
value_type = NodeValue::kVideoParams;
|
||||
|
||||
// Universal parameters for video/image footage
|
||||
param_mask |= VideoParamEdit::kEnabled;
|
||||
param_mask |= VideoParamEdit::kColorspace;
|
||||
param_mask |= VideoParamEdit::kPixelAspect;
|
||||
param_mask |= VideoParamEdit::kInterlacing;
|
||||
|
||||
if (vp.channel_count() == VideoParams::kRGBAChannelCount) {
|
||||
// If this has an alpha channel, add a premultiplied optino
|
||||
param_mask |= VideoParamEdit::kPremultipliedAlpha;
|
||||
}
|
||||
|
||||
if (vp.video_type() != VideoParams::kVideoTypeVideo) {
|
||||
// This is either a still image or an image sequence, add properties for those
|
||||
param_mask |= VideoParamEdit::kIsImageSequence;
|
||||
param_mask |= VideoParamEdit::kStartTime;
|
||||
param_mask |= VideoParamEdit::kEndTime;
|
||||
param_mask |= VideoParamEdit::kFrameRate;
|
||||
} else {
|
||||
// Ensure timebase isn't overwritten by the frame rate field
|
||||
param_mask |= VideoParamEdit::kFrameRateIsNotTimebase;
|
||||
}
|
||||
} else {
|
||||
value_type = NodeValue::kAudioParams;
|
||||
param_mask = 0;
|
||||
}
|
||||
|
||||
AddInput(input_id, value_type,
|
||||
InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
|
||||
SetStandardValue(input_id, value);
|
||||
SetInputProperty(input_id, QStringLiteral("mask"), QVariant::fromValue(param_mask));
|
||||
inputs_for_stream_properties_.insert(ref, input_id);
|
||||
|
||||
// Create output for stream
|
||||
QString output_id = Track::Reference(type, index).ToString();
|
||||
AddOutput(output_id);
|
||||
outputs_for_streams_.insert(ref, output_id);
|
||||
}*/
|
||||
|
||||
void Footage::CheckFootage()
|
||||
{
|
||||
QString fn = filename();
|
||||
@@ -559,21 +472,4 @@ void Footage::CheckFootage()
|
||||
}
|
||||
}
|
||||
|
||||
/*QString Track::Reference::video_colorspace(bool default_if_empty) const
|
||||
{
|
||||
if (IsValid()) {
|
||||
VideoParams params = footage_->GetVideoParams(index_);
|
||||
|
||||
if (params.is_valid()) {
|
||||
if (params.colorspace().isEmpty() && default_if_empty) {
|
||||
|
||||
} else {
|
||||
return params.colorspace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
@@ -170,9 +170,6 @@ public:
|
||||
static QString DescribeVideoStream(const VideoParams& params);
|
||||
static QString DescribeAudioStream(const AudioParams& params);
|
||||
|
||||
static bool CompareFootageToFile(Footage* footage, const QString& filename);
|
||||
static bool CompareFootageToItsFilename(Footage* footage);
|
||||
|
||||
virtual void Hash(const QString& output, QCryptographicHash &hash, const rational &time) const override;
|
||||
|
||||
virtual NodeValueTable Value(const QString &output, NodeValueDatabase& value) const override;
|
||||
|
||||
@@ -220,16 +220,6 @@ void Project::set_filename(const QString &s)
|
||||
emit NameChanged();
|
||||
}
|
||||
|
||||
ColorManager *Project::color_manager()
|
||||
{
|
||||
return color_manager_;
|
||||
}
|
||||
|
||||
bool Project::is_modified() const
|
||||
{
|
||||
return is_modified_;
|
||||
}
|
||||
|
||||
void Project::set_modified(bool e)
|
||||
{
|
||||
is_modified_ = e;
|
||||
|
||||
@@ -62,9 +62,10 @@ public:
|
||||
QString pretty_filename() const;
|
||||
void set_filename(const QString& s);
|
||||
|
||||
ColorManager* color_manager();
|
||||
ColorManager* color_manager() { return color_manager_; }
|
||||
ProjectSettingsNode* settings() { return settings_; }
|
||||
|
||||
bool is_modified() const;
|
||||
bool is_modified() const { return is_modified_; }
|
||||
void set_modified(bool e);
|
||||
|
||||
bool has_autorecovery_been_saved() const;
|
||||
|
||||
@@ -454,7 +454,7 @@ void ProjectViewModel::DisconnectItem(Node *n)
|
||||
disconnect(f, &Folder::BeginRemoveItem, this, &ProjectViewModel::ItemRemoved);
|
||||
|
||||
foreach (Node* c, f->children()) {
|
||||
ConnectItem(c);
|
||||
DisconnectItem(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+27
-10
@@ -22,6 +22,7 @@
|
||||
|
||||
#include "node.h"
|
||||
#include "render/job/footagejob.h"
|
||||
#include "render/rendermanager.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -110,7 +111,7 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const QString& output
|
||||
// By this point, the node should have all the inputs it needs to render correctly
|
||||
NodeValueTable table = n->Value(output, database);
|
||||
|
||||
PostProcessTable(n, range, table);
|
||||
PostProcessTable(n, output, range, table);
|
||||
|
||||
return table;
|
||||
}
|
||||
@@ -171,10 +172,15 @@ QVariant NodeTraverser::ProcessFrameGeneration(const Node *node, const GenerateJ
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant NodeTraverser::GetCachedFrame(const Node *node, const rational &time)
|
||||
void NodeTraverser::SaveCachedTexture(const QByteArray &hash, const QVariant &texture)
|
||||
{
|
||||
Q_UNUSED(node)
|
||||
Q_UNUSED(time)
|
||||
Q_UNUSED(hash)
|
||||
Q_UNUSED(texture)
|
||||
}
|
||||
|
||||
QVariant NodeTraverser::GetCachedTexture(const QByteArray& hash)
|
||||
{
|
||||
Q_UNUSED(hash)
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
@@ -190,17 +196,23 @@ void NodeTraverser::AddGlobalsToDatabase(NodeValueDatabase &db, const TimeRange&
|
||||
db.Insert(QStringLiteral("global"), global);
|
||||
}
|
||||
|
||||
void NodeTraverser::PostProcessTable(const Node *node, const TimeRange &range, NodeValueTable &output_params)
|
||||
void NodeTraverser::PostProcessTable(const Node *node, const QString& output, const TimeRange &range, NodeValueTable &output_params)
|
||||
{
|
||||
bool got_cached_frame = false;
|
||||
QByteArray cached_node_hash;
|
||||
|
||||
// Convert footage to image/sample buffers
|
||||
QVariant cached_frame = GetCachedFrame(node, range.in());
|
||||
if (!cached_frame.isNull()) {
|
||||
output_params.Push(NodeValue::kTexture, cached_frame, node);
|
||||
if (CanCacheFrames() && node->GetCacheTextures()) {
|
||||
// This node is set to cache the result, see if we can retrieved a previously cached version
|
||||
cached_node_hash = RenderManager::Hash(node, output, GetCacheVideoParams(), range.in());
|
||||
|
||||
// No more to do here
|
||||
got_cached_frame = true;
|
||||
QVariant cached_frame = GetCachedTexture(cached_node_hash);
|
||||
if (!cached_frame.isNull()) {
|
||||
output_params.Push(NodeValue::kTexture, cached_frame, node);
|
||||
|
||||
// No more to do here
|
||||
got_cached_frame = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Strip out any jobs or footage
|
||||
@@ -285,6 +297,11 @@ void NodeTraverser::PostProcessTable(const Node *node, const TimeRange &range, N
|
||||
output_params.Push(NodeValue::kSamples, value, node);
|
||||
}
|
||||
}
|
||||
|
||||
if (CanCacheFrames() && node->GetCacheTextures() && !got_cached_frame) {
|
||||
// Save cached texture
|
||||
SaveCachedTexture(cached_node_hash, output_params.Get(NodeValue::kTexture));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
-2
@@ -59,7 +59,19 @@ protected:
|
||||
|
||||
virtual QVariant ProcessFrameGeneration(const Node *node, const GenerateJob& job);
|
||||
|
||||
virtual QVariant GetCachedFrame(const Node *node, const rational &time);
|
||||
virtual QVariant GetCachedTexture(const QByteArray& hash);
|
||||
|
||||
virtual void SaveCachedTexture(const QByteArray& hash, const QVariant& texture);
|
||||
|
||||
virtual bool CanCacheFrames()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual VideoParams GetCacheVideoParams()
|
||||
{
|
||||
return VideoParams();
|
||||
}
|
||||
|
||||
void AddGlobalsToDatabase(NodeValueDatabase& db, const TimeRange &range) const;
|
||||
|
||||
@@ -69,7 +81,7 @@ protected:
|
||||
}
|
||||
|
||||
private:
|
||||
void PostProcessTable(const Node *node, const TimeRange &range, NodeValueTable &output_params);
|
||||
void PostProcessTable(const Node *node, const QString &output, const TimeRange &range, NodeValueTable &output_params);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -187,14 +187,24 @@ bool FrameHashCache::SaveCacheFrame(const QByteArray& hash,
|
||||
const VideoParams& vparam,
|
||||
int linesize_bytes) const
|
||||
{
|
||||
QString fn = CachePathName(hash);
|
||||
return SaveCacheFrame(GetCacheDirectory(), hash, data, vparam, linesize_bytes);
|
||||
}
|
||||
|
||||
bool FrameHashCache::SaveCacheFrame(const QByteArray &hash, FramePtr frame) const
|
||||
{
|
||||
return SaveCacheFrame(GetCacheDirectory(), hash, frame);
|
||||
}
|
||||
|
||||
bool FrameHashCache::SaveCacheFrame(const QString &cache_path, const QByteArray &hash, char *data, const VideoParams &vparam, int linesize_bytes)
|
||||
{
|
||||
QString fn = CachePathName(cache_path, hash);
|
||||
|
||||
if (SaveCacheFrame(fn, data, vparam, linesize_bytes)) {
|
||||
// Register frame with the disk manager
|
||||
QMetaObject::invokeMethod(DiskManager::instance(),
|
||||
"CreatedFile",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(QString, GetCacheDirectory()),
|
||||
Q_ARG(QString, cache_path),
|
||||
Q_ARG(QString, fn),
|
||||
Q_ARG(QByteArray, hash));
|
||||
|
||||
@@ -204,10 +214,10 @@ bool FrameHashCache::SaveCacheFrame(const QByteArray& hash,
|
||||
}
|
||||
}
|
||||
|
||||
bool FrameHashCache::SaveCacheFrame(const QByteArray &hash, FramePtr frame) const
|
||||
bool FrameHashCache::SaveCacheFrame(const QString &cache_path, const QByteArray &hash, FramePtr frame)
|
||||
{
|
||||
if (frame) {
|
||||
return SaveCacheFrame(hash, frame->data(), frame->video_params(), frame->linesize_bytes());
|
||||
return SaveCacheFrame(cache_path, hash, frame->data(), frame->video_params(), frame->linesize_bytes());
|
||||
} else {
|
||||
qWarning() << "Attempted to save a NULL frame to the cache. This may or may not be desirable.";
|
||||
return false;
|
||||
@@ -394,7 +404,7 @@ QString FrameHashCache::CachePathName(const QString &cache_path, const QByteArra
|
||||
return cache_dir.filePath(filename);
|
||||
}
|
||||
|
||||
bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const VideoParams &vparam, int linesize_bytes) const
|
||||
bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const VideoParams &vparam, int linesize_bytes)
|
||||
{
|
||||
if (!VideoParams::FormatIsFloat(vparam.format())) {
|
||||
qCritical() << "Tried to cache frame with non-float pixel format";
|
||||
|
||||
@@ -61,9 +61,11 @@ public:
|
||||
QString CachePathName(const QByteArray &hash) const;
|
||||
static QString CachePathName(const QString& cache_path, const QByteArray &hash);
|
||||
|
||||
bool SaveCacheFrame(const QString& filename, char *data, const VideoParams &vparam, int linesize_bytes) const;
|
||||
static bool SaveCacheFrame(const QString& filename, char *data, const VideoParams &vparam, int linesize_bytes);
|
||||
bool SaveCacheFrame(const QByteArray& hash, char *data, const VideoParams &vparam, int linesize_bytes) const;
|
||||
bool SaveCacheFrame(const QByteArray& hash, FramePtr frame) const;
|
||||
static bool SaveCacheFrame(const QString& cache_path, const QByteArray& hash, char *data, const VideoParams &vparam, int linesize_bytes);
|
||||
static bool SaveCacheFrame(const QString& cache_path, const QByteArray& hash, FramePtr frame);
|
||||
static FramePtr LoadCacheFrame(const QString& cache_path, const QByteArray& hash);
|
||||
FramePtr LoadCacheFrame(const QByteArray& hash) const;
|
||||
static FramePtr LoadCacheFrame(const QString& fn);
|
||||
|
||||
@@ -69,7 +69,7 @@ void PreviewAutoCacher::GenerateHashes(ViewerOutput *viewer, FrameHashCache* cac
|
||||
|
||||
foreach (const rational& time, times) {
|
||||
// See if hash already exists in disk cache
|
||||
QByteArray hash = RenderManager::Hash(viewer->GetConnectedNode(ViewerOutput::kTextureInput), viewer->GetVideoParams(), time);
|
||||
QByteArray hash = RenderManager::Hash(viewer->GetConnectedTextureOutput(), viewer->GetVideoParams(), time);
|
||||
|
||||
// Check memory list since disk checking is slow
|
||||
bool hash_exists = (std::find(existing_hashes.begin(), existing_hashes.end(), hash) != existing_hashes.end());
|
||||
@@ -370,6 +370,8 @@ void PreviewAutoCacher::ClearHashQueue(bool wait)
|
||||
for (auto it=copy.cbegin(); it!=copy.cend(); it++) {
|
||||
(*it)->waitForFinished();
|
||||
}
|
||||
|
||||
hash_tasks_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,12 +388,21 @@ void PreviewAutoCacher::ClearVideoQueue(bool wait)
|
||||
watcher->Cancel();
|
||||
}
|
||||
if (wait) {
|
||||
// Re-copy because the above cancels may have deleted these watchers
|
||||
vt_copy = video_tasks_;
|
||||
sft_copy = single_frame_tasks_;
|
||||
|
||||
for (auto it=vt_copy.cbegin(); it!=vt_copy.cend(); it++) {
|
||||
it.key()->WaitForFinished();
|
||||
}
|
||||
foreach (RenderTicketWatcher* watcher, sft_copy) {
|
||||
watcher->WaitForFinished();
|
||||
}
|
||||
|
||||
// If we're waiting, we prioritize clearing the cache. Otherwise, we assume that tasks can still
|
||||
// finish after this function returns.
|
||||
video_tasks_.clear();
|
||||
single_frame_tasks_.clear();
|
||||
}
|
||||
|
||||
has_changed_ = true;
|
||||
@@ -411,6 +422,8 @@ void PreviewAutoCacher::ClearAudioQueue(bool wait)
|
||||
for (auto it=copy.cbegin(); it!=copy.cend(); it++) {
|
||||
it.key()->WaitForFinished();
|
||||
}
|
||||
|
||||
audio_tasks_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,6 +440,8 @@ void PreviewAutoCacher::ClearVideoDownloadQueue(bool wait)
|
||||
for (auto it=copy.cbegin(); it!=copy.cend(); it++) {
|
||||
it.key()->WaitForFinished();
|
||||
}
|
||||
|
||||
video_download_tasks_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ RenderManager::~RenderManager()
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray RenderManager::Hash(const Node *n, const VideoParams ¶ms, const rational &time)
|
||||
QByteArray RenderManager::Hash(const Node *n, const QString& output, const VideoParams ¶ms, const rational &time)
|
||||
{
|
||||
QCryptographicHash hasher(QCryptographicHash::Sha1);
|
||||
|
||||
@@ -93,7 +93,7 @@ QByteArray RenderManager::Hash(const Node *n, const VideoParams ¶ms, const r
|
||||
hasher.addData(reinterpret_cast<const char*>(&format), sizeof(VideoParams::Format));
|
||||
|
||||
if (n) {
|
||||
n->Hash(Node::kDefaultOutput, hasher, time);
|
||||
n->Hash(output, hasher, time);
|
||||
}
|
||||
|
||||
return hasher.result();
|
||||
@@ -189,7 +189,7 @@ RenderTicketPtr RenderManager::SaveFrameToCache(FrameHashCache *cache, FramePtr
|
||||
// Create ticket
|
||||
RenderTicketPtr ticket = std::make_shared<RenderTicket>();
|
||||
|
||||
ticket->setProperty("cache", Node::PtrToValue(cache));
|
||||
ticket->setProperty("cache", cache->GetCacheDirectory());
|
||||
ticket->setProperty("frame", QVariant::fromValue(frame));
|
||||
ticket->setProperty("hash", hash);
|
||||
ticket->setProperty("type", kTypeVideoDownload);
|
||||
|
||||
@@ -67,7 +67,11 @@ public:
|
||||
/**
|
||||
* @brief Generate a unique identifier for a certain node at a certain time
|
||||
*/
|
||||
static QByteArray Hash(const Node *n, const VideoParams ¶ms, const rational &time);
|
||||
static QByteArray Hash(const Node *n, const QString &output, const VideoParams ¶ms, const rational &time);
|
||||
static QByteArray Hash(const NodeOutput &output, const VideoParams ¶ms, const rational &time)
|
||||
{
|
||||
return Hash(output.node(), output.output(), params, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Asynchronously generate a frame at a given time
|
||||
|
||||
@@ -148,11 +148,11 @@ void RenderProcessor::Run()
|
||||
}
|
||||
case RenderManager::kTypeVideoDownload:
|
||||
{
|
||||
FrameHashCache* cache = Node::ValueToPtr<FrameHashCache>(ticket_->property("cache"));
|
||||
QString cache = ticket_->property("cache").toString();
|
||||
FramePtr frame = ticket_->property("frame").value<FramePtr>();
|
||||
QByteArray hash = ticket_->property("hash").toByteArray();
|
||||
|
||||
ticket_->Finish(cache->SaveCacheFrame(hash, frame), false);
|
||||
ticket_->Finish(FrameHashCache::SaveCacheFrame(cache, hash, frame), false);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -226,19 +226,18 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim
|
||||
continue;
|
||||
}
|
||||
|
||||
// FIXME: Doesn't handle reversing
|
||||
if (b->IsInputKeyframing(Block::kSpeedInput) || b->IsInputConnected(Block::kSpeedInput)) {
|
||||
// FIXME: We'll need to calculate the speed hoo boy
|
||||
} else {
|
||||
double speed_value = b->GetStandardValue(Block::kSpeedInput).toDouble();
|
||||
double speed_value = b->GetStandardValue(Block::kSpeedInput).toDouble();
|
||||
|
||||
if (qIsNull(speed_value)) {
|
||||
// Just silence, don't think there's any other practical application of 0 speed audio
|
||||
samples_from_this_block->fill(0);
|
||||
} else if (!qFuzzyCompare(speed_value, 1.0)) {
|
||||
// Multiply time
|
||||
samples_from_this_block->speed(speed_value);
|
||||
}
|
||||
if (qIsNull(speed_value)) {
|
||||
// Just silence, don't think there's any other practical application of 0 speed audio
|
||||
samples_from_this_block->fill(0);
|
||||
} else if (!qFuzzyCompare(speed_value, 1.0)) {
|
||||
// Multiply time
|
||||
samples_from_this_block->speed(speed_value);
|
||||
}
|
||||
|
||||
if (b->GetStandardValue(Block::kReverseInput).toBool()) {
|
||||
samples_from_this_block->reverse();
|
||||
}
|
||||
|
||||
int copy_length = qMin(max_dest_sz, samples_from_this_block->sample_count());
|
||||
@@ -274,6 +273,11 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim
|
||||
|
||||
QVariant RenderProcessor::ProcessVideoFootage(const FootageJob &stream, const rational &input_time)
|
||||
{
|
||||
if (ticket_->property("type").value<RenderManager::TicketType>() != RenderManager::kTypeVideo) {
|
||||
// Video cannot contribute to audio, so we do nothing here
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
TexturePtr value = nullptr;
|
||||
|
||||
// Check the still frame cache. On large frames such as high resolution still images, uploading
|
||||
@@ -534,34 +538,62 @@ QVariant RenderProcessor::ProcessFrameGeneration(const Node *node, const Generat
|
||||
return QVariant::fromValue(texture);
|
||||
}
|
||||
|
||||
QVariant RenderProcessor::GetCachedFrame(const Node *node, const rational &time)
|
||||
bool RenderProcessor::CanCacheFrames()
|
||||
{
|
||||
if (!ticket_->property("cache").toString().isEmpty()
|
||||
&& node->id() == QStringLiteral("org.olivevideoeditor.Olive.videoinput")) {
|
||||
const VideoParams& video_params = ticket_->property("vparam").value<VideoParams>();
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray hash = RenderManager::Hash(node, video_params, time);
|
||||
QVariant RenderProcessor::GetCachedTexture(const QByteArray& hash)
|
||||
{
|
||||
VideoParams video_params = GetCacheVideoParams();
|
||||
QString cache_dir = ticket_->property("cache").toString();
|
||||
|
||||
FramePtr f = FrameHashCache::LoadCacheFrame(ticket_->property("cache").toString(), hash);
|
||||
FramePtr f = FrameHashCache::LoadCacheFrame(cache_dir, hash);
|
||||
|
||||
if (f) {
|
||||
// The cached frame won't load with the correct divider by default, so we enforce it here
|
||||
VideoParams p = f->video_params();
|
||||
if (f) {
|
||||
// The cached frame won't load with the correct divider by default, so we enforce it here
|
||||
VideoParams p = f->video_params();
|
||||
|
||||
p.set_width(f->width() * video_params.divider());
|
||||
p.set_height(f->height() * video_params.divider());
|
||||
p.set_divider(video_params.divider());
|
||||
p.set_width(f->width() * video_params.divider());
|
||||
p.set_height(f->height() * video_params.divider());
|
||||
p.set_divider(video_params.divider());
|
||||
|
||||
f->set_video_params(p);
|
||||
f->set_video_params(p);
|
||||
|
||||
TexturePtr texture = render_ctx_->CreateTexture(f->video_params(), f->data(), f->linesize_pixels());
|
||||
return QVariant::fromValue(texture);
|
||||
}
|
||||
TexturePtr texture = render_ctx_->CreateTexture(f->video_params(), f->data(), f->linesize_pixels());
|
||||
qDebug() << "Loaded mid-render frame from cache";
|
||||
return QVariant::fromValue(texture);
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void RenderProcessor::SaveCachedTexture(const QByteArray &hash, const QVariant &tex_var)
|
||||
{
|
||||
// FIXME: Temporarily disabled because I don't know how to ensure that the frame saved here is
|
||||
// not the main frame. If it is, it'll be saved twice which will waste a lot of cycles.
|
||||
// At least disabled, the frame will still save, and if nothing else alters the hash, it
|
||||
// will pick up automatically from GetCachedTexture.
|
||||
/*if (!tex_var.isNull()) {
|
||||
QString cache_dir = ticket_->property("cache").toString();
|
||||
|
||||
if (!cache_dir.isEmpty()) {
|
||||
TexturePtr texture = tex_var.value<TexturePtr>();
|
||||
FramePtr frame = Frame::Create();
|
||||
frame->set_video_params(texture->params());
|
||||
frame->allocate();
|
||||
render_ctx_->DownloadFromTexture(texture.get(), frame->data(), frame->linesize_pixels());
|
||||
FrameHashCache::SaveCacheFrame(cache_dir, hash, frame);
|
||||
qDebug() << "Saved mid-render frame to cache";
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
VideoParams RenderProcessor::GetCacheVideoParams()
|
||||
{
|
||||
return ticket_->property("vparam").value<VideoParams>();
|
||||
}
|
||||
|
||||
QVector2D RenderProcessor::GenerateResolution() const
|
||||
{
|
||||
// Set resolution to the destination to the "logical" resolution of the destination
|
||||
|
||||
@@ -53,7 +53,13 @@ protected:
|
||||
|
||||
virtual QVariant ProcessFrameGeneration(const Node *node, const GenerateJob& job) override;
|
||||
|
||||
virtual QVariant GetCachedFrame(const Node *node, const rational &time) override;
|
||||
virtual bool CanCacheFrames() override;
|
||||
|
||||
virtual QVariant GetCachedTexture(const QByteArray &hash) override;
|
||||
|
||||
virtual void SaveCachedTexture(const QByteArray& hash, const QVariant& texture) override;
|
||||
|
||||
virtual VideoParams GetCacheVideoParams() override;
|
||||
|
||||
virtual QVector2D GenerateResolution() const override;
|
||||
|
||||
|
||||
@@ -27,28 +27,31 @@ namespace olive {
|
||||
PreCacheTask::PreCacheTask(Footage *footage, int index, Sequence* sequence) :
|
||||
RenderTask(new ViewerOutput(), sequence->GetVideoParams(), sequence->GetAudioParams())
|
||||
{
|
||||
// Create new project
|
||||
project_ = new Project();
|
||||
|
||||
// Create viewer with same parameters as the sequence
|
||||
viewer()->setParent(project_);
|
||||
viewer()->SetVideoParams(sequence->GetVideoParams());
|
||||
viewer()->SetAudioParams(sequence->GetAudioParams());
|
||||
|
||||
// FIXME: I've been lazy and haven't included support for anything connected to a footage input.
|
||||
// At the moment, footage nodes have no connectable inputs so it's not a problem, but if
|
||||
// they ever do, that needs to be addressed immediately.
|
||||
Q_ASSERT(footage->inputs().isEmpty());
|
||||
// Copy project config nodes
|
||||
Node::CopyInputs(footage->project()->color_manager(), project_->color_manager(), false);
|
||||
Node::CopyInputs(footage->project()->settings(), project_->settings(), false);
|
||||
|
||||
// Copy footage node so it can precache without any modifications from the user screwing it up
|
||||
footage_ = static_cast<Footage*>(footage->copy());
|
||||
index_ = index;
|
||||
footage_->setParent(project_);
|
||||
Node::CopyInputs(footage, footage_, false);
|
||||
Node::ConnectEdge(NodeOutput(footage_, Track::Reference(Track::kVideo, index).ToString()), NodeInput(viewer(), ViewerOutput::kTextureInput));
|
||||
|
||||
Node::ConnectEdge(footage_, NodeInput(viewer(), ViewerOutput::kTextureInput));
|
||||
|
||||
SetTitle(tr("Pre-caching %1:%2").arg(footage_->filename()));
|
||||
SetTitle(tr("Pre-caching %1:%2").arg(footage_->filename(), index));
|
||||
}
|
||||
|
||||
PreCacheTask::~PreCacheTask()
|
||||
{
|
||||
// We created this viewer node ourselves, so now we should delete it
|
||||
delete viewer();
|
||||
// This should delete the footage we copied and the viewer we created
|
||||
delete project_;
|
||||
}
|
||||
|
||||
bool PreCacheTask::Run()
|
||||
|
||||
@@ -43,9 +43,9 @@ protected:
|
||||
virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples, qint64 job_time) override;
|
||||
|
||||
private:
|
||||
Footage* footage_;
|
||||
Project* project_;
|
||||
|
||||
int index_;
|
||||
Footage* footage_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -96,13 +96,10 @@ void ProjectImportTask::Import(Folder *folder, QFileInfoList import, int &counte
|
||||
// Create a folder corresponding to the directory
|
||||
Folder* f = new Folder();
|
||||
|
||||
f->moveToThread(folder->thread());
|
||||
|
||||
f->SetLabel(file_info.fileName());
|
||||
|
||||
// Create undoable command that adds the items to the model
|
||||
parent_command->add_child(new NodeAddCommand(folder->parent(), f));
|
||||
parent_command->add_child(new FolderAddChild(folder, f));
|
||||
AddItemToFolder(folder, f, parent_command);
|
||||
|
||||
// Recursively follow this path
|
||||
Import(f, entry_list, counter, parent_command);
|
||||
@@ -119,11 +116,7 @@ void ProjectImportTask::Import(Folder *folder, QFileInfoList import, int &counte
|
||||
ValidateImageSequence(footage, import, i);
|
||||
|
||||
// Create undoable command that adds the items to the model
|
||||
NodeAddCommand* nac = new NodeAddCommand(folder->parent(), footage);
|
||||
nac->PushToThread(folder->thread());
|
||||
parent_command->add_child(nac);
|
||||
|
||||
parent_command->add_child(new FolderAddChild(folder, footage));
|
||||
AddItemToFolder(folder, footage, parent_command);
|
||||
} else {
|
||||
// Add to list so we can tell the user about it later
|
||||
invalid_files_.append(file_info.absoluteFilePath());
|
||||
@@ -223,6 +216,18 @@ void ProjectImportTask::ValidateImageSequence(Footage *footage, QFileInfoList& i
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectImportTask::AddItemToFolder(Folder *folder, Node *item, MultiUndoCommand *command)
|
||||
{
|
||||
// Create undoable command that adds the items to the model
|
||||
Project* project = model_->project();
|
||||
|
||||
NodeAddCommand* nac = new NodeAddCommand(project, item);
|
||||
nac->PushToThread(project->thread());
|
||||
command->add_child(nac);
|
||||
|
||||
command->add_child(new FolderAddChild(folder, item));
|
||||
}
|
||||
|
||||
bool ProjectImportTask::ItemIsStillImageFootageOnly(Footage* footage)
|
||||
{
|
||||
if (footage->GetTotalStreamCount() != 1) {
|
||||
|
||||
@@ -61,6 +61,8 @@ private:
|
||||
|
||||
void ValidateImageSequence(Footage *footage, QFileInfoList &info_list, int index);
|
||||
|
||||
void AddItemToFolder(Folder* folder, Node* item, MultiUndoCommand* command);
|
||||
|
||||
static bool ItemIsStillImageFootageOnly(Footage *footage);
|
||||
|
||||
static bool CompareStillImageSize(Footage *footage, const QSize& sz);
|
||||
|
||||
@@ -27,17 +27,17 @@
|
||||
#include <opentimelineio/gap.h>
|
||||
#include <opentimelineio/serializableCollection.h>
|
||||
#include <opentimelineio/timeline.h>
|
||||
#include <opentimelineio/transition.h>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "node/block/clip/clip.h"
|
||||
#include "node/block/gap/gap.h"
|
||||
#include "node/block/transition/crossdissolve/crossdissolvetransition.h"
|
||||
#include "node/project/folder/folder.h"
|
||||
#include "node/project/footage/footage.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "widget/timelinewidget/timelineundo.h"
|
||||
|
||||
#define OTIO opentimelineio::v1_0
|
||||
|
||||
namespace olive {
|
||||
|
||||
LoadOTIOTask::LoadOTIOTask(const QString& s) :
|
||||
@@ -82,13 +82,16 @@ bool LoadOTIOTask::Run()
|
||||
QMap<QString, Footage*> imported_footage;
|
||||
|
||||
foreach (auto timeline, timelines) {
|
||||
// Create sequence
|
||||
Sequence* sequence = new Sequence();
|
||||
sequence->SetLabel(QString::fromStdString(timeline->name()));
|
||||
sequence->setParent(project_->root());
|
||||
sequence->setParent(project_);
|
||||
FolderAddChild(project_->root(), sequence).redo();
|
||||
|
||||
// FIXME: As far as I know, OTIO doesn't store video/audio parameters?
|
||||
sequence->set_default_parameters();
|
||||
|
||||
// Iterate through tracks
|
||||
for (auto c : timeline->tracks()->children()) {
|
||||
auto otio_track = static_cast<OTIO::Track*>(c.value);
|
||||
|
||||
@@ -105,6 +108,7 @@ bool LoadOTIOTask::Run()
|
||||
type = Track::kAudio;
|
||||
}
|
||||
|
||||
// Create track
|
||||
TimelineAddTrackCommand t(sequence->track_list(type));
|
||||
t.redo();
|
||||
track = t.track();
|
||||
@@ -120,6 +124,9 @@ bool LoadOTIOTask::Run()
|
||||
return false;
|
||||
}
|
||||
|
||||
Block* previous_block = nullptr;
|
||||
bool prev_block_transition = false;
|
||||
|
||||
for (auto otio_block_retainer : clip_map) {
|
||||
|
||||
auto otio_block = otio_block_retainer.value;
|
||||
@@ -134,27 +141,67 @@ bool LoadOTIOTask::Run()
|
||||
|
||||
block = new GapBlock();
|
||||
|
||||
} else if (otio_block->schema_name() == "Transition") {
|
||||
|
||||
// Todo: Look into OTIO supported transitions and add them to Olive
|
||||
block = new CrossDissolveTransition();
|
||||
|
||||
} else {
|
||||
|
||||
// We don't know what this is yet, just create a gap for now so that *something* is there
|
||||
qWarning() << "Found unknown block type:" << otio_block->schema_name().c_str();
|
||||
block = new GapBlock();
|
||||
|
||||
}
|
||||
|
||||
block->setParent(project_);
|
||||
block->SetLabel(QString::fromStdString(otio_block->name()));
|
||||
|
||||
rational start_time = rational::fromDouble(static_cast<OTIO::Item*>(otio_block)->source_range()->start_time().to_seconds());
|
||||
rational duration = rational::fromDouble(static_cast<OTIO::Item*>(otio_block)->source_range()->duration().to_seconds());
|
||||
|
||||
block->set_media_in(start_time);
|
||||
block->set_length_and_media_out(duration);
|
||||
block->setParent(sequence);
|
||||
track->AppendBlock(block);
|
||||
|
||||
rational start_time;
|
||||
rational duration;
|
||||
|
||||
if (otio_block->schema_name() == "Clip" || otio_block->schema_name() == "Gap") {
|
||||
start_time =
|
||||
rational::fromDouble(static_cast<OTIO::Item*>(otio_block)->source_range()->start_time().to_seconds());
|
||||
duration =
|
||||
rational::fromDouble(static_cast<OTIO::Item*>(otio_block)->source_range()->duration().to_seconds());
|
||||
|
||||
block->set_media_in(start_time);
|
||||
block->set_length_and_media_out(duration);
|
||||
}
|
||||
|
||||
// If the previous block was a transition, connect the current block to it
|
||||
if (prev_block_transition) {
|
||||
TransitionBlock* previous_transition_block = static_cast<TransitionBlock*>(previous_block);
|
||||
Node::ConnectEdge(block, NodeInput(previous_transition_block, TransitionBlock::kInBlockInput));
|
||||
prev_block_transition = false;
|
||||
}
|
||||
|
||||
if (otio_block->schema_name() == "Transition") {
|
||||
TransitionBlock* transition_block = static_cast<TransitionBlock*>(block);
|
||||
OTIO::Transition* otio_block_transition = static_cast<OTIO::Transition*>(otio_block);
|
||||
|
||||
duration = rational::fromDouble((otio_block_transition->in_offset() + otio_block_transition->out_offset()).to_seconds());
|
||||
transition_block->set_length_and_media_out(duration);
|
||||
|
||||
if (previous_block) {
|
||||
Node::ConnectEdge(previous_block, NodeInput(transition_block, TransitionBlock::kOutBlockInput));
|
||||
|
||||
// Set how far the transition eats into the previous clip
|
||||
transition_block->set_media_in(rational::fromDouble(-otio_block_transition->out_offset().to_seconds()));
|
||||
}
|
||||
prev_block_transition = true;
|
||||
}
|
||||
|
||||
// Update this after it's used but before any continue statements
|
||||
previous_block = block;
|
||||
|
||||
if (otio_block->schema_name() == "Clip") {
|
||||
auto otio_clip = static_cast<OTIO::Clip*>(otio_block);
|
||||
|
||||
if (!otio_clip->media_reference()) {
|
||||
continue;
|
||||
}
|
||||
if (otio_clip->media_reference()->schema_name() == "ExternalReference") {
|
||||
// Link footage
|
||||
QString footage_url = QString::fromStdString(static_cast<OTIO::ExternalReference*>(otio_clip->media_reference())->target_url());
|
||||
@@ -166,7 +213,7 @@ bool LoadOTIOTask::Run()
|
||||
} else {
|
||||
probed_item = new Footage(footage_url);
|
||||
imported_footage.insert(footage_url, probed_item);
|
||||
probed_item->setParent(project_->root());
|
||||
probed_item->setParent(project_);
|
||||
}
|
||||
|
||||
Track::Reference reference;
|
||||
@@ -194,4 +241,4 @@ bool LoadOTIOTask::Run()
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif // USE_OTIO
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#ifdef USE_OTIO
|
||||
|
||||
#include "common/otioutils.h"
|
||||
#include "node/project/project.h"
|
||||
#include "task/project/load/loadbasetask.h"
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <opentimelineio/externalReference.h>
|
||||
#include <opentimelineio/gap.h>
|
||||
#include <opentimelineio/serializableCollection.h>
|
||||
#include <opentimelineio/serializableObject.h>
|
||||
#include <opentimelineio/transition.h>
|
||||
|
||||
#include "node/block/transition/transition.h"
|
||||
@@ -48,7 +49,7 @@ bool SaveOTIOTask::Run()
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<opentimelineio::v1_0::SerializableObject*> serialized;
|
||||
std::vector<OTIO::SerializableObject*> serialized;
|
||||
|
||||
foreach (Sequence* seq, sequences) {
|
||||
auto otio_timeline = SerializeTimeline(seq);
|
||||
@@ -69,7 +70,7 @@ bool SaveOTIOTask::Run()
|
||||
}
|
||||
}
|
||||
|
||||
opentimelineio::v1_0::ErrorStatus es;
|
||||
OTIO::ErrorStatus es;
|
||||
|
||||
if (serialized.size() == 1) {
|
||||
// Serialize timeline on its own
|
||||
@@ -78,7 +79,7 @@ bool SaveOTIOTask::Run()
|
||||
t->possibly_delete();
|
||||
} else {
|
||||
// Serialize all into a SerializableCollection
|
||||
auto collection = new opentimelineio::v1_0::SerializableCollection("Sequences", serialized);
|
||||
auto collection = new OTIO::SerializableCollection("Sequences", serialized);
|
||||
collection->to_json_file(project_->filename().toStdString(), &es);
|
||||
collection->possibly_delete();
|
||||
|
||||
@@ -88,12 +89,16 @@ bool SaveOTIOTask::Run()
|
||||
}
|
||||
}
|
||||
|
||||
return (es == opentimelineio::v1_0::ErrorStatus::OK);
|
||||
return (es == OTIO::ErrorStatus::OK);
|
||||
}
|
||||
|
||||
opentimelineio::v1_0::Timeline *SaveOTIOTask::SerializeTimeline(Sequence *sequence)
|
||||
OTIO::Timeline *SaveOTIOTask::SerializeTimeline(Sequence *sequence)
|
||||
{
|
||||
auto otio_timeline = new opentimelineio::v1_0::Timeline(sequence->GetLabel().toStdString());
|
||||
auto otio_timeline = new OTIO::Timeline(sequence->GetLabel().toStdString());
|
||||
// Retainers clean themselves up when the final user is removed
|
||||
OTIO::Timeline::Retainer<OTIO::Timeline>* timeline_retainer = new OTIO::Timeline::Retainer<OTIO::Timeline>(otio_timeline);
|
||||
// Suppress unused variable warning
|
||||
Q_UNUSED(timeline_retainer);
|
||||
|
||||
if (!SerializeTrackList(sequence->track_list(Track::kVideo), otio_timeline)
|
||||
|| !SerializeTrackList(sequence->track_list(Track::kAudio), otio_timeline)) {
|
||||
@@ -104,11 +109,11 @@ opentimelineio::v1_0::Timeline *SaveOTIOTask::SerializeTimeline(Sequence *sequen
|
||||
return otio_timeline;
|
||||
}
|
||||
|
||||
opentimelineio::v1_0::Track *SaveOTIOTask::SerializeTrack(Track *track)
|
||||
OTIO::Track *SaveOTIOTask::SerializeTrack(Track *track)
|
||||
{
|
||||
auto otio_track = new opentimelineio::v1_0::Track();
|
||||
auto otio_track = new OTIO::Track();
|
||||
|
||||
opentimelineio::v1_0::ErrorStatus es;
|
||||
OTIO::ErrorStatus es;
|
||||
|
||||
switch (track->type()) {
|
||||
case Track::kVideo:
|
||||
@@ -123,19 +128,19 @@ opentimelineio::v1_0::Track *SaveOTIOTask::SerializeTrack(Track *track)
|
||||
}
|
||||
|
||||
foreach (Block* block, track->Blocks()) {
|
||||
opentimelineio::v1_0::Composable* otio_block = nullptr;
|
||||
OTIO::Composable* otio_block = nullptr;
|
||||
|
||||
switch (block->type()) {
|
||||
case Block::kClip:
|
||||
{
|
||||
auto otio_clip = new opentimelineio::v1_0::Clip(block->GetLabel().toStdString());
|
||||
auto otio_clip = new OTIO::Clip(block->GetLabel().toStdString());
|
||||
|
||||
otio_clip->set_source_range(opentimelineio::v1_0::TimeRange(block->in().toRationalTime(),
|
||||
block->length().toRationalTime()));
|
||||
otio_clip->set_source_range(OTIO::TimeRange(block->in().toRationalTime(),
|
||||
block->length().toRationalTime()));
|
||||
|
||||
QVector<Footage*> media_nodes = block->FindInputNodes<Footage>();
|
||||
if (!media_nodes.isEmpty()) {
|
||||
auto media_ref = new opentimelineio::v1_0::ExternalReference(media_nodes.first()->filename().toStdString());
|
||||
auto media_ref = new OTIO::ExternalReference(media_nodes.first()->filename().toStdString());
|
||||
otio_clip->set_media_reference(media_ref);
|
||||
}
|
||||
|
||||
@@ -144,22 +149,22 @@ opentimelineio::v1_0::Track *SaveOTIOTask::SerializeTrack(Track *track)
|
||||
}
|
||||
case Block::kGap:
|
||||
{
|
||||
otio_block = new opentimelineio::v1_0::Gap(
|
||||
opentimelineio::v1_0::TimeRange(block->in().toRationalTime(), block->length().toRationalTime()),
|
||||
block->GetLabel().toStdString()
|
||||
);
|
||||
otio_block = new OTIO::Gap(OTIO::TimeRange(block->in().toRationalTime(),
|
||||
block->length().toRationalTime()),
|
||||
block->GetLabel().toStdString()
|
||||
);
|
||||
break;
|
||||
}
|
||||
case Block::kTransition:
|
||||
{
|
||||
auto otio_transition = new opentimelineio::v1_0::Transition(block->GetLabel().toStdString());
|
||||
auto otio_transition = new OTIO::Transition(block->GetLabel().toStdString());
|
||||
|
||||
TransitionBlock* our_transition = static_cast<TransitionBlock*>(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();
|
||||
otio_block = new OTIO::Transition();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -171,7 +176,7 @@ opentimelineio::v1_0::Track *SaveOTIOTask::SerializeTrack(Track *track)
|
||||
|
||||
otio_track->append_child(otio_block, &es);
|
||||
|
||||
if (es != opentimelineio::v1_0::ErrorStatus::OK) {
|
||||
if (es != OTIO::ErrorStatus::OK) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
@@ -184,9 +189,9 @@ fail:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool SaveOTIOTask::SerializeTrackList(TrackList *list, opentimelineio::v1_0::Timeline* otio_timeline)
|
||||
bool SaveOTIOTask::SerializeTrackList(TrackList *list, OTIO::Timeline* otio_timeline)
|
||||
{
|
||||
opentimelineio::v1_0::ErrorStatus es;
|
||||
OTIO::ErrorStatus es;
|
||||
|
||||
foreach (Track* track, list->GetTracks()) {
|
||||
auto otio_track = SerializeTrack(track);
|
||||
@@ -197,7 +202,7 @@ bool SaveOTIOTask::SerializeTrackList(TrackList *list, opentimelineio::v1_0::Tim
|
||||
|
||||
otio_timeline->tracks()->append_child(otio_track, &es);
|
||||
|
||||
if (es != opentimelineio::v1_0::ErrorStatus::OK) {
|
||||
if (es != OTIO::ErrorStatus::OK) {
|
||||
otio_track->possibly_delete();
|
||||
return false;
|
||||
}
|
||||
@@ -208,4 +213,4 @@ bool SaveOTIOTask::SerializeTrackList(TrackList *list, opentimelineio::v1_0::Tim
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif // USE_OTIO
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <opentimelineio/timeline.h>
|
||||
#include <opentimelineio/track.h>
|
||||
|
||||
#include "common/otioutils.h"
|
||||
#include "node/project/project.h"
|
||||
#include "task/task.h"
|
||||
|
||||
@@ -41,11 +42,11 @@ protected:
|
||||
virtual bool Run() override;
|
||||
|
||||
private:
|
||||
opentimelineio::v1_0::Timeline* SerializeTimeline(Sequence* sequence);
|
||||
OTIO::Timeline* SerializeTimeline(Sequence* sequence);
|
||||
|
||||
opentimelineio::v1_0::Track* SerializeTrack(Track* track);
|
||||
OTIO::Track* SerializeTrack(Track* track);
|
||||
|
||||
bool SerializeTrackList(TrackList* list, opentimelineio::v1_0::Timeline *otio_timeline);
|
||||
bool SerializeTrackList(TrackList* list, OTIO::Timeline *otio_timeline);
|
||||
|
||||
Project* project_;
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ bool RenderTask::Render(ColorManager* manager,
|
||||
return true;
|
||||
}
|
||||
|
||||
hashes[i] = RenderManager::instance()->Hash(viewer(), video_params_, times.at(i));
|
||||
hashes[i] = RenderManager::instance()->Hash(viewer()->GetConnectedTextureOutput(), video_params_, times.at(i));
|
||||
}
|
||||
|
||||
// Filter out duplicates
|
||||
|
||||
@@ -37,8 +37,10 @@ void HandMovableView::ApplicationToolChanged(Tool::Item tool)
|
||||
{
|
||||
if (tool == Tool::kHand) {
|
||||
setDragMode(ScrollHandDrag);
|
||||
setInteractive(false);
|
||||
} else {
|
||||
setDragMode(default_drag_mode_);
|
||||
setInteractive(true);
|
||||
}
|
||||
|
||||
ToolChangedEvent(tool);
|
||||
@@ -51,6 +53,7 @@ bool HandMovableView::HandPress(QMouseEvent *event)
|
||||
dragging_hand_ = true;
|
||||
|
||||
setDragMode(ScrollHandDrag);
|
||||
setInteractive(false);
|
||||
|
||||
// Transform mouse event to act like the left button is pressed
|
||||
QMouseEvent transformed(event->type(),
|
||||
@@ -94,6 +97,7 @@ bool HandMovableView::HandRelease(QMouseEvent *event)
|
||||
|
||||
QGraphicsView::mouseReleaseEvent(&transformed);
|
||||
|
||||
setInteractive(true);
|
||||
setDragMode(pre_hand_drag_mode_);
|
||||
|
||||
dragging_hand_ = false;
|
||||
|
||||
@@ -262,7 +262,7 @@ void NodeParamView::UpdateItemTime(const int64_t ×tamp)
|
||||
|
||||
void NodeParamView::QueueKeyframePositionUpdate()
|
||||
{
|
||||
QMetaObject::invokeMethod(this, "UpdateElementY", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(this, &NodeParamView::UpdateElementY, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void NodeParamView::SignalNodeOrder()
|
||||
@@ -310,8 +310,9 @@ void NodeParamView::AddNode(Node *n)
|
||||
connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::QueueKeyframePositionUpdate);
|
||||
connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::SignalNodeOrder);
|
||||
connect(item, &NodeParamViewItem::PinToggled, this, &NodeParamView::PinNode);
|
||||
connect(item, &NodeParamViewItem::ExpandedChanged, this, &NodeParamView::UpdateElementY);
|
||||
connect(item, &NodeParamViewItem::ArrayExpandedChanged, this, &NodeParamView::UpdateElementY);
|
||||
connect(item, &NodeParamViewItem::ArrayExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate);
|
||||
connect(item, &NodeParamViewItem::ExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate);
|
||||
connect(item, &NodeParamViewItem::Moved, this, &NodeParamView::QueueKeyframePositionUpdate);
|
||||
|
||||
// Set time target
|
||||
item->SetTimeTarget(GetTimeTarget());
|
||||
|
||||
@@ -36,8 +36,13 @@ const int NodeParamViewItemBody::kKeyControlColumn = 10;
|
||||
const int NodeParamViewItemBody::kArrayInsertColumn = kKeyControlColumn-1;
|
||||
const int NodeParamViewItemBody::kArrayRemoveColumn = kArrayInsertColumn-1;
|
||||
|
||||
// 0 is for the array collapse button, 1 is for the main label, widgets start at 2
|
||||
const int NodeParamViewItemBody::kWidgetStartColumn = 2;
|
||||
|
||||
#define super QDockWidget
|
||||
|
||||
NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) :
|
||||
QDockWidget(parent),
|
||||
super(parent),
|
||||
node_(node),
|
||||
highlighted_(false)
|
||||
{
|
||||
@@ -97,12 +102,12 @@ void NodeParamViewItem::changeEvent(QEvent *e)
|
||||
Retranslate();
|
||||
}
|
||||
|
||||
QWidget::changeEvent(e);
|
||||
super::changeEvent(e);
|
||||
}
|
||||
|
||||
void NodeParamViewItem::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QDockWidget::paintEvent(event);
|
||||
super::paintEvent(event);
|
||||
|
||||
// Draw border if focused
|
||||
if (highlighted_) {
|
||||
@@ -113,6 +118,13 @@ void NodeParamViewItem::paintEvent(QPaintEvent *event)
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewItem::moveEvent(QMoveEvent *event)
|
||||
{
|
||||
super::moveEvent(event);
|
||||
|
||||
emit Moved();
|
||||
}
|
||||
|
||||
void NodeParamViewItem::Retranslate()
|
||||
{
|
||||
node_->Retranslate();
|
||||
@@ -257,6 +269,10 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
|
||||
|
||||
InputUI ui_objects;
|
||||
|
||||
// Store layout and row
|
||||
ui_objects.layout = layout;
|
||||
ui_objects.row = row;
|
||||
|
||||
// Add descriptor label
|
||||
ui_objects.main_label = new QLabel();
|
||||
|
||||
@@ -299,23 +315,24 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
|
||||
|
||||
// Create a widget/input bridge for this input
|
||||
ui_objects.widget_bridge = new NodeParamViewWidgetBridge(NodeInput(node, input, element), this);
|
||||
connect(ui_objects.widget_bridge, &NodeParamViewWidgetBridge::WidgetsRecreated, this, &NodeParamViewItemBody::ReplaceWidgets);
|
||||
connect(ui_objects.widget_bridge, &NodeParamViewWidgetBridge::ArrayWidgetDoubleClicked, this, &NodeParamViewItemBody::ToggleArrayExpanded);
|
||||
|
||||
// 0 is for the array collapse button, 1 is for the main label, widgets start at 2
|
||||
const int widget_start = 2;
|
||||
// Place widgets into layout
|
||||
PlaceWidgetsFromBridge(layout, ui_objects.widget_bridge, row);
|
||||
|
||||
// Add widgets for this parameter to the layout
|
||||
for (int i=0; i<ui_objects.widget_bridge->widgets().size(); i++) {
|
||||
QWidget* w = ui_objects.widget_bridge->widgets().at(i);
|
||||
|
||||
layout->addWidget(w, row, i+widget_start);
|
||||
layout->addWidget(w, row, i+kWidgetStartColumn);
|
||||
}
|
||||
|
||||
if (node->IsInputConnectable(input)) {
|
||||
// Create clickable label used when an input is connected
|
||||
ui_objects.connected_label = new NodeParamViewConnectedLabel(input_ref);
|
||||
connect(ui_objects.connected_label, &NodeParamViewConnectedLabel::RequestSelectNode, this, &NodeParamViewItemBody::RequestSelectNode);
|
||||
layout->addWidget(ui_objects.connected_label, row, widget_start);
|
||||
layout->addWidget(ui_objects.connected_label, row, kWidgetStartColumn);
|
||||
}
|
||||
|
||||
// Add keyframe control to this layout if parameter is keyframable
|
||||
@@ -414,6 +431,16 @@ void NodeParamViewItemBody::UpdateUIForEdgeConnection(const NodeInput& input)
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewItemBody::PlaceWidgetsFromBridge(QGridLayout* layout, NodeParamViewWidgetBridge *bridge, int row)
|
||||
{
|
||||
// Add widgets for this parameter to the layout
|
||||
for (int i=0; i<bridge->widgets().size(); i++) {
|
||||
QWidget* w = bridge->widgets().at(i);
|
||||
|
||||
layout->addWidget(w, row, i+kWidgetStartColumn);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewItemBody::ArrayCollapseBtnPressed(bool checked)
|
||||
{
|
||||
const NodeInputPair& input = array_collapse_buttons_.key(static_cast<CollapseButton*>(sender()));
|
||||
@@ -510,12 +537,19 @@ void NodeParamViewItemBody::ToggleArrayExpanded()
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewItemBody::SetTimebase(const rational& timebase) {
|
||||
void NodeParamViewItemBody::SetTimebase(const rational& timebase)
|
||||
{
|
||||
foreach (const InputUI& ui_obj, input_ui_map_) {
|
||||
ui_obj.widget_bridge->SetTimebase(timebase);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewItemBody::ReplaceWidgets(const NodeInput &input)
|
||||
{
|
||||
InputUI ui = input_ui_map_.value(input);
|
||||
PlaceWidgetsFromBridge(ui.layout, ui.widget_bridge, ui.row);
|
||||
}
|
||||
|
||||
NodeParamViewItemBody::InputUI::InputUI() :
|
||||
main_label(nullptr),
|
||||
widget_bridge(nullptr),
|
||||
|
||||
@@ -99,6 +99,8 @@ private:
|
||||
|
||||
void UpdateUIForEdgeConnection(const NodeInput &input);
|
||||
|
||||
void PlaceWidgetsFromBridge(QGridLayout *layout, NodeParamViewWidgetBridge* bridge, int row);
|
||||
|
||||
struct InputUI {
|
||||
InputUI();
|
||||
|
||||
@@ -106,6 +108,8 @@ private:
|
||||
NodeParamViewWidgetBridge* widget_bridge;
|
||||
NodeParamViewConnectedLabel* connected_label;
|
||||
NodeParamViewKeyframeControl* key_control;
|
||||
QGridLayout* layout;
|
||||
int row;
|
||||
|
||||
NodeParamViewArrayButton* array_insert_btn;
|
||||
NodeParamViewArrayButton* array_remove_btn;
|
||||
@@ -134,6 +138,8 @@ private:
|
||||
static const int kArrayInsertColumn;
|
||||
static const int kArrayRemoveColumn;
|
||||
|
||||
static const int kWidgetStartColumn;
|
||||
|
||||
private slots:
|
||||
void EdgeChanged(const NodeOutput &output, const NodeInput &input);
|
||||
|
||||
@@ -149,6 +155,8 @@ private slots:
|
||||
|
||||
void ToggleArrayExpanded();
|
||||
|
||||
void ReplaceWidgets(const NodeInput& input);
|
||||
|
||||
};
|
||||
|
||||
class NodeParamViewItem : public QDockWidget
|
||||
@@ -192,11 +200,15 @@ signals:
|
||||
|
||||
void ArrayExpandedChanged(bool e);
|
||||
|
||||
void Moved();
|
||||
|
||||
protected:
|
||||
virtual void changeEvent(QEvent *e) override;
|
||||
|
||||
virtual void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
virtual void moveEvent(QMoveEvent *event) override;
|
||||
|
||||
private:
|
||||
NodeParamViewItemTitleBar* title_bar_;
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ NodeParamViewWidgetBridge::NodeParamViewWidgetBridge(const NodeInput &input, QOb
|
||||
|
||||
connect(input_.node(), &Node::ValueChanged, this, &NodeParamViewWidgetBridge::InputValueChanged);
|
||||
connect(input_.node(), &Node::InputPropertyChanged, this, &NodeParamViewWidgetBridge::PropertyChanged);
|
||||
connect(input_.node(), &Node::InputDataTypeChanged, this, &NodeParamViewWidgetBridge::InputDataTypeChanged);
|
||||
}
|
||||
|
||||
void NodeParamViewWidgetBridge::SetTime(const rational &time)
|
||||
@@ -212,14 +213,25 @@ void NodeParamViewWidgetBridge::SetInputValueInternal(const QVariant &value, int
|
||||
command->add_child(new NodeParamSetKeyframeValueCommand(existing_key, value));
|
||||
} else {
|
||||
// No existing key, create a new one
|
||||
NodeKeyframe* new_key = new NodeKeyframe(node_time,
|
||||
value,
|
||||
input_.node()->GetBestKeyframeTypeForTimeOnTrack(NodeKeyframeTrackReference(input_, track), node_time),
|
||||
track,
|
||||
input_.element(),
|
||||
input_.input());
|
||||
int nb_tracks = NodeValue::get_number_of_keyframe_tracks(input_.node()->GetInputDataType(input_.input()));
|
||||
for (int i=0; i<nb_tracks; i++) {
|
||||
QVariant track_value;
|
||||
|
||||
command->add_child(new NodeParamInsertKeyframeCommand(input_.node(), new_key));
|
||||
if (i == track) {
|
||||
track_value = value;
|
||||
} else {
|
||||
track_value = input_.node()->GetValueAtTime(input_.input(), node_time, input_.element());
|
||||
}
|
||||
|
||||
NodeKeyframe* new_key = new NodeKeyframe(node_time,
|
||||
track_value,
|
||||
input_.node()->GetBestKeyframeTypeForTimeOnTrack(NodeKeyframeTrackReference(input_, i), node_time),
|
||||
i,
|
||||
input_.element(),
|
||||
input_.input());
|
||||
|
||||
command->add_child(new NodeParamInsertKeyframeCommand(input_.node(), new_key));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(input_, track), value));
|
||||
@@ -767,6 +779,22 @@ void NodeParamViewWidgetBridge::PropertyChanged(const QString& input, const QStr
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewWidgetBridge::InputDataTypeChanged(const QString &input, NodeValue::Type type)
|
||||
{
|
||||
Q_UNUSED(type)
|
||||
if (input == this->input_.input()) {
|
||||
// Delete all widgets
|
||||
qDeleteAll(widgets_);
|
||||
widgets_.clear();
|
||||
|
||||
// Create new widgets
|
||||
CreateWidgets();
|
||||
|
||||
// Signal that widgets are new
|
||||
emit WidgetsRecreated(input_);
|
||||
}
|
||||
}
|
||||
|
||||
bool NodeParamViewScrollBlocker::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
Q_UNUSED(watched)
|
||||
|
||||
@@ -54,6 +54,8 @@ public:
|
||||
signals:
|
||||
void ArrayWidgetDoubleClicked();
|
||||
|
||||
void WidgetsRecreated(const NodeInput& input);
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
|
||||
@@ -87,6 +89,8 @@ private slots:
|
||||
|
||||
void PropertyChanged(const QString &input, const QString& key, const QVariant& value);
|
||||
|
||||
void InputDataTypeChanged(const QString& input, NodeValue::Type type);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -298,11 +298,11 @@ void NodeView::Paste()
|
||||
|
||||
QVector<Node*> pasted_nodes = PasteNodesFromClipboard(graph_, command);
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
|
||||
if (!pasted_nodes.isEmpty()) {
|
||||
AttachNodesToCursor(pasted_nodes);
|
||||
command->add_child(new NodeViewAttachNodesToCursor(this, pasted_nodes));
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
}
|
||||
|
||||
void NodeView::Duplicate()
|
||||
@@ -321,9 +321,11 @@ void NodeView::Duplicate()
|
||||
|
||||
QVector<Node*> duplicated_nodes = Node::CopyDependencyGraph(selected, command);
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
if (!duplicated_nodes.isEmpty()) {
|
||||
command->add_child(new NodeViewAttachNodesToCursor(this, duplicated_nodes));
|
||||
}
|
||||
|
||||
AttachNodesToCursor(duplicated_nodes);
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
}
|
||||
|
||||
void NodeView::SetColorLabel(int index)
|
||||
@@ -839,4 +841,26 @@ void NodeView::ZoomFromKeyboard(double multiplier)
|
||||
ZoomIntoCursorPosition(multiplier, cursor_pos);
|
||||
}
|
||||
|
||||
NodeView::NodeViewAttachNodesToCursor::NodeViewAttachNodesToCursor(NodeView *view, const QVector<Node *> &nodes) :
|
||||
view_(view),
|
||||
nodes_(nodes)
|
||||
{
|
||||
}
|
||||
|
||||
void NodeView::NodeViewAttachNodesToCursor::redo()
|
||||
{
|
||||
view_->AttachNodesToCursor(nodes_);
|
||||
}
|
||||
|
||||
void NodeView::NodeViewAttachNodesToCursor::undo()
|
||||
{
|
||||
view_->DetachItemsFromCursor();
|
||||
}
|
||||
|
||||
Project *NodeView::NodeViewAttachNodesToCursor::GetRelevantProject() const
|
||||
{
|
||||
// Will either return a project or a nullptr which is also acceptable
|
||||
return dynamic_cast<Project*>(view_->graph_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -110,6 +110,24 @@ private:
|
||||
|
||||
void ZoomFromKeyboard(double multiplier);
|
||||
|
||||
class NodeViewAttachNodesToCursor : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodeViewAttachNodesToCursor(NodeView* view, const QVector<Node*>& nodes);
|
||||
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
|
||||
virtual Project * GetRelevantProject() const override;
|
||||
|
||||
private:
|
||||
NodeView* view_;
|
||||
|
||||
QVector<Node*> nodes_;
|
||||
|
||||
};
|
||||
|
||||
NodeGraph* graph_;
|
||||
|
||||
struct AttachedItem {
|
||||
|
||||
@@ -490,15 +490,19 @@ void ProjectExplorer::ContextMenuStartProxy(QAction *a)
|
||||
Sequence* sequence = Node::ValueToPtr<Sequence>(a->data());
|
||||
|
||||
// To get here, the `context_menu_items_` must be all kFootage
|
||||
foreach (Node* i, context_menu_items_) {
|
||||
Footage* f = static_cast<Footage*>(i);
|
||||
foreach (Node* item, context_menu_items_) {
|
||||
Footage* f = static_cast<Footage*>(item);
|
||||
|
||||
QVector<VideoParams> enabled_streams = f->GetEnabledVideoStreams();
|
||||
int sz = f->InputArraySize(Footage::kVideoParamsInput);
|
||||
|
||||
foreach (const VideoParams& stream, enabled_streams) {
|
||||
// Start a background task for proxying
|
||||
PreCacheTask* proxy_task = new PreCacheTask(f, stream.stream_index(), sequence);
|
||||
TaskManager::instance()->AddTask(proxy_task);
|
||||
for (int j=0; j<sz; j++) {
|
||||
VideoParams vp = f->GetVideoParams(j);
|
||||
|
||||
if (vp.enabled()) {
|
||||
// Start a background task for proxying
|
||||
PreCacheTask* proxy_task = new PreCacheTask(f, j, sequence);
|
||||
TaskManager::instance()->AddTask(proxy_task);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1009,7 +1009,7 @@ void TimelineWidget::SetViewTimestamp(const int64_t &ts)
|
||||
for (int i=0;i<views_.size();i++) {
|
||||
TimelineAndTrackView* view = views_.at(i);
|
||||
|
||||
if (use_audio_time_units_ && i == Track::kAudio) {
|
||||
if (GetConnectedNode() && use_audio_time_units_ && i == Track::kAudio) {
|
||||
view->view()->SetTime(Timecode::rescale_timestamp(ts,
|
||||
timebase(),
|
||||
GetConnectedNode()->GetAudioParams().sample_rate_as_time_base()));
|
||||
@@ -1021,7 +1021,7 @@ void TimelineWidget::SetViewTimestamp(const int64_t &ts)
|
||||
|
||||
void TimelineWidget::ViewTimestampChanged(int64_t ts)
|
||||
{
|
||||
if (use_audio_time_units_ && sender() == views_.at(Track::kAudio)) {
|
||||
if (GetConnectedNode() && use_audio_time_units_ && sender() == views_.at(Track::kAudio)) {
|
||||
ts = Timecode::rescale_timestamp(ts,
|
||||
GetConnectedNode()->GetAudioParams().sample_rate_as_time_base(),
|
||||
timebase());
|
||||
@@ -1503,6 +1503,10 @@ QVector<SnapData> AttemptSnap(const QVector<double>& screen_pt,
|
||||
|
||||
bool TimelineWidget::SnapPoint(QVector<rational> start_times, rational* movement, int snap_points)
|
||||
{
|
||||
if (!GetConnectedNode()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QVector<double> screen_pt;
|
||||
|
||||
foreach (const rational& s, start_times) {
|
||||
|
||||
@@ -160,6 +160,10 @@ int SeekableWidget::TimeToScreen(const rational &time) const
|
||||
|
||||
void SeekableWidget::SeekToScreenPoint(int screen)
|
||||
{
|
||||
if (timebase().isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t timestamp = qMax(static_cast<int64_t>(0), ScreenToUnitRounded(screen));
|
||||
|
||||
if (Core::instance()->snapping() && snap_service_) {
|
||||
|
||||
@@ -129,7 +129,7 @@ ViewerWidget::~ViewerWidget()
|
||||
{
|
||||
instances_.removeOne(this);
|
||||
|
||||
QList<ViewerWindow*> windows = windows_;
|
||||
auto windows = windows_;
|
||||
|
||||
foreach (ViewerWindow* window, windows) {
|
||||
delete window;
|
||||
@@ -311,6 +311,11 @@ void ViewerWidget::SetFullScreen(QScreen *screen)
|
||||
}
|
||||
}
|
||||
|
||||
if (windows_.contains(screen)) {
|
||||
delete windows_.take(screen);
|
||||
return;
|
||||
}
|
||||
|
||||
ViewerWindow* vw = new ViewerWindow(this);
|
||||
|
||||
vw->setGeometry(screen->geometry());
|
||||
@@ -326,7 +331,7 @@ void ViewerWidget::SetFullScreen(QScreen *screen)
|
||||
|
||||
vw->display_widget()->SetImage(display_widget_->last_loaded_buffer());
|
||||
|
||||
windows_.append(vw);
|
||||
windows_.insert(screen, vw);
|
||||
}
|
||||
|
||||
void ViewerWidget::ForceUpdate()
|
||||
@@ -745,7 +750,7 @@ void ViewerWidget::ContextMenuSetCustomSafeMargins()
|
||||
|
||||
void ViewerWidget::WindowAboutToClose()
|
||||
{
|
||||
windows_.removeOne(static_cast<ViewerWindow*>(sender()));
|
||||
windows_.remove(windows_.key(static_cast<ViewerWindow*>(sender())));
|
||||
}
|
||||
|
||||
void ViewerWidget::ContextMenuScopeTriggered(QAction *action)
|
||||
@@ -801,6 +806,10 @@ void ViewerWidget::RendererGeneratedFrameForQueue()
|
||||
|
||||
void ViewerWidget::ShowContextMenu(const QPoint &pos)
|
||||
{
|
||||
if (!GetConnectedNode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Menu menu(static_cast<QWidget*>(sender()));
|
||||
|
||||
context_menu_widget_ = dynamic_cast<ViewerDisplayWidget*>(sender());
|
||||
@@ -854,6 +863,8 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos)
|
||||
QString::number(s->size().height())));
|
||||
|
||||
a->setData(i);
|
||||
a->setCheckable(true);
|
||||
a->setChecked(windows_.contains(QGuiApplication::screens().at(i)));
|
||||
}
|
||||
|
||||
connect(full_screen_menu, &QMenu::triggered, this, &ViewerWidget::ContextMenuSetFullScreen);
|
||||
|
||||
@@ -223,7 +223,7 @@ private:
|
||||
|
||||
AudioWaveformView* waveform_view_;
|
||||
|
||||
QList<ViewerWindow*> windows_;
|
||||
QHash<QScreen*, ViewerWindow*> windows_;
|
||||
|
||||
ViewerDisplayWidget* display_widget_;
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
MainMenu* main_menu = new MainMenu(this);
|
||||
setMenuBar(main_menu);
|
||||
|
||||
LoadCustomShortcuts();
|
||||
|
||||
// Create and set status bar
|
||||
MainStatusBar* status_bar = new MainStatusBar(this);
|
||||
status_bar->ConnectTaskManager(TaskManager::instance());
|
||||
@@ -331,21 +333,20 @@ void MainWindow::ProjectOpen(Project *p)
|
||||
|
||||
void MainWindow::ProjectClose(Project *p)
|
||||
{
|
||||
// Close any open sequences from project
|
||||
QVector<Sequence*> open_sequences = p->root()->ListChildrenOfType<Sequence>();
|
||||
// Close any nodes open in TimeBasedWidgets
|
||||
foreach (PanelWidget* panel, PanelManager::instance()->panels()) {
|
||||
TimeBasedPanel* tbp = dynamic_cast<TimeBasedPanel*>(panel);
|
||||
|
||||
foreach (Sequence* seq, open_sequences) {
|
||||
if (IsSequenceOpen(seq)) {
|
||||
CloseSequence(seq);
|
||||
if (tbp && tbp->GetConnectedViewer() && tbp->GetConnectedViewer()->project() == p) {
|
||||
if (dynamic_cast<TimelinePanel*>(tbp)) {
|
||||
// Prefer our CloseSequence function which will delete any unnecessary timeline panels
|
||||
CloseSequence(static_cast<Sequence*>(tbp->GetConnectedViewer()));
|
||||
} else {
|
||||
tbp->DisconnectViewerNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close any open footage in footage viewer
|
||||
if (footage_viewer_panel_->GetConnectedViewer()
|
||||
&& footage_viewer_panel_->GetConnectedViewer()->project() == p) {
|
||||
footage_viewer_panel_->DisconnectViewerNode();
|
||||
}
|
||||
|
||||
// Close any extra folder panels
|
||||
foreach (ProjectPanel* panel, folder_panels_) {
|
||||
if (panel->project() == p) {
|
||||
@@ -407,6 +408,8 @@ void MainWindow::closeEvent(QCloseEvent *e)
|
||||
|
||||
PanelManager::instance()->DeleteAllPanels();
|
||||
|
||||
SaveCustomShortcuts();
|
||||
|
||||
QMainWindow::closeEvent(e);
|
||||
}
|
||||
|
||||
@@ -523,11 +526,9 @@ void MainWindow::RemoveTimelinePanel(TimelinePanel *panel)
|
||||
{
|
||||
// Stop showing this timeline in the viewer
|
||||
TimelineFocused(nullptr);
|
||||
panel->ConnectViewerNode(nullptr);
|
||||
|
||||
if (timeline_panels_.size() == 1) {
|
||||
// Leave our single remaining timeline panel open
|
||||
panel->ConnectViewerNode(nullptr);
|
||||
} else {
|
||||
if (timeline_panels_.size() != 1) {
|
||||
timeline_panels_.removeOne(panel);
|
||||
panel->deleteLater();
|
||||
}
|
||||
@@ -550,6 +551,107 @@ void MainWindow::TimelineFocused(ViewerOutput* viewer)
|
||||
curve_panel_->ConnectViewerNode(viewer);
|
||||
}
|
||||
|
||||
QString MainWindow::GetCustomShortcutsFile()
|
||||
{
|
||||
return QDir(FileFunctions::GetConfigurationLocation()).filePath(QStringLiteral("shortcuts"));
|
||||
}
|
||||
|
||||
void LoadCustomShortcutsInternal(QMenu* menu, const QMap<QString, QString>& shortcuts)
|
||||
{
|
||||
QList<QAction*> actions = menu->actions();
|
||||
|
||||
foreach (QAction* a, actions) {
|
||||
if (a->menu()) {
|
||||
LoadCustomShortcutsInternal(a->menu(), shortcuts);
|
||||
} else if (!a->isSeparator()) {
|
||||
QString action_id = a->property("id").toString();
|
||||
|
||||
if (shortcuts.contains(action_id)) {
|
||||
a->setShortcut(shortcuts.value(action_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::LoadCustomShortcuts()
|
||||
{
|
||||
QFile shortcut_file(GetCustomShortcutsFile());
|
||||
if (shortcut_file.exists() && shortcut_file.open(QFile::ReadOnly)) {
|
||||
QMap<QString, QString> shortcuts;
|
||||
|
||||
QString shortcut_str = QString::fromUtf8(shortcut_file.readAll());
|
||||
|
||||
QStringList shortcut_list = shortcut_str.split(QStringLiteral("\n"));
|
||||
|
||||
foreach (const QString& s, shortcut_list) {
|
||||
QStringList shortcut_line = s.split(QStringLiteral("\t"));
|
||||
if (shortcut_line.size() >= 2) {
|
||||
shortcuts.insert(shortcut_line.at(0), shortcut_line.at(1));
|
||||
}
|
||||
}
|
||||
|
||||
shortcut_file.close();
|
||||
|
||||
if (!shortcuts.isEmpty()) {
|
||||
QList<QAction*> menus = menuBar()->actions();
|
||||
|
||||
foreach (QAction* menu, menus) {
|
||||
LoadCustomShortcutsInternal(menu->menu(), shortcuts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SaveCustomShortcutsInternal(QMenu* menu, QMap<QString, QString>* shortcuts)
|
||||
{
|
||||
QList<QAction*> actions = menu->actions();
|
||||
|
||||
foreach (QAction* a, actions) {
|
||||
if (a->menu()) {
|
||||
SaveCustomShortcutsInternal(a->menu(), shortcuts);
|
||||
} else if (!a->isSeparator()) {
|
||||
QString default_shortcut = a->property("keydefault").toString();
|
||||
QString current_shortcut = a->shortcut().toString();
|
||||
if (current_shortcut != default_shortcut) {
|
||||
QString action_id = a->property("id").toString();
|
||||
shortcuts->insert(action_id, current_shortcut);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::SaveCustomShortcuts()
|
||||
{
|
||||
QMap<QString, QString> shortcuts;
|
||||
QList<QAction*> menus = menuBar()->actions();
|
||||
|
||||
foreach (QAction* menu, menus) {
|
||||
SaveCustomShortcutsInternal(menu->menu(), &shortcuts);
|
||||
}
|
||||
|
||||
QFile shortcut_file(GetCustomShortcutsFile());
|
||||
if (shortcuts.isEmpty()) {
|
||||
if (shortcut_file.exists()) {
|
||||
// No custom shortcuts, remove any existing file
|
||||
shortcut_file.remove();
|
||||
}
|
||||
} else if (shortcut_file.open(QFile::WriteOnly)) {
|
||||
for (auto it=shortcuts.cbegin(); it!=shortcuts.cend(); it++) {
|
||||
if (it != shortcuts.cbegin()) {
|
||||
shortcut_file.write(QStringLiteral("\n").toUtf8());
|
||||
}
|
||||
|
||||
shortcut_file.write(it.key().toUtf8());
|
||||
shortcut_file.write(QStringLiteral("\t").toUtf8());
|
||||
shortcut_file.write(it.value().toUtf8());
|
||||
}
|
||||
shortcut_file.close();
|
||||
} else {
|
||||
qCritical() << "Failed to save custom keyboard shortcuts";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::FocusedPanelChanged(PanelWidget *panel)
|
||||
{
|
||||
TimelinePanel* timeline = dynamic_cast<TimelinePanel*>(panel);
|
||||
@@ -643,9 +745,9 @@ void MainWindow::showEvent(QShowEvent *e)
|
||||
if (!strcmp(vendor, "nouveau")) {
|
||||
QMetaObject::invokeMethod(this, "ShowNouveauWarning", Qt::QueuedConnection);
|
||||
}
|
||||
#endif
|
||||
|
||||
first_show_ = false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -132,6 +132,12 @@ private:
|
||||
|
||||
void TimelineFocused(ViewerOutput *viewer);
|
||||
|
||||
static QString GetCustomShortcutsFile();
|
||||
|
||||
void LoadCustomShortcuts();
|
||||
|
||||
void SaveCustomShortcuts();
|
||||
|
||||
QByteArray premaximized_state_;
|
||||
|
||||
// Standard panels
|
||||
|
||||
Reference in New Issue
Block a user