Merge branch 'master' into rational_slider

This commit is contained in:
ThomasWilshaw
2021-04-12 10:42:26 +01:00
committed by GitHub
80 changed files with 1266 additions and 491 deletions
+7 -7
View File
@@ -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
+12
View File
@@ -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) {
+7
View File
@@ -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();
+1
View File
@@ -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
+6 -2
View File
@@ -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++) {
+12 -1
View File
@@ -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 {
+29
View File
@@ -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
+4 -2
View File
@@ -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
+2 -1
View File
@@ -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
View File
@@ -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);
+62 -25
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+5 -2
View File
@@ -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);
}
}
+3
View File
@@ -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;
+1
View File
@@ -56,6 +56,7 @@ public:
kProjectFootage,
kProjectFolder,
kProjectSequence,
kValueNode,
// Count value
kInternalNodeCount
+8 -1
View File
@@ -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();
}
}
+5
View File
@@ -40,6 +40,11 @@ public:
*/
NodeGraph();
/**
* @brief NodeGraph Destructor
*/
virtual ~NodeGraph() override;
/**
* @brief Destructively destroys all nodes in the graph
*/
+1
View File
@@ -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}
+6
View File
@@ -0,0 +1,6 @@
#include "multicamnode.h"
MultiCamNode::MultiCamNode()
{
}
+11
View File
@@ -0,0 +1,11 @@
#ifndef MULTICAMNODE_H
#define MULTICAMNODE_H
class MultiCamNode
{
public:
MultiCamNode();
};
#endif // MULTICAMNODE_H
+22
View File
@@ -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
)
+81
View File
@@ -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);
}
}
+78
View File
@@ -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
+18 -5
View File
@@ -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();
}
}
+2 -3
View File
@@ -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_;
};
+12 -6
View File
@@ -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;
+7
View File
@@ -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
*
+6 -2
View File
@@ -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
View File
@@ -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);
+12
View File
@@ -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
+3 -1
View File
@@ -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));
}
}
+36 -16
View File
@@ -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) {
+4
View File
@@ -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);
+12 -116
View File
@@ -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 &params)
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();
}*/
}
-3
View File
@@ -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;
-10
View File
@@ -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;
+3 -2
View File
@@ -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;
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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);
};
+15 -5
View File
@@ -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";
+3 -1
View File
@@ -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);
+16 -1
View File
@@ -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();
}
}
+3 -3
View File
@@ -79,7 +79,7 @@ RenderManager::~RenderManager()
}
}
QByteArray RenderManager::Hash(const Node *n, const VideoParams &params, const rational &time)
QByteArray RenderManager::Hash(const Node *n, const QString& output, const VideoParams &params, const rational &time)
{
QCryptographicHash hasher(QCryptographicHash::Sha1);
@@ -93,7 +93,7 @@ QByteArray RenderManager::Hash(const Node *n, const VideoParams &params, 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);
+5 -1
View File
@@ -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 &params, const rational &time);
static QByteArray Hash(const Node *n, const QString &output, const VideoParams &params, const rational &time);
static QByteArray Hash(const NodeOutput &output, const VideoParams &params, const rational &time)
{
return Hash(output.node(), output.output(), params, time);
}
/**
* @brief Asynchronously generate a frame at a given time
+62 -30
View File
@@ -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
+7 -1
View File
@@ -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;
+13 -10
View File
@@ -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()
+2 -2
View File
@@ -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_;
};
+14 -9
View File
@@ -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) {
+2
View File
@@ -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);
+60 -13
View File
@@ -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
+1
View File
@@ -23,6 +23,7 @@
#ifdef USE_OTIO
#include "common/otioutils.h"
#include "node/project/project.h"
#include "task/project/load/loadbasetask.h"
+30 -25
View File
@@ -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
+4 -3
View File
@@ -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_;
+1 -1
View File
@@ -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;
+4 -3
View File
@@ -262,7 +262,7 @@ void NodeParamView::UpdateItemTime(const int64_t &timestamp)
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());
+42 -8
View File
@@ -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);
};
}
+29 -5
View File
@@ -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_);
}
}
+18
View File
@@ -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 {
+11 -7
View File
@@ -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);
}
}
}
}
+6 -2
View File
@@ -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) {
+4
View File
@@ -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_) {
+14 -3
View File
@@ -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);
+1 -1
View File
@@ -223,7 +223,7 @@ private:
AudioWaveformView* waveform_view_;
QList<ViewerWindow*> windows_;
QHash<QScreen*, ViewerWindow*> windows_;
ViewerDisplayWidget* display_widget_;
+118 -16
View File
@@ -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
}
}
+6
View File
@@ -132,6 +132,12 @@ private:
void TimelineFocused(ViewerOutput *viewer);
static QString GetCustomShortcutsFile();
void LoadCustomShortcuts();
void SaveCustomShortcuts();
QByteArray premaximized_state_;
// Standard panels