Merge master into branch
This commit is contained in:
+27
-28
@@ -24,14 +24,31 @@ vcpkg integrate install
|
||||
cd %APPVEYOR_BUILD_FOLDER%
|
||||
|
||||
REM Acquire FFmpeg
|
||||
set FFMPEG_VER=ffmpeg-4.2.1-win64
|
||||
set FFMPEG_VER=ffmpeg-4.2.3-win64
|
||||
curl https://ffmpeg.zeranoe.com/builds/win64/dev/%FFMPEG_VER%-dev.zip > %FFMPEG_VER%-dev.zip
|
||||
curl https://ffmpeg.zeranoe.com/builds/win64/shared/%FFMPEG_VER%-shared.zip > %FFMPEG_VER%-shared.zip
|
||||
7z x %FFMPEG_VER%-dev.zip
|
||||
7z x %FFMPEG_VER%-shared.zip
|
||||
|
||||
REM Add Qt and FFmpeg directory to path
|
||||
set PATH=%PATH%;C:\Qt\5.13.2\msvc2017_64\bin;%APPVEYOR_BUILD_FOLDER%\%FFMPEG_VER%-dev
|
||||
REM Acquire Google Crashpad
|
||||
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
|
||||
set PATH=%PATH%;%APPVEYOR_BUILD_FOLDER%\depot_tools
|
||||
|
||||
REM Run `fetch` through cmd /c since fetch is a batch file that seems to call exit
|
||||
cmd /c fetch crashpad
|
||||
cd crashpad
|
||||
cmd /c gn gen out/Default
|
||||
|
||||
REM Patch to build a dynamic release instead of a static release
|
||||
ren out\Default\toolchain.ninja toolchain.ninja.old
|
||||
sed "s/${cflags_c}/${cflags_c} \/MD/g" out\Default\toolchain.ninja.old > out\Default\toolchain.ninja
|
||||
|
||||
REM Build Crashpad
|
||||
ninja.exe -C out/Default
|
||||
cd ..
|
||||
|
||||
REM Add Qt, FFmpeg, and Crashpad to path
|
||||
set PATH=%PATH%;C:\Qt\5.13.2\msvc2017_64\bin;%APPVEYOR_BUILD_FOLDER%\%FFMPEG_VER%-dev;%APPVEYOR_BUILD_FOLDER%\crashpad;%APPVEYOR_BUILD_FOLDER%\crashpad\out\Default
|
||||
|
||||
REM Run cmake
|
||||
cmake -G "Ninja" . -DCMAKE_TOOLCHAIN_FILE=c:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||
@@ -42,36 +59,21 @@ ninja.exe || exit /B 1
|
||||
REM If this is a pull request, no further packaging/deploying needs to be done
|
||||
if NOT "%APPVEYOR_PULL_REQUEST_NUMBER%" == "" goto end
|
||||
|
||||
REM Create Crashpad symbol file and upload it
|
||||
C:\msys64\usr\bin\wget.exe https://github.com/google/breakpad/blob/master/src/tools/windows/binaries/dump_syms.exe?raw=true -O dump_syms.exe
|
||||
dump_syms app\olive-editor.pdb > olive-editor.sym
|
||||
curl -F symfile=@olive-editor.sym https://olivevideoeditor.org/crashpad/symbols.php
|
||||
|
||||
REM Start building package
|
||||
mkdir olive-editor
|
||||
cd olive-editor
|
||||
copy ..\app\olive-editor.exe .
|
||||
copy ..\app\olive-editor.pdb .
|
||||
copy ..\app\crashhandler.exe .
|
||||
copy ..\crashpad\out\Default\crashpad_handler.exe .
|
||||
windeployqt olive-editor.exe
|
||||
copy ..\%FFMPEG_VER%-shared\bin\*.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\OpenColorIO.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\OpenImageIO.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\yaml-cpp.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\Half-2_3.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\Iex-2_3.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\IexMath-2_3.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\IlmImf-2_3.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\IlmImfUtil-2_3.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\IlmThread-2_3.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\Imath-2_3.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\*.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\*.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\*.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\*.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\libpng16.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\jpeg62.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\tiff.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\zlib1.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\lzma.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\boost_date_time-vc141-mt-x64-1_72.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\boost_filesystem-vc141-mt-x64-1_72.dll .
|
||||
copy C:\Tools\vcpkg\installed\x64-windows\bin\boost_thread-vc141-mt-x64-1_72.dll .
|
||||
copy ..\app\*.dll .
|
||||
|
||||
REM Package done, begin deployment
|
||||
cd ..
|
||||
@@ -85,9 +87,6 @@ REM Create portable
|
||||
copy nul olive-editor\portable
|
||||
7z a %PKGNAME%.zip olive-editor
|
||||
|
||||
REM We're ready to upload, but we only upload *sometimes*
|
||||
REM set PATH=%PATH%;C:\msys64\usr\bin
|
||||
|
||||
REM If this was a tagged build, upload
|
||||
if "%APPVEYOR_REPO_TAG%"=="true" GOTO upload
|
||||
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ export VERSION=$(git rev-parse --short=8 HEAD)
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
|
||||
# Generate Makefile
|
||||
cmake .
|
||||
cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||
|
||||
# Make
|
||||
make -j$(sysctl -n hw.ncpu)
|
||||
@@ -50,7 +50,7 @@ if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
elif [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
|
||||
# Generate Makefile
|
||||
cmake .
|
||||
cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||
|
||||
# Make
|
||||
make -j$(nproc)
|
||||
|
||||
@@ -67,6 +67,12 @@ find_package(FFMPEG 3.0 REQUIRED
|
||||
swresample
|
||||
)
|
||||
|
||||
find_package(GoogleCrashpad)
|
||||
|
||||
if (NOT GoogleCrashpad_FOUND)
|
||||
message(" Automatic crash reporting will be disabled.")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
find_package(Git)
|
||||
if(GIT_FOUND)
|
||||
|
||||
+28
-5
@@ -79,9 +79,6 @@ if(APPLE)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9")
|
||||
endif()
|
||||
|
||||
# Set compiler definitions
|
||||
target_compile_definitions(${OLIVE_TARGET} PRIVATE ${OLIVE_DEFINITIONS})
|
||||
|
||||
# Set compiler options
|
||||
if(MSVC)
|
||||
target_compile_options(
|
||||
@@ -100,13 +97,14 @@ else()
|
||||
target_compile_options(
|
||||
${OLIVE_TARGET}
|
||||
PRIVATE
|
||||
-O2
|
||||
"$<$<CONFIG:RELEASE>:-O2>"
|
||||
-Werror
|
||||
-Wuninitialized
|
||||
-pedantic-errors
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wno-unused-parameter
|
||||
-Wshadow
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -125,7 +123,12 @@ target_include_directories(
|
||||
${FFMPEG_INCLUDE_DIRS}
|
||||
${OCIO_INCLUDE_DIRS}
|
||||
${OIIO_INCLUDE_DIRS}
|
||||
${OPENEXR_INCLUDE_DIRS}
|
||||
${OPENEXR_INCLUDES}
|
||||
|
||||
# HACK: Brew on macOS separates OpenEXR and IlmBase into two folders even though they seem to
|
||||
# expect to be in one. This includes the IlmBase files as if they were in the same folders
|
||||
# as the OpenEXR headers.
|
||||
${ILMBASE_INCLUDES}/OpenEXR
|
||||
)
|
||||
|
||||
# Set link libraries
|
||||
@@ -165,6 +168,26 @@ elseif (APPLE)
|
||||
)
|
||||
endif()
|
||||
|
||||
# Enable Crashpad if found
|
||||
if (GoogleCrashpad_FOUND)
|
||||
set(OLIVE_DEFINITIONS ${OLIVE_DEFINITIONS} USE_CRASHPAD)
|
||||
|
||||
target_include_directories(
|
||||
${OLIVE_TARGET}
|
||||
PRIVATE
|
||||
${CRASHPAD_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
${OLIVE_TARGET}
|
||||
PRIVATE
|
||||
${CRASHPAD_LIBRARIES}
|
||||
)
|
||||
endif()
|
||||
|
||||
# Set compiler definitions
|
||||
target_compile_definitions(${OLIVE_TARGET} PRIVATE ${OLIVE_DEFINITIONS})
|
||||
|
||||
set(OLIVE_TS_FILES
|
||||
# FIXME: Empty variable
|
||||
)
|
||||
|
||||
@@ -48,18 +48,31 @@ AudioManager *AudioManager::instance()
|
||||
|
||||
void AudioManager::RefreshDevices()
|
||||
{
|
||||
output_watcher_.setFuture(QtConcurrent::run(QAudioDeviceInfo::availableDevices, QAudio::AudioOutput));
|
||||
input_watcher_.setFuture(QtConcurrent::run(QAudioDeviceInfo::availableDevices, QAudio::AudioInput));
|
||||
if (!is_refreshing_outputs_) {
|
||||
QFutureWatcher< QList<QAudioDeviceInfo> >* output_watcher = new QFutureWatcher< QList<QAudioDeviceInfo> >();
|
||||
connect(output_watcher, &QFutureWatcher< QList<QAudioDeviceInfo> >::finished, this, &AudioManager::OutputDevicesRefreshed);
|
||||
output_watcher->setFuture(QtConcurrent::run(QAudioDeviceInfo::availableDevices, QAudio::AudioOutput));
|
||||
|
||||
is_refreshing_outputs_ = true;
|
||||
}
|
||||
|
||||
if (!is_refreshing_inputs_) {
|
||||
QFutureWatcher< QList<QAudioDeviceInfo> >* input_watcher = new QFutureWatcher< QList<QAudioDeviceInfo> >();
|
||||
connect(input_watcher, &QFutureWatcher< QList<QAudioDeviceInfo> >::finished, this, &AudioManager::InputDevicesRefreshed);
|
||||
input_watcher->setFuture(QtConcurrent::run(QAudioDeviceInfo::availableDevices, QAudio::AudioInput));
|
||||
|
||||
is_refreshing_inputs_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioManager::IsRefreshingOutputs()
|
||||
{
|
||||
return output_watcher_.isRunning();
|
||||
return is_refreshing_outputs_;
|
||||
}
|
||||
|
||||
bool AudioManager::IsRefreshingInputs()
|
||||
{
|
||||
return input_watcher_.isRunning();
|
||||
return is_refreshing_inputs_;
|
||||
}
|
||||
|
||||
void AudioManager::PushToOutput(const QByteArray &samples)
|
||||
@@ -200,6 +213,8 @@ void AudioManager::ReverseBuffer(char *buffer, int buffer_size, int sample_size)
|
||||
}
|
||||
|
||||
AudioManager::AudioManager() :
|
||||
is_refreshing_inputs_(false),
|
||||
is_refreshing_outputs_(false),
|
||||
output_is_set_(false),
|
||||
input_(nullptr),
|
||||
input_file_(nullptr)
|
||||
@@ -210,9 +225,6 @@ AudioManager::AudioManager() :
|
||||
output_manager_.moveToThread(&output_thread_);
|
||||
|
||||
connect(&output_manager_, &AudioOutputManager::OutputNotified, this, &AudioManager::OutputNotified);
|
||||
|
||||
connect(&output_watcher_, &QFutureWatcher< QList<QAudioDeviceInfo> >::finished, this, &AudioManager::OutputDevicesRefreshed);
|
||||
connect(&input_watcher_, &QFutureWatcher< QList<QAudioDeviceInfo> >::finished, this, &AudioManager::InputDevicesRefreshed);
|
||||
}
|
||||
|
||||
AudioManager::~AudioManager()
|
||||
@@ -224,7 +236,11 @@ AudioManager::~AudioManager()
|
||||
|
||||
void AudioManager::OutputDevicesRefreshed()
|
||||
{
|
||||
output_devices_ = output_watcher_.result();
|
||||
QFutureWatcher< QList<QAudioDeviceInfo> >* watcher = static_cast<QFutureWatcher< QList<QAudioDeviceInfo> >*>(sender());
|
||||
|
||||
output_devices_ = watcher->result();
|
||||
watcher->deleteLater();
|
||||
is_refreshing_outputs_ = false;
|
||||
|
||||
QString preferred_audio_output = Config::Current()["PreferredAudioOutput"].toString();
|
||||
|
||||
@@ -247,7 +263,11 @@ void AudioManager::OutputDevicesRefreshed()
|
||||
|
||||
void AudioManager::InputDevicesRefreshed()
|
||||
{
|
||||
input_devices_ = input_watcher_.result();
|
||||
QFutureWatcher< QList<QAudioDeviceInfo> >* watcher = static_cast<QFutureWatcher< QList<QAudioDeviceInfo> >*>(sender());
|
||||
|
||||
input_devices_ = watcher->result();
|
||||
watcher->deleteLater();
|
||||
is_refreshing_inputs_ = false;
|
||||
|
||||
QString preferred_audio_input = Config::Current()["PreferredAudioInput"].toString();
|
||||
|
||||
|
||||
@@ -102,8 +102,8 @@ private:
|
||||
QList<QAudioDeviceInfo> input_devices_;
|
||||
QList<QAudioDeviceInfo> output_devices_;
|
||||
|
||||
QFutureWatcher< QList<QAudioDeviceInfo> > input_watcher_;
|
||||
QFutureWatcher< QList<QAudioDeviceInfo> > output_watcher_;
|
||||
bool is_refreshing_inputs_;
|
||||
bool is_refreshing_outputs_;
|
||||
|
||||
static AudioManager* instance_;
|
||||
|
||||
|
||||
@@ -231,10 +231,13 @@ void AudioVisualWaveform::DrawSample(QPainter *painter, const QVector<SamplePerC
|
||||
int channel_half_height = channel_height / 2;
|
||||
|
||||
for (int i=0;i<sample.size();i++) {
|
||||
qfloat16 max = qMin(sample.at(i).max, static_cast<qfloat16>(1.0f));
|
||||
qfloat16 min = qMax(sample.at(i).min, static_cast<qfloat16>(-1.0));
|
||||
|
||||
if (Config::Current()[QStringLiteral("RectifiedWaveforms")].toBool()) {
|
||||
int channel_bottom = y + channel_height * (i + 1);
|
||||
|
||||
int diff = qRound((sample.at(i).max - sample.at(i).min) * channel_half_height);
|
||||
int diff = qRound((max - min) * channel_half_height);
|
||||
|
||||
painter->drawLine(x,
|
||||
channel_bottom - diff,
|
||||
@@ -244,9 +247,9 @@ void AudioVisualWaveform::DrawSample(QPainter *painter, const QVector<SamplePerC
|
||||
int channel_mid = y + channel_height * i + channel_half_height;
|
||||
|
||||
painter->drawLine(x,
|
||||
channel_mid + qRound(sample.at(i).min * static_cast<float>(channel_half_height)),
|
||||
channel_mid + qRound(min * static_cast<float>(channel_half_height)),
|
||||
x,
|
||||
channel_mid + qRound(sample.at(i).max * static_cast<float>(channel_half_height)));
|
||||
channel_mid + qRound(max * static_cast<float>(channel_half_height)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "cliexportmanager.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
CLIExportManager::CLIExportManager()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -0,0 +1,36 @@
|
||||
/***
|
||||
|
||||
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 CLIEXPORTMANAGER_H
|
||||
#define CLIEXPORTMANAGER_H
|
||||
|
||||
#include "task/export/export.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class CLIExportManager : public QObject
|
||||
{
|
||||
public:
|
||||
CLIExportManager();
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // CLIEXPORTMANAGER_H
|
||||
@@ -27,9 +27,10 @@ OLIVE_NAMESPACE_ENTER
|
||||
CLIProgressDialog::CLIProgressDialog(const QString& title, QObject *parent) :
|
||||
QObject(parent),
|
||||
title_(title),
|
||||
progress_(0),
|
||||
progress_(-1),
|
||||
drawn_(false)
|
||||
{
|
||||
SetProgress(0);
|
||||
}
|
||||
|
||||
void CLIProgressDialog::Update()
|
||||
@@ -68,7 +69,7 @@ void CLIProgressDialog::Update()
|
||||
std::cout << "[";
|
||||
|
||||
// Get UI bar progress
|
||||
int bar_prog = qRound(progress_ * 0.01 * progress_bar_columns);
|
||||
int bar_prog = qRound(progress_ * progress_bar_columns);
|
||||
|
||||
// Draw filled in bar
|
||||
for (int i=0;i<bar_prog;i++) {
|
||||
@@ -90,10 +91,10 @@ void CLIProgressDialog::Update()
|
||||
std::cout << " ";
|
||||
}
|
||||
|
||||
std::cout << progress_ << "% " << std::flush;
|
||||
std::cout << qRound(progress_ * 100.0) << "% " << std::endl << std::flush;
|
||||
}
|
||||
|
||||
void CLIProgressDialog::SetProgress(int p)
|
||||
void CLIProgressDialog::SetProgress(double p)
|
||||
{
|
||||
if (progress_ != p) {
|
||||
progress_ = p;
|
||||
|
||||
@@ -34,14 +34,14 @@ public:
|
||||
CLIProgressDialog(const QString &title, QObject* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void SetProgress(int p);
|
||||
void SetProgress(double p);
|
||||
|
||||
private:
|
||||
void Update();
|
||||
|
||||
QString title_;
|
||||
|
||||
int progress_;
|
||||
double progress_;
|
||||
|
||||
bool drawn_;
|
||||
|
||||
|
||||
@@ -23,9 +23,15 @@
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
CLITaskDialog::CLITaskDialog(Task *task, QObject* parent) :
|
||||
CLIProgressDialog(task->GetTitle(), parent)
|
||||
CLIProgressDialog(task->GetTitle(), parent),
|
||||
task_(task)
|
||||
{
|
||||
// FIXME: Still developing this, don't try to use
|
||||
connect(task_, &Task::ProgressChanged, this, &CLITaskDialog::SetProgress);
|
||||
}
|
||||
|
||||
bool CLITaskDialog::Run()
|
||||
{
|
||||
return task_->Start();
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -28,9 +28,15 @@ OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class CLITaskDialog : public CLIProgressDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
CLITaskDialog(Task *task, QObject* parent = nullptr);
|
||||
|
||||
bool Run();
|
||||
|
||||
private:
|
||||
Task* task_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
#include "codec/oiio/oiiodecoder.h"
|
||||
#include "codec/waveinput.h"
|
||||
#include "codec/waveoutput.h"
|
||||
#include "common/filefunctions.h"
|
||||
#include "task/taskmanager.h"
|
||||
#include "project/project.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -175,6 +177,11 @@ QString Decoder::GetConformedFilename(const AudioParams ¶ms)
|
||||
return index_fn;
|
||||
}
|
||||
|
||||
QString Decoder::GetIndexFilename()
|
||||
{
|
||||
return QDir(stream_->footage()->project()->cache_path()).filePath(FileFunctions::GetUniqueFileIdentifier(stream()->footage()->filename()).append(QString::number(stream()->index())));
|
||||
}
|
||||
|
||||
bool Decoder::ConformAudio(const QAtomicInt *, const AudioParams& )
|
||||
{
|
||||
return false;
|
||||
|
||||
+2
-8
@@ -242,19 +242,13 @@ signals:
|
||||
protected:
|
||||
void SignalProcessingProgress(const int64_t& ts);
|
||||
|
||||
/**
|
||||
* @brief Returns the filename for the index
|
||||
*
|
||||
* Retrieves the absolute filename of the index file for this stream. Decoder must be open for
|
||||
* this to work correctly.
|
||||
*/
|
||||
virtual QString GetIndexFilename() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the destination filename of an audio stream conformed to a set of parameters
|
||||
*/
|
||||
QString GetConformedFilename(const AudioParams ¶ms);
|
||||
|
||||
QString GetIndexFilename();
|
||||
|
||||
bool open_;
|
||||
|
||||
QMutex mutex_;
|
||||
|
||||
@@ -88,6 +88,11 @@ void EncodingParams::set_video_threads(const int &threads)
|
||||
video_threads_ = threads;
|
||||
}
|
||||
|
||||
void EncodingParams::set_video_pix_fmt(const QString &s)
|
||||
{
|
||||
video_pix_fmt_ = s;
|
||||
}
|
||||
|
||||
const QString &EncodingParams::filename() const
|
||||
{
|
||||
return filename_;
|
||||
@@ -133,6 +138,11 @@ const int &EncodingParams::video_threads() const
|
||||
return video_threads_;
|
||||
}
|
||||
|
||||
const QString &EncodingParams::video_pix_fmt() const
|
||||
{
|
||||
return video_pix_fmt_;
|
||||
}
|
||||
|
||||
bool EncodingParams::audio_enabled() const
|
||||
{
|
||||
return audio_enabled_;
|
||||
@@ -158,6 +168,59 @@ void EncodingParams::SetExportLength(const rational &export_length)
|
||||
export_length_ = export_length;
|
||||
}
|
||||
|
||||
void EncodingParams::Save(QXmlStreamWriter *writer) const
|
||||
{
|
||||
writer->writeTextElement(QStringLiteral("filename"), filename_);
|
||||
|
||||
writer->writeStartElement(QStringLiteral("video"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("enabled"), QString::number(video_enabled_));
|
||||
|
||||
if (video_enabled_) {
|
||||
writer->writeTextElement(QStringLiteral("codec"), QString::number(video_codec_));
|
||||
writer->writeTextElement(QStringLiteral("width"), QString::number(video_params_.width()));
|
||||
writer->writeTextElement(QStringLiteral("height"), QString::number(video_params_.height()));
|
||||
writer->writeTextElement(QStringLiteral("format"), QString::number(video_params_.format()));
|
||||
writer->writeTextElement(QStringLiteral("timebase"), video_params_.time_base().toString());
|
||||
writer->writeTextElement(QStringLiteral("divider"), QString::number(video_params_.divider()));
|
||||
writer->writeTextElement(QStringLiteral("bitrate"), QString::number(video_bit_rate_));
|
||||
writer->writeTextElement(QStringLiteral("maxbitrate"), QString::number(video_max_bit_rate_));
|
||||
writer->writeTextElement(QStringLiteral("bufsize"), QString::number(video_buffer_size_));
|
||||
writer->writeTextElement(QStringLiteral("threads"), QString::number(video_threads_));
|
||||
|
||||
if (!video_opts_.isEmpty()) {
|
||||
writer->writeStartElement(QStringLiteral("opts"));
|
||||
|
||||
QHash<QString, QString>::const_iterator i;
|
||||
for (i=video_opts_.constBegin(); i!=video_opts_.constEnd(); i++) {
|
||||
writer->writeStartElement(QStringLiteral("entry"));
|
||||
|
||||
writer->writeTextElement(QStringLiteral("key"), i.key());
|
||||
writer->writeTextElement(QStringLiteral("value"), i.value());
|
||||
|
||||
writer->writeEndElement(); // entry
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // opts
|
||||
}
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // video
|
||||
|
||||
writer->writeStartElement(QStringLiteral("audio"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("enabled"), QString::number(audio_enabled_));
|
||||
|
||||
if (audio_enabled_) {
|
||||
writer->writeTextElement(QStringLiteral("codec"), QString::number(audio_codec_));
|
||||
writer->writeTextElement(QStringLiteral("samplerate"), QString::number(audio_params_.sample_rate()));
|
||||
writer->writeTextElement(QStringLiteral("channellayout"), QString::number(audio_params_.channel_layout()));
|
||||
writer->writeTextElement(QStringLiteral("format"), QString::number(audio_params_.format()));
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // audio
|
||||
}
|
||||
|
||||
Encoder* Encoder::CreateFromID(const QString &id, const EncodingParams& params)
|
||||
{
|
||||
Q_UNUSED(id)
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <QString>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
#include "codec/exportcodec.h"
|
||||
#include "codec/exportformat.h"
|
||||
@@ -50,6 +51,7 @@ public:
|
||||
void set_video_max_bit_rate(const int64_t& rate);
|
||||
void set_video_buffer_size(const int64_t& sz);
|
||||
void set_video_threads(const int& threads);
|
||||
void set_video_pix_fmt(const QString& s);
|
||||
|
||||
const QString& filename() const;
|
||||
|
||||
@@ -61,6 +63,7 @@ public:
|
||||
const int64_t& video_max_bit_rate() const;
|
||||
const int64_t& video_buffer_size() const;
|
||||
const int& video_threads() const;
|
||||
const QString& video_pix_fmt() const;
|
||||
|
||||
bool audio_enabled() const;
|
||||
const ExportCodec::Codec &audio_codec() const;
|
||||
@@ -69,6 +72,8 @@ public:
|
||||
const rational& GetExportLength() const;
|
||||
void SetExportLength(const rational& GetExportLength);
|
||||
|
||||
virtual void Save(QXmlStreamWriter* writer) const;
|
||||
|
||||
private:
|
||||
QString filename_;
|
||||
|
||||
@@ -80,6 +85,7 @@ private:
|
||||
int64_t video_max_bit_rate_;
|
||||
int64_t video_buffer_size_;
|
||||
int video_threads_;
|
||||
QString video_pix_fmt_;
|
||||
|
||||
bool audio_enabled_;
|
||||
ExportCodec::Codec audio_codec_;
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
|
||||
#include "exportcodec.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
QString ExportCodec::GetCodecName(ExportCodec::Codec c)
|
||||
@@ -77,4 +82,47 @@ bool ExportCodec::IsCodecAStillImage(ExportCodec::Codec c)
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList ExportCodec::GetPixelFormatsForCodec(ExportCodec::Codec c)
|
||||
{
|
||||
QStringList pix_fmts;
|
||||
|
||||
AVCodec* codec_info = nullptr;
|
||||
|
||||
switch (c) {
|
||||
case kCodecH264:
|
||||
codec_info = avcodec_find_encoder(AV_CODEC_ID_H264);
|
||||
break;
|
||||
case kCodecDNxHD:
|
||||
codec_info = avcodec_find_encoder(AV_CODEC_ID_DNXHD);
|
||||
break;
|
||||
case kCodecProRes:
|
||||
codec_info = avcodec_find_encoder(AV_CODEC_ID_PRORES);
|
||||
break;
|
||||
case kCodecH265:
|
||||
codec_info = avcodec_find_encoder(AV_CODEC_ID_HEVC);
|
||||
break;
|
||||
case kCodecOpenEXR:
|
||||
case kCodecPNG:
|
||||
case kCodecTIFF:
|
||||
// FIXME: Add these in (these will most likely use an OIIOEncoder which doesn't exist yet)
|
||||
break;
|
||||
case kCodecMP2:
|
||||
case kCodecMP3:
|
||||
case kCodecAAC:
|
||||
case kCodecPCM:
|
||||
case kCodecCount:
|
||||
// These are audio or invalid codecs and therefore have no pixel formats
|
||||
break;
|
||||
}
|
||||
|
||||
if (codec_info) {
|
||||
for (int i=0; codec_info->pix_fmts[i]!=-1; i++) {
|
||||
const char* pix_fmt_name = av_get_pix_fmt_name(codec_info->pix_fmts[i]);
|
||||
pix_fmts.append(pix_fmt_name);
|
||||
}
|
||||
}
|
||||
|
||||
return pix_fmts;
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -51,6 +51,8 @@ public:
|
||||
|
||||
static bool IsCodecAStillImage(Codec c);
|
||||
|
||||
static QStringList GetPixelFormatsForCodec(Codec c);
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
+262
-268
@@ -52,7 +52,8 @@ QHash< Stream*, QList<FFmpegDecoderInstance*> > FFmpegDecoder::instance_map_;
|
||||
QMutex FFmpegDecoder::instance_map_lock_;
|
||||
QHash< Stream*, FFmpegFramePool* > FFmpegDecoder::frame_pool_map_;
|
||||
|
||||
// FIXME: Hardcoded, ideally this value is dynamically chosen based on memory restraints
|
||||
// FIXME: Hardcoded value. It seems to work fine, but is there a possibility we should make
|
||||
// this a dynamic value somehow or a configurable value?
|
||||
const int FFmpegDecoderInstance::kMaxFrameLife = 2000;
|
||||
|
||||
FFmpegDecoder::FFmpegDecoder() :
|
||||
@@ -86,19 +87,20 @@ bool FFmpegDecoder::Open()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stream()->type() == Stream::kVideo) {
|
||||
if (stream()->type() == Stream::kImage || stream()->type() == Stream::kVideo) {
|
||||
// Get an Olive compatible AVPixelFormat
|
||||
src_pix_fmt_ = static_cast<AVPixelFormat>(our_instance->stream()->codecpar->format);
|
||||
ideal_pix_fmt_ = FFmpegCommon::GetCompatiblePixelFormat(src_pix_fmt_);
|
||||
|
||||
{
|
||||
if (stream()->type() == Stream::kVideo) {
|
||||
QMutexLocker map_locker(&instance_map_lock_);
|
||||
|
||||
// FIXME: Test code, this should be changed later
|
||||
FFmpegFramePool* frame_pool = frame_pool_map_.value(stream().get());
|
||||
|
||||
if (!frame_pool) {
|
||||
frame_pool = new FFmpegFramePool(256,
|
||||
// FIXME: Hardcoded value. It seems to work fine, but is there a possibility we should make
|
||||
// this a dynamic value somehow or a configurable value?
|
||||
frame_pool = new FFmpegFramePool(32,
|
||||
our_instance->stream()->codecpar->width,
|
||||
our_instance->stream()->codecpar->height,
|
||||
static_cast<AVPixelFormat>(our_instance->stream()->codecpar->format));
|
||||
@@ -106,7 +108,6 @@ bool FFmpegDecoder::Open()
|
||||
}
|
||||
|
||||
our_instance->SetFramePool(frame_pool);
|
||||
// End test code
|
||||
}
|
||||
|
||||
// Determine which Olive native pixel format we retrieved
|
||||
@@ -114,8 +115,6 @@ bool FFmpegDecoder::Open()
|
||||
native_pix_fmt_ = GetNativePixelFormat(ideal_pix_fmt_);
|
||||
|
||||
Q_ASSERT(native_pix_fmt_ != PixelFormat::PIX_FMT_INVALID);
|
||||
|
||||
aspect_ratio_ = our_instance->sample_aspect_ratio();
|
||||
}
|
||||
|
||||
time_base_ = our_instance->stream()->time_base;
|
||||
@@ -144,165 +143,177 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (stream()->type() != Stream::kVideo) {
|
||||
if (stream()->type() != Stream::kImage && stream()->type() != Stream::kVideo) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int64_t target_ts = Timecode::time_to_timestamp(timecode, time_base_) + start_time_;
|
||||
ImageStreamPtr is = std::static_pointer_cast<ImageStream>(stream());
|
||||
|
||||
VideoStreamPtr vs = std::static_pointer_cast<VideoStream>(stream());
|
||||
if (stream()->type() == Stream::kImage) {
|
||||
|
||||
FFmpegDecoderInstance* working_instance = nullptr;
|
||||
FFmpegFramePool::ElementPtr return_frame = nullptr;
|
||||
// FIXME: Hacky
|
||||
FFmpegDecoderInstance i(stream()->footage()->filename().toUtf8(), stream()->index());
|
||||
|
||||
// Find instance
|
||||
do {
|
||||
QMutexLocker list_locker(&instance_map_lock_);
|
||||
AVPacket* pkt = av_packet_alloc();
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
FramePtr output_frame = nullptr;
|
||||
|
||||
QList<FFmpegDecoderInstance*> non_ideal_contenders;
|
||||
int ret = i.GetFrame(pkt, frame);
|
||||
|
||||
QList<FFmpegDecoderInstance*> instances = instance_map_.value(stream().get());
|
||||
if (ret >= 0) {
|
||||
output_frame = BuffersToNativeFrame(divider,
|
||||
is->width(),
|
||||
is->height(),
|
||||
0,
|
||||
frame->data,
|
||||
frame->linesize);
|
||||
} else {
|
||||
qWarning() << "Failed to retrieve still image from decoder";
|
||||
}
|
||||
|
||||
foreach (FFmpegDecoderInstance* i, instances) {
|
||||
av_frame_free(&frame);
|
||||
av_packet_free(&pkt);
|
||||
|
||||
i->cache_lock()->lock();
|
||||
return output_frame;
|
||||
|
||||
if (i->CacheContainsTime(target_ts)) {
|
||||
} else {
|
||||
|
||||
// Found our instance, allow others to enter the list
|
||||
FFmpegFramePool::ElementPtr return_frame = nullptr;
|
||||
|
||||
list_locker.unlock();
|
||||
int64_t target_ts = Timecode::time_to_timestamp(timecode, time_base_) + start_time_;
|
||||
|
||||
// Get the frame from this cache
|
||||
return_frame = i->GetFrameFromCache(target_ts);
|
||||
VideoStreamPtr vs = std::static_pointer_cast<VideoStream>(stream());
|
||||
|
||||
// Got our frame, allow cache to continue
|
||||
i->cache_lock()->unlock();
|
||||
break;
|
||||
FFmpegDecoderInstance* working_instance = nullptr;
|
||||
|
||||
} else if (i->CacheWillContainTime(target_ts) || i->CacheCouldContainTime(target_ts)) {
|
||||
// Find instance
|
||||
do {
|
||||
QMutexLocker list_locker(&instance_map_lock_);
|
||||
|
||||
// Found our instance, allow others to enter the list
|
||||
list_locker.unlock();
|
||||
QList<FFmpegDecoderInstance*> non_ideal_contenders;
|
||||
|
||||
// If the instance is currently in use, enter into a loop of seeing from frames come up next in case one is ours
|
||||
if (i->IsWorking()) {
|
||||
QList<FFmpegDecoderInstance*> instances = instance_map_.value(stream().get());
|
||||
|
||||
do {
|
||||
// Allow instance to continue to the next frame
|
||||
i->cache_wait_cond()->wait(i->cache_lock());
|
||||
foreach (FFmpegDecoderInstance* i, instances) {
|
||||
|
||||
// See if the cache now contains this frame, if so we'll exit this loop
|
||||
if (i->CacheContainsTime(target_ts)) {
|
||||
i->cache_lock()->lock();
|
||||
|
||||
// Grab the frame
|
||||
return_frame = i->GetFrameFromCache(target_ts);
|
||||
if (i->CacheContainsTime(target_ts)) {
|
||||
|
||||
// We can release this worker now since we don't need it anymore
|
||||
i->cache_lock()->unlock();
|
||||
// Found our instance, allow others to enter the list
|
||||
|
||||
} else if (!i->IsWorking()) {
|
||||
list_locker.unlock();
|
||||
|
||||
// This instance finished and we didn't get our frame, we'll take it and continue it
|
||||
working_instance = i;
|
||||
break;
|
||||
// Get the frame from this cache
|
||||
return_frame = i->GetFrameFromCache(target_ts);
|
||||
|
||||
}
|
||||
} while (!return_frame);
|
||||
// Got our frame, allow cache to continue
|
||||
i->cache_lock()->unlock();
|
||||
break;
|
||||
|
||||
} else if (i->CacheWillContainTime(target_ts) || i->CacheCouldContainTime(target_ts)) {
|
||||
|
||||
// Found our instance, allow others to enter the list
|
||||
list_locker.unlock();
|
||||
|
||||
// If the instance is currently in use, enter into a loop of seeing from frames come up next in case one is ours
|
||||
if (i->IsWorking()) {
|
||||
|
||||
do {
|
||||
// Allow instance to continue to the next frame
|
||||
i->cache_wait_cond()->wait(i->cache_lock());
|
||||
|
||||
// See if the cache now contains this frame, if so we'll exit this loop
|
||||
if (i->CacheContainsTime(target_ts)) {
|
||||
|
||||
// Grab the frame
|
||||
return_frame = i->GetFrameFromCache(target_ts);
|
||||
|
||||
// We can release this worker now since we don't need it anymore
|
||||
i->cache_lock()->unlock();
|
||||
|
||||
} else if (!i->IsWorking()) {
|
||||
|
||||
// This instance finished and we didn't get our frame, we'll take it and continue it
|
||||
working_instance = i;
|
||||
break;
|
||||
|
||||
}
|
||||
} while (!return_frame);
|
||||
|
||||
} else {
|
||||
// Otherwise, we'll grab this instance and continue it ourselves
|
||||
working_instance = i;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
} else if (i->IsWorking()) {
|
||||
|
||||
// Ignore currently working instances
|
||||
i->cache_lock()->unlock();
|
||||
|
||||
} else if (i->CacheIsEmpty()) {
|
||||
|
||||
// Prioritize this cache over others (leaves this instance LOCKED in case we end up using it later)
|
||||
non_ideal_contenders.prepend(i);
|
||||
|
||||
} else {
|
||||
// Otherwise, we'll grab this instance and continue it ourselves
|
||||
working_instance = i;
|
||||
|
||||
// De-prioritize this cache (leaves this instance LOCKED in case we end up using it later)
|
||||
non_ideal_contenders.append(i);
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
} else if (i->IsWorking()) {
|
||||
|
||||
// Ignore currently working instances
|
||||
i->cache_lock()->unlock();
|
||||
|
||||
} else if (i->CacheIsEmpty()) {
|
||||
|
||||
// Prioritize this cache over others (leaves this instance LOCKED in case we end up using it later)
|
||||
non_ideal_contenders.prepend(i);
|
||||
|
||||
} else {
|
||||
|
||||
// De-prioritize this cache (leaves this instance LOCKED in case we end up using it later)
|
||||
non_ideal_contenders.append(i);
|
||||
|
||||
}
|
||||
|
||||
// If we didn't find a suitable contender, grab the first non-suitable and roll with that
|
||||
if (!return_frame && !working_instance && !non_ideal_contenders.isEmpty()) {
|
||||
working_instance = non_ideal_contenders.takeFirst();
|
||||
}
|
||||
|
||||
// For all instances we left locked but didn't end up using, lock them now
|
||||
foreach (FFmpegDecoderInstance* unsuitable_instance, non_ideal_contenders) {
|
||||
unsuitable_instance->cache_lock()->unlock();
|
||||
}
|
||||
} while (!return_frame && !working_instance);
|
||||
|
||||
if (!return_frame && working_instance) {
|
||||
|
||||
// This instance SHOULD remain locked from our earlier loop, making this operation safe
|
||||
working_instance->SetWorking(true);
|
||||
|
||||
// Retrieve frame
|
||||
return_frame = working_instance->RetrieveFrame(target_ts, true);
|
||||
|
||||
// Set working to false and wake any threads waiting
|
||||
working_instance->cache_lock()->lock();
|
||||
working_instance->SetWorking(false);
|
||||
working_instance->cache_wait_cond()->wakeAll();
|
||||
working_instance->cache_lock()->unlock();
|
||||
}
|
||||
|
||||
// If we didn't find a suitable contender, grab the first non-suitable and roll with that
|
||||
if (!return_frame && !working_instance && !non_ideal_contenders.isEmpty()) {
|
||||
working_instance = non_ideal_contenders.takeFirst();
|
||||
// We found the frame, we'll return a copy
|
||||
if (return_frame) {
|
||||
// Align buffer to data/linesize points that can be passed to sws_scale
|
||||
uint8_t* input_data[4];
|
||||
int input_linesize[4];
|
||||
|
||||
av_image_fill_arrays(input_data,
|
||||
input_linesize,
|
||||
reinterpret_cast<const uint8_t*>(return_frame->data()),
|
||||
src_pix_fmt_,
|
||||
vs->width(),
|
||||
vs->height(),
|
||||
1);
|
||||
|
||||
return BuffersToNativeFrame(divider,
|
||||
vs->width(),
|
||||
vs->height(),
|
||||
target_ts,
|
||||
input_data,
|
||||
input_linesize);
|
||||
}
|
||||
|
||||
// For all instances we left locked but didn't end up using, lock them now
|
||||
foreach (FFmpegDecoderInstance* unsuitable_instance, non_ideal_contenders) {
|
||||
unsuitable_instance->cache_lock()->unlock();
|
||||
}
|
||||
} while (!return_frame && !working_instance);
|
||||
|
||||
if (!return_frame && working_instance) {
|
||||
|
||||
// This instance SHOULD remain locked from our earlier loop, making this operation safe
|
||||
working_instance->SetWorking(true);
|
||||
|
||||
// Retrieve frame
|
||||
return_frame = working_instance->RetrieveFrame(target_ts, true);
|
||||
|
||||
// Set working to false and wake any threads waiting
|
||||
working_instance->cache_lock()->lock();
|
||||
working_instance->SetWorking(false);
|
||||
working_instance->cache_wait_cond()->wakeAll();
|
||||
working_instance->cache_lock()->unlock();
|
||||
}
|
||||
|
||||
// We found the frame, we'll return a copy
|
||||
if (return_frame) {
|
||||
if (divider != scale_divider_) {
|
||||
FreeScaler();
|
||||
InitScaler(divider);
|
||||
}
|
||||
|
||||
// Create frame to return
|
||||
FramePtr copy = Frame::Create();
|
||||
copy->set_video_params(VideoParams(vs->width(),
|
||||
vs->height(),
|
||||
native_pix_fmt_,
|
||||
divider));
|
||||
copy->set_timestamp(Timecode::timestamp_to_time(target_ts, time_base_));
|
||||
copy->set_sample_aspect_ratio(aspect_ratio_);
|
||||
copy->allocate();
|
||||
|
||||
// Align buffer to data/linesize points that can be passed to sws_scale
|
||||
uint8_t* input_data[4];
|
||||
int input_linesize[4];
|
||||
|
||||
av_image_fill_arrays(input_data,
|
||||
input_linesize,
|
||||
reinterpret_cast<const uint8_t*>(return_frame->data()),
|
||||
src_pix_fmt_,
|
||||
vs->width(),
|
||||
vs->height(),
|
||||
1);
|
||||
|
||||
// Convert frame to RGB/A for the rest of the pipeline
|
||||
uint8_t* output_data = reinterpret_cast<uint8_t*>(copy->data());
|
||||
int output_linesize = copy->linesize_bytes();
|
||||
|
||||
sws_scale(scale_ctx_,
|
||||
input_data,
|
||||
input_linesize,
|
||||
0,
|
||||
vs->height(),
|
||||
&output_data,
|
||||
&output_linesize);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
@@ -436,8 +447,6 @@ bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled)
|
||||
AVFormatContext* fmt_ctx = nullptr;
|
||||
error_code = avformat_open_input(&fmt_ctx, filename, nullptr, nullptr);
|
||||
|
||||
QList<Stream*> streams_that_need_manual_duration;
|
||||
|
||||
// Handle format context error
|
||||
if (error_code == 0) {
|
||||
|
||||
@@ -453,15 +462,83 @@ bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled)
|
||||
|
||||
if (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
|
||||
// Create a video stream object
|
||||
VideoStreamPtr video_stream = std::make_shared<VideoStream>();
|
||||
bool image_is_still = false;
|
||||
rational pixel_aspect_ratio;
|
||||
rational frame_rate;
|
||||
VideoParams::Interlacing interlacing = VideoParams::kInterlaceNone;
|
||||
|
||||
video_stream->set_width(avstream->codecpar->width);
|
||||
video_stream->set_height(avstream->codecpar->height);
|
||||
video_stream->set_frame_rate(av_guess_frame_rate(fmt_ctx, avstream, nullptr));
|
||||
video_stream->set_start_time(avstream->start_time);
|
||||
{
|
||||
// Read at least two frames to get more information about this video stream
|
||||
AVPacket* pkt = av_packet_alloc();
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
|
||||
str = video_stream;
|
||||
{
|
||||
FFmpegDecoderInstance instance(filename, i);
|
||||
|
||||
// Read first frame and retrieve some metadata
|
||||
if (instance.GetFrame(pkt, frame) >= 0) {
|
||||
// Check if video is interlaced and what field dominance it has if so
|
||||
if (frame->interlaced_frame) {
|
||||
if (frame->top_field_first) {
|
||||
interlacing = VideoParams::kInterlacedTopFirst;
|
||||
} else {
|
||||
interlacing = VideoParams::kInterlacedBottomFirst;
|
||||
}
|
||||
}
|
||||
|
||||
pixel_aspect_ratio = av_guess_sample_aspect_ratio(instance.fmt_ctx(),
|
||||
instance.stream(),
|
||||
frame);
|
||||
|
||||
frame_rate = av_guess_frame_rate(instance.fmt_ctx(),
|
||||
instance.stream(),
|
||||
frame);
|
||||
}
|
||||
|
||||
// Read second frame
|
||||
int ret = instance.GetFrame(pkt, frame);
|
||||
|
||||
if (ret >= 0) {
|
||||
// Check if we need a manual duration
|
||||
if (avstream->duration == AV_NOPTS_VALUE) {
|
||||
int64_t new_dur;
|
||||
|
||||
do {
|
||||
new_dur = frame->pts;
|
||||
} while (instance.GetFrame(pkt, frame) >= 0);
|
||||
|
||||
avstream->duration = new_dur;
|
||||
}
|
||||
} else if (ret == AVERROR_EOF) {
|
||||
// Video has only one frame in it, treat it like a still image
|
||||
image_is_still = true;
|
||||
}
|
||||
}
|
||||
|
||||
av_frame_free(&frame);
|
||||
av_packet_free(&pkt);
|
||||
}
|
||||
|
||||
ImageStreamPtr image_stream;
|
||||
|
||||
if (image_is_still) {
|
||||
image_stream = std::make_shared<ImageStream>();
|
||||
} else {
|
||||
VideoStreamPtr video_stream = std::make_shared<VideoStream>();
|
||||
|
||||
video_stream->set_frame_rate(frame_rate);
|
||||
video_stream->set_start_time(avstream->start_time);
|
||||
|
||||
image_stream = video_stream;
|
||||
}
|
||||
|
||||
image_stream->set_width(avstream->codecpar->width);
|
||||
image_stream->set_height(avstream->codecpar->height);
|
||||
image_stream->set_format(GetNativePixelFormat(FFmpegCommon::GetCompatiblePixelFormat(static_cast<AVPixelFormat>(avstream->codecpar->format))));
|
||||
image_stream->set_interlacing(interlacing);
|
||||
image_stream->set_pixel_aspect_ratio(pixel_aspect_ratio);
|
||||
|
||||
str = image_stream;
|
||||
|
||||
} else if (avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
|
||||
@@ -510,11 +587,6 @@ bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled)
|
||||
str->set_timebase(avstream->time_base);
|
||||
str->set_duration(avstream->duration);
|
||||
|
||||
// The container/stream info may not contain a duration, so we'll need to manually retrieve it
|
||||
if (avstream->duration == AV_NOPTS_VALUE) {
|
||||
streams_that_need_manual_duration.append(str.get());
|
||||
}
|
||||
|
||||
f->add_stream(str);
|
||||
}
|
||||
|
||||
@@ -522,51 +594,6 @@ bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled)
|
||||
result = true;
|
||||
}
|
||||
|
||||
// If the metadata did not contain a duration, we'll need to loop through the file to retrieve it
|
||||
if (!streams_that_need_manual_duration.isEmpty()) {
|
||||
|
||||
AVPacket* pkt = av_packet_alloc();
|
||||
|
||||
QVector<int64_t> durations(streams_that_need_manual_duration.size());
|
||||
durations.fill(0);
|
||||
|
||||
while (true) {
|
||||
if (cancelled && *cancelled) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Ensure previous buffers are cleared
|
||||
av_packet_unref(pkt);
|
||||
|
||||
// Read packet from file
|
||||
int ret = av_read_frame(fmt_ctx, pkt);
|
||||
|
||||
if (ret < 0) {
|
||||
// Handle errors that aren't EOF (which simply means the file is finished)
|
||||
if (ret != AVERROR_EOF) {
|
||||
qWarning() << "Error while finding duration";
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
for (int i=0;i<streams_that_need_manual_duration.size();i++) {
|
||||
if (streams_that_need_manual_duration.at(i)->index() == pkt->stream_index
|
||||
&& pkt->pts > durations.at(i)) {
|
||||
durations.replace(i, pkt->pts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
av_packet_free(&pkt);
|
||||
|
||||
if (!cancelled || !*cancelled) {
|
||||
for (int i=0;i<streams_that_need_manual_duration.size();i++) {
|
||||
streams_that_need_manual_duration.at(i)->set_duration(durations.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Free all memory
|
||||
avformat_close_input(&fmt_ctx);
|
||||
|
||||
@@ -590,40 +617,6 @@ void FFmpegDecoder::Error(const QString &s)
|
||||
ClearResources();
|
||||
}
|
||||
|
||||
QMutex scaler_lock;
|
||||
void SaveCacheFrame(FFmpegDecoder* decoder,
|
||||
SwsContext* scaler,
|
||||
AVFrame* frame,
|
||||
VideoParams params,
|
||||
QString dst_fn)
|
||||
{
|
||||
QByteArray converted_buffer(PixelFormat::GetBufferSize(params.format(),
|
||||
params.width(),
|
||||
params.height()),
|
||||
Qt::Uninitialized);
|
||||
|
||||
uint8_t* converted_data = reinterpret_cast<uint8_t*>(converted_buffer.data());
|
||||
int converted_linesize = PixelFormat::GetBufferSize(params.format(),
|
||||
params.width(),
|
||||
1);
|
||||
|
||||
scaler_lock.lock();
|
||||
sws_scale(scaler,
|
||||
frame->data,
|
||||
frame->linesize,
|
||||
0,
|
||||
frame->height,
|
||||
&converted_data,
|
||||
&converted_linesize);
|
||||
scaler_lock.unlock();
|
||||
|
||||
if (!FrameHashCache::SaveCacheFrame(dst_fn, converted_buffer.data(), params, converted_linesize)) {
|
||||
qCritical() <<" Failed to save cache frame" << dst_fn;
|
||||
}
|
||||
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams &p)
|
||||
{
|
||||
// Iterate through each audio frame and extract the PCM data
|
||||
@@ -753,17 +746,6 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams
|
||||
return success;
|
||||
}
|
||||
|
||||
QString FFmpegDecoder::GetIndexFilename() const
|
||||
{
|
||||
return FileFunctions::GetMediaIndexFilename(FileFunctions::GetUniqueFileIdentifier(stream()->footage()->filename()))
|
||||
.append(QString::number(stream()->index()));
|
||||
}
|
||||
|
||||
QString FFmpegDecoder::GetProxyFilename(int divider) const
|
||||
{
|
||||
return GetIndexFilename().append('d').append(QString::number(divider));
|
||||
}
|
||||
|
||||
int FFmpegDecoder::GetScaledDimension(int dim, int divider)
|
||||
{
|
||||
return dim / divider;
|
||||
@@ -794,6 +776,39 @@ uint64_t FFmpegDecoder::ValidateChannelLayout(AVStream* stream)
|
||||
return av_get_default_channel_layout(stream->codecpar->channels);
|
||||
}
|
||||
|
||||
FramePtr FFmpegDecoder::BuffersToNativeFrame(int divider, int width, int height, int64_t ts, uint8_t** input_data, int* input_linesize)
|
||||
{
|
||||
if (divider != scale_divider_) {
|
||||
FreeScaler();
|
||||
InitScaler(divider);
|
||||
}
|
||||
|
||||
// Create frame to return
|
||||
FramePtr copy = Frame::Create();
|
||||
copy->set_video_params(VideoParams(width,
|
||||
height,
|
||||
native_pix_fmt_,
|
||||
std::static_pointer_cast<ImageStream>(stream())->pixel_aspect_ratio(),
|
||||
std::static_pointer_cast<ImageStream>(stream())->interlacing(),
|
||||
divider));
|
||||
copy->set_timestamp(Timecode::timestamp_to_time(ts, time_base_));
|
||||
copy->allocate();
|
||||
|
||||
// Convert frame to RGB/A for the rest of the pipeline
|
||||
uint8_t* output_data = reinterpret_cast<uint8_t*>(copy->data());
|
||||
int output_linesize = copy->linesize_bytes();
|
||||
|
||||
sws_scale(scale_ctx_,
|
||||
input_data,
|
||||
input_linesize,
|
||||
0,
|
||||
height,
|
||||
&output_data,
|
||||
&output_linesize);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
int FFmpegDecoderInstance::GetFrame(AVPacket *pkt, AVFrame *frame)
|
||||
{
|
||||
bool eof = false;
|
||||
@@ -1008,7 +1023,11 @@ FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t&
|
||||
// Handle an "expected" EOF by using the last frame of our cache
|
||||
cache_at_eof_ = true;
|
||||
|
||||
return_frame = cached_frames_.last();
|
||||
if (cached_frames_.isEmpty()) {
|
||||
qCritical() << "Unexpected codec EOF - unable to retrieve frame";
|
||||
} else {
|
||||
return_frame = cached_frames_.last();
|
||||
}
|
||||
|
||||
cache_wait_cond_.wakeAll();
|
||||
cache_lock_.unlock();
|
||||
@@ -1113,14 +1132,6 @@ void FFmpegDecoder::FreeScaler()
|
||||
}
|
||||
}
|
||||
|
||||
QString FFmpegDecoder::GetProxyFrameFilename(const int64_t ×tamp, const int& divider) const
|
||||
{
|
||||
QString dst_fn = GetProxyFilename(divider);
|
||||
dst_fn.append(QString::number(timestamp));
|
||||
dst_fn.append(FrameHashCache::GetFormatExtension());
|
||||
return dst_fn;
|
||||
}
|
||||
|
||||
int64_t FFmpegDecoderInstance::RangeStart() const
|
||||
{
|
||||
if (cached_frames_.isEmpty()) {
|
||||
@@ -1213,16 +1224,6 @@ void FFmpegDecoderInstance::TruncateCacheRangeTo(const qint64 &t)
|
||||
}
|
||||
}
|
||||
|
||||
rational FFmpegDecoderInstance::sample_aspect_ratio() const
|
||||
{
|
||||
return av_guess_sample_aspect_ratio(fmt_ctx_, avstream_, nullptr);
|
||||
}
|
||||
|
||||
AVStream *FFmpegDecoderInstance::stream() const
|
||||
{
|
||||
return avstream_;
|
||||
}
|
||||
|
||||
FFmpegDecoderInstance::FFmpegDecoderInstance(const char *filename, int stream_index) :
|
||||
fmt_ctx_(nullptr),
|
||||
opts_(nullptr),
|
||||
@@ -1306,8 +1307,8 @@ FFmpegDecoderInstance::FFmpegDecoderInstance(const char *filename, int stream_in
|
||||
// Start clear timer
|
||||
clear_timer_ = new QTimer();
|
||||
clear_timer_->setInterval(kMaxFrameLife);
|
||||
//clear_timer_->moveToThread(qApp->thread());
|
||||
connect(clear_timer_, &QTimer::timeout, this, &FFmpegDecoderInstance::ClearTimerEvent);
|
||||
clear_timer_->moveToThread(qApp->thread());
|
||||
connect(clear_timer_, &QTimer::timeout, this, &FFmpegDecoderInstance::ClearTimerEvent, Qt::DirectConnection);
|
||||
QMetaObject::invokeMethod(clear_timer_, "start", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
@@ -1336,16 +1337,9 @@ void FFmpegDecoderInstance::ClearResources()
|
||||
|
||||
// Stop timer
|
||||
if (clear_timer_) {
|
||||
|
||||
if (clear_timer_->thread() == QThread::currentThread()) {
|
||||
clear_timer_->stop();
|
||||
} else {
|
||||
QMetaObject::invokeMethod(clear_timer_, "stop", Qt::BlockingQueuedConnection);
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(clear_timer_, "deleteLater", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(clear_timer_, "stop", Qt::QueuedConnection);
|
||||
clear_timer_->deleteLater();
|
||||
clear_timer_ = nullptr;
|
||||
|
||||
}
|
||||
|
||||
if (opts_) {
|
||||
|
||||
@@ -64,8 +64,15 @@ public:
|
||||
void RemoveFramesBefore(const qint64& t);
|
||||
void TruncateCacheRangeTo(const qint64& t);
|
||||
|
||||
rational sample_aspect_ratio() const;
|
||||
AVStream* stream() const;
|
||||
AVFormatContext* fmt_ctx() const
|
||||
{
|
||||
return fmt_ctx_;
|
||||
}
|
||||
|
||||
AVStream* stream() const
|
||||
{
|
||||
return avstream_;
|
||||
}
|
||||
|
||||
void ClearFrameCache();
|
||||
|
||||
@@ -165,23 +172,19 @@ private:
|
||||
*/
|
||||
void FFmpegError(int error_code);
|
||||
|
||||
virtual QString GetIndexFilename() const override;
|
||||
|
||||
QString GetProxyFilename(int divider) const;
|
||||
|
||||
void ClearResources();
|
||||
|
||||
void InitScaler(int divider);
|
||||
void FreeScaler();
|
||||
|
||||
QString GetProxyFrameFilename(const int64_t& timestamp, const int ÷r) const;
|
||||
|
||||
static int GetScaledDimension(int dim, int divider);
|
||||
|
||||
static PixelFormat::Format GetNativePixelFormat(AVPixelFormat pix_fmt);
|
||||
|
||||
static uint64_t ValidateChannelLayout(AVStream *stream);
|
||||
|
||||
FramePtr BuffersToNativeFrame(int divider, int width, int height, int64_t ts, uint8_t **input_data, int* input_linesize);
|
||||
|
||||
SwsContext* scale_ctx_;
|
||||
int scale_divider_;
|
||||
AVPixelFormat src_pix_fmt_;
|
||||
@@ -189,7 +192,6 @@ private:
|
||||
PixelFormat::Format native_pix_fmt_;
|
||||
|
||||
rational time_base_;
|
||||
rational aspect_ratio_;
|
||||
int64_t start_time_;
|
||||
|
||||
static QHash< Stream*, QList<FFmpegDecoderInstance*> > instance_map_;
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
|
||||
#include "ffmpegencoder.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/pixdesc.h>
|
||||
}
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include "ffmpegcommon.h"
|
||||
@@ -134,6 +138,17 @@ bool FFmpegEncoder::WriteFrame(FramePtr frame, rational time)
|
||||
encoded_frame->height = frame->height();
|
||||
encoded_frame->format = video_codec_ctx_->pix_fmt;
|
||||
|
||||
// Set interlacing
|
||||
if (frame->video_params().interlacing() != VideoParams::kInterlaceNone) {
|
||||
encoded_frame->interlaced_frame = 1;
|
||||
|
||||
if (frame->video_params().interlacing() == VideoParams::kInterlacedTopFirst) {
|
||||
encoded_frame->top_field_first = 1;
|
||||
} else {
|
||||
encoded_frame->top_field_first = 0;
|
||||
}
|
||||
}
|
||||
|
||||
error_code = av_frame_get_buffer(encoded_frame, 0);
|
||||
if (error_code < 0) {
|
||||
FFmpegError("Failed to create AVFrame buffer", error_code);
|
||||
@@ -439,11 +454,27 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV
|
||||
if (type == AVMEDIA_TYPE_VIDEO) {
|
||||
codec_ctx->width = params().video_params().width();
|
||||
codec_ctx->height = params().video_params().height();
|
||||
codec_ctx->sample_aspect_ratio = {1, 1};
|
||||
codec_ctx->sample_aspect_ratio = params().video_params().pixel_aspect_ratio().toAVRational();
|
||||
codec_ctx->time_base = params().video_params().time_base().toAVRational();
|
||||
codec_ctx->pix_fmt = av_get_pix_fmt(params().video_pix_fmt().toUtf8());
|
||||
|
||||
// FIXME: Make this customizable again
|
||||
codec_ctx->pix_fmt = encoder->pix_fmts[0];
|
||||
if (params().video_params().interlacing() != VideoParams::kInterlaceNone) {
|
||||
// FIXME: I actually don't know what these flags do, the documentation helpfully doesn't
|
||||
// explain them at all. I hope using both of them is the right thing to do.
|
||||
codec_ctx->flags |= AV_CODEC_FLAG_INTERLACED_DCT | AV_CODEC_FLAG_INTERLACED_ME;
|
||||
|
||||
|
||||
if (params().video_params().interlacing() == VideoParams::kInterlacedTopFirst) {
|
||||
codec_ctx->field_order = AV_FIELD_TT;
|
||||
} else {
|
||||
codec_ctx->field_order = AV_FIELD_BB;
|
||||
|
||||
if (codec_id == AV_CODEC_ID_H264) {
|
||||
// For some reason, FFmpeg doesn't set libx264's bff flag so we have to do it ourselves
|
||||
av_opt_set(video_codec_ctx_->priv_data, "x264opts", "bff=1", AV_OPT_SEARCH_CHILDREN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set custom options
|
||||
{
|
||||
|
||||
+1
-12
@@ -27,8 +27,7 @@
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
Frame::Frame() :
|
||||
timestamp_(0),
|
||||
sample_aspect_ratio_(1)
|
||||
timestamp_(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -106,16 +105,6 @@ void Frame::set_pixel(int x, int y, const Color &c)
|
||||
c.toData(data_.data() + byte_offset, video_params().format());
|
||||
}
|
||||
|
||||
const rational &Frame::sample_aspect_ratio() const
|
||||
{
|
||||
return sample_aspect_ratio_;
|
||||
}
|
||||
|
||||
void Frame::set_sample_aspect_ratio(const rational &aspect_ratio)
|
||||
{
|
||||
sample_aspect_ratio_ = aspect_ratio;
|
||||
}
|
||||
|
||||
const rational &Frame::timestamp() const
|
||||
{
|
||||
return timestamp_;
|
||||
|
||||
@@ -57,9 +57,6 @@ public:
|
||||
bool contains_pixel(int x, int y) const;
|
||||
void set_pixel(int x, int y, const Color& c);
|
||||
|
||||
const rational& sample_aspect_ratio() const;
|
||||
void set_sample_aspect_ratio(const rational& sample_aspect_ratio);
|
||||
|
||||
/**
|
||||
* @brief Get frame's timestamp.
|
||||
*
|
||||
@@ -116,8 +113,6 @@ private:
|
||||
|
||||
int64_t native_timestamp_;
|
||||
|
||||
rational sample_aspect_ratio_;
|
||||
|
||||
int linesize_;
|
||||
|
||||
};
|
||||
|
||||
@@ -117,13 +117,15 @@ bool OIIODecoder::Probe(Footage *f, const QAtomicInt *cancelled)
|
||||
|
||||
image_stream->set_width(in->spec().width);
|
||||
image_stream->set_height(in->spec().height);
|
||||
image_stream->set_format(GetFormatFromOIIOBasetype(in->spec()));
|
||||
image_stream->set_pixel_aspect_ratio(GetPixelAspectRatioFromOIIO(in->spec()));
|
||||
|
||||
// Images will always have just one stream
|
||||
image_stream->set_index(0);
|
||||
|
||||
// OIIO automatically premultiplies alpha
|
||||
// FIXME: We usually disassociate the alpha for the color management later, for 8-bit images this likely reduces the
|
||||
// fidelity?
|
||||
// FIXME: We usually disassociate the alpha for the color management later, for 8-bit images this
|
||||
// likely reduces the fidelity?
|
||||
image_stream->set_premultiplied_alpha(true);
|
||||
|
||||
// Get stats for this image and dump them into the Footage file
|
||||
@@ -177,6 +179,8 @@ FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider
|
||||
frame->set_video_params(VideoParams(buffer_->spec().width,
|
||||
buffer_->spec().height,
|
||||
pix_fmt_,
|
||||
GetPixelAspectRatioFromOIIO(buffer_->spec()),
|
||||
VideoParams::kInterlaceNone, // FIXME: Does OIIO deinterlace for us?
|
||||
divider));
|
||||
frame->allocate();
|
||||
|
||||
@@ -216,11 +220,6 @@ bool OIIODecoder::SupportsVideo()
|
||||
return true;
|
||||
}
|
||||
|
||||
QString OIIODecoder::GetIndexFilename() const
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
void OIIODecoder::FrameToBuffer(FramePtr frame, OIIO::ImageBuf *buf)
|
||||
{
|
||||
#if OIIO_VERSION < 20112
|
||||
@@ -278,6 +277,28 @@ void OIIODecoder::BufferToFrame(OIIO::ImageBuf *buf, FramePtr frame)
|
||||
#endif
|
||||
}
|
||||
|
||||
PixelFormat::Format OIIODecoder::GetFormatFromOIIOBasetype(const OIIO::ImageSpec& spec)
|
||||
{
|
||||
bool has_alpha = (spec.nchannels == kRGBAChannels);
|
||||
|
||||
if (spec.format == OIIO::TypeDesc::UINT8) {
|
||||
return has_alpha ? PixelFormat::PIX_FMT_RGBA8 : PixelFormat::PIX_FMT_RGB8;
|
||||
} else if (spec.format == OIIO::TypeDesc::UINT16) {
|
||||
return has_alpha ? PixelFormat::PIX_FMT_RGBA16U : PixelFormat::PIX_FMT_RGB16U;
|
||||
} else if (spec.format == OIIO::TypeDesc::HALF) {
|
||||
return has_alpha ? PixelFormat::PIX_FMT_RGBA16F : PixelFormat::PIX_FMT_RGB16F;
|
||||
} else if (spec.format == OIIO::TypeDesc::FLOAT) {
|
||||
return has_alpha ? PixelFormat::PIX_FMT_RGBA32F : PixelFormat::PIX_FMT_RGB32F;
|
||||
} else {
|
||||
return PixelFormat::PIX_FMT_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
rational OIIODecoder::GetPixelAspectRatioFromOIIO(const OIIO::ImageSpec &spec)
|
||||
{
|
||||
return rational::fromDouble(spec.extra_attribs.get_float("PixelAspectRatio", 1));
|
||||
}
|
||||
|
||||
bool OIIODecoder::FileTypeIsSupported(const QString& fn)
|
||||
{
|
||||
// We prioritize OIIO over FFmpeg to pick up still images more effectively, but some OIIO decoders (notably OpenJPEG)
|
||||
@@ -361,16 +382,9 @@ bool OIIODecoder::OpenImageHandler(const QString &fn)
|
||||
|
||||
is_rgba_ = (spec.nchannels == kRGBAChannels);
|
||||
|
||||
// Weirdly, switch statement doesn't work correctly here
|
||||
if (spec.format == OIIO::TypeDesc::UINT8) {
|
||||
pix_fmt_ = is_rgba_ ? PixelFormat::PIX_FMT_RGBA8 : PixelFormat::PIX_FMT_RGB8;
|
||||
} else if (spec.format == OIIO::TypeDesc::UINT16) {
|
||||
pix_fmt_ = is_rgba_ ? PixelFormat::PIX_FMT_RGBA16U : PixelFormat::PIX_FMT_RGB16U;
|
||||
} else if (spec.format == OIIO::TypeDesc::HALF) {
|
||||
pix_fmt_ = is_rgba_ ? PixelFormat::PIX_FMT_RGBA16F : PixelFormat::PIX_FMT_RGB16F;
|
||||
} else if (spec.format == OIIO::TypeDesc::FLOAT) {
|
||||
pix_fmt_ = is_rgba_ ? PixelFormat::PIX_FMT_RGBA32F : PixelFormat::PIX_FMT_RGB32F;
|
||||
} else {
|
||||
pix_fmt_ = GetFormatFromOIIOBasetype(spec);
|
||||
|
||||
if (pix_fmt_ == PixelFormat::PIX_FMT_INVALID) {
|
||||
qWarning() << "Failed to convert OIIO::ImageDesc to native pixel format";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -51,12 +51,14 @@ public:
|
||||
|
||||
virtual bool SupportsVideo() override;
|
||||
|
||||
virtual QString GetIndexFilename() const override;
|
||||
|
||||
static void FrameToBuffer(FramePtr frame, OIIO::ImageBuf* buf);
|
||||
|
||||
static void BufferToFrame(OIIO::ImageBuf* buf, FramePtr frame);
|
||||
|
||||
static PixelFormat::Format GetFormatFromOIIOBasetype(const OIIO::ImageSpec& spec);
|
||||
|
||||
static rational GetPixelAspectRatioFromOIIO(const OIIO::ImageSpec& spec);
|
||||
|
||||
private:
|
||||
#if OIIO_VERSION < 10903
|
||||
OIIO::ImageInput* image_;
|
||||
|
||||
@@ -23,6 +23,8 @@ set(OLIVE_SOURCES
|
||||
common/clamp.h
|
||||
common/crashhandler.h
|
||||
common/crashhandler.cpp
|
||||
common/crashpadinterface.cpp
|
||||
common/crashpadinterface.h
|
||||
common/debug.h
|
||||
common/debug.cpp
|
||||
common/define.h
|
||||
@@ -36,6 +38,8 @@ set(OLIVE_SOURCES
|
||||
common/qtutils.h
|
||||
common/qtutils.cpp
|
||||
common/range.h
|
||||
common/ratiodialog.h
|
||||
common/ratiodialog.cpp
|
||||
common/rational.h
|
||||
common/rational.cpp
|
||||
common/threadedobject.h
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "crashpadinterface.h"
|
||||
|
||||
#ifdef USE_CRASHPAD
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
|
||||
#include "filefunctions.h"
|
||||
|
||||
// Copied from base::FilePath to match its macro
|
||||
#if defined(OS_POSIX)
|
||||
// On most platforms, native pathnames are char arrays, and the encoding
|
||||
// may or may not be specified. On Mac OS X, native pathnames are encoded
|
||||
// in UTF-8.
|
||||
#define TO_BASE_STRING_TYPE(x) x.toStdString()
|
||||
#elif defined(OS_WIN)
|
||||
// On Windows, for Unicode-aware applications, native pathnames are wchar_t
|
||||
// arrays encoded in UTF-16.
|
||||
#define TO_BASE_STRING_TYPE(x) x.toStdWString()
|
||||
#endif // OS_WIN
|
||||
|
||||
bool InitializeCrashpad()
|
||||
{
|
||||
QString exe_dir = QCoreApplication::applicationDirPath();
|
||||
|
||||
// FIXME: On Linux, probably should put this in a subdir so that it doesn't conflict with
|
||||
// anything else in /usr/bin
|
||||
|
||||
base::FilePath handler(TO_BASE_STRING_TYPE(QDir(exe_dir).filePath(QStringLiteral("crashpad_handler.exe"))));
|
||||
|
||||
base::FilePath reports_dir(TO_BASE_STRING_TYPE(QDir(OLIVE_NAMESPACE::FileFunctions::GetTempFilePath()).filePath("reports")));
|
||||
|
||||
base::FilePath metrics_dir(TO_BASE_STRING_TYPE(QDir(OLIVE_NAMESPACE::FileFunctions::GetTempFilePath()).filePath("metrics")));
|
||||
|
||||
std::string url = "https://olivevideoeditor.org/crashpad/report.php";
|
||||
|
||||
// Metadata that will be posted to the server with the crash report map
|
||||
std::map<std::string, std::string> annotations;
|
||||
|
||||
// Disable crashpad rate limiting so that all crashes have dmp files
|
||||
std::vector<std::string> arguments;
|
||||
arguments.push_back("--no-rate-limit");
|
||||
arguments.push_back("--no-upload-gzip");
|
||||
|
||||
// Initialize Crashpad database
|
||||
std::unique_ptr<crashpad::CrashReportDatabase> database = crashpad::CrashReportDatabase::Initialize(reports_dir);
|
||||
if (database == NULL) return false;
|
||||
|
||||
// Enable automated crash uploads
|
||||
crashpad::Settings *settings = database->GetSettings();
|
||||
if (settings == NULL) return false;
|
||||
settings->SetUploadsEnabled(true);
|
||||
|
||||
// Start crash handler
|
||||
crashpad::CrashpadClient *client = new crashpad::CrashpadClient();
|
||||
bool status = client->StartHandler(handler, reports_dir, metrics_dir,
|
||||
url, annotations, arguments, true, true);
|
||||
return status;
|
||||
}
|
||||
|
||||
#endif // USE_CRASHPAD
|
||||
@@ -0,0 +1,34 @@
|
||||
/***
|
||||
|
||||
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 CRASHPAD_INTERFACE_H
|
||||
#define CRASHPAD_INTERFACE_H
|
||||
|
||||
#ifdef USE_CRASHPAD
|
||||
|
||||
#include <client/crashpad_client.h>
|
||||
#include <client/crash_report_database.h>
|
||||
#include <client/settings.h>
|
||||
|
||||
bool InitializeCrashpad();
|
||||
|
||||
#endif // USE_CRASHPAD
|
||||
|
||||
#endif // CRASHPAD_INTERFACE_H
|
||||
@@ -42,6 +42,8 @@ const int kProjectIconSizeMaximum = 256;
|
||||
/// The default size an icon in ProjectExplorer can be
|
||||
const int kProjectIconSizeDefault = 64;
|
||||
|
||||
const int kBytesInGigabyte = 1073741824;
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#define MACRO_NAME_AS_STR(s) #s
|
||||
|
||||
@@ -50,35 +50,6 @@ QString FileFunctions::GetUniqueFileIdentifier(const QString &filename)
|
||||
return QString(result.toHex());
|
||||
}
|
||||
|
||||
QString FileFunctions::GetMediaIndexLocation()
|
||||
{
|
||||
QDir local_appdata_dir(Config::Current()["DiskCachePath"].toString());
|
||||
|
||||
QDir media_index_dir = local_appdata_dir.filePath("mediaindex");
|
||||
|
||||
// Attempt to ensure this folder exists
|
||||
media_index_dir.mkpath(".");
|
||||
|
||||
return media_index_dir.absolutePath();
|
||||
}
|
||||
|
||||
QString FileFunctions::GetMediaIndexFilename(const QString &filename)
|
||||
{
|
||||
return QDir(GetMediaIndexLocation()).filePath(filename);
|
||||
}
|
||||
|
||||
QString FileFunctions::GetMediaCacheLocation()
|
||||
{
|
||||
QDir local_appdata_dir(Config::Current()["DiskCachePath"].toString());
|
||||
|
||||
QDir media_cache_dir = local_appdata_dir.filePath("mediacache");
|
||||
|
||||
// Attempt to ensure this folder exists
|
||||
media_cache_dir.mkpath(".");
|
||||
|
||||
return media_cache_dir.absolutePath();
|
||||
}
|
||||
|
||||
QString FileFunctions::GetConfigurationLocation()
|
||||
{
|
||||
if (IsPortable()) {
|
||||
@@ -112,6 +83,30 @@ QString FileFunctions::GetTempFilePath()
|
||||
return temp_path;
|
||||
}
|
||||
|
||||
bool FileFunctions::CanCopyDirectoryWithoutOverwriting(const QString& source, const QString& dest)
|
||||
{
|
||||
QFileInfoList info_list = QDir(source).entryInfoList();
|
||||
|
||||
foreach (const QFileInfo& info, info_list) {
|
||||
// QDir::NoDotAndDotDot continues to not work, so we have to check manually
|
||||
if (info.fileName() == QStringLiteral(".") || info.fileName() == QStringLiteral("..")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString dest_equivalent = QDir(dest).filePath(info.fileName());
|
||||
|
||||
if (info.isDir()) {
|
||||
if (!CanCopyDirectoryWithoutOverwriting(info.absoluteFilePath(), dest_equivalent)) {
|
||||
return false;
|
||||
}
|
||||
} else if (QFileInfo::exists(dest_equivalent)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileFunctions::CopyDirectory(const QString &source, const QString &dest, bool overwrite)
|
||||
{
|
||||
QDir d(source);
|
||||
@@ -152,4 +147,27 @@ void FileFunctions::CopyDirectory(const QString &source, const QString &dest, bo
|
||||
}
|
||||
}
|
||||
|
||||
bool FileFunctions::DirectoryIsValid(const QString &dir, bool try_to_create)
|
||||
{
|
||||
// Empty string is invalid
|
||||
if (dir.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir d(dir);
|
||||
|
||||
// If directory already exists, this is valid
|
||||
if (d.exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we can create and creation is successful, this is valid
|
||||
if (try_to_create && d.mkpath(".")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, invalid
|
||||
return false;
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -42,20 +42,18 @@ public:
|
||||
|
||||
static QString GetUniqueFileIdentifier(const QString& filename);
|
||||
|
||||
static QString GetMediaIndexLocation();
|
||||
|
||||
static QString GetMediaIndexFilename(const QString& filename);
|
||||
|
||||
static QString GetMediaCacheLocation();
|
||||
|
||||
static QString GetConfigurationLocation();
|
||||
|
||||
static QString GetApplicationPath();
|
||||
|
||||
static QString GetTempFilePath();
|
||||
|
||||
static bool CanCopyDirectoryWithoutOverwriting(const QString& source, const QString& dest);
|
||||
|
||||
static void CopyDirectory(const QString& source, const QString& dest, bool overwrite = false);
|
||||
|
||||
static bool DirectoryIsValid(const QString& dir, bool try_to_create);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "ratiodialog.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QMessageBox>
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
double GetFloatRatioFromUser(QWidget* parent,
|
||||
const QString& title,
|
||||
bool* ok_in)
|
||||
{
|
||||
QString s;
|
||||
|
||||
forever {
|
||||
bool ok;
|
||||
|
||||
s = QInputDialog::getText(parent,
|
||||
title,
|
||||
QCoreApplication::translate("RatioDialog", "Enter custom ratio (e.g. \"4:3\", \"16/9\", etc.):"),
|
||||
QLineEdit::Normal,
|
||||
s,
|
||||
&ok);
|
||||
|
||||
if (!ok) {
|
||||
// User cancelled dialog, do nothing
|
||||
if (ok_in) {
|
||||
*ok_in = false;
|
||||
}
|
||||
return qSNaN();
|
||||
}
|
||||
|
||||
QStringList ratio_components = s.split(QRegExp(QStringLiteral(":|;|\\/")));
|
||||
|
||||
if (ratio_components.size() == 1) {
|
||||
bool float_ok;
|
||||
|
||||
double flt = ratio_components.at(0).toDouble(&float_ok);
|
||||
|
||||
if (float_ok && flt > 0) {
|
||||
if (ok_in) {
|
||||
*ok_in = true;
|
||||
}
|
||||
return flt;
|
||||
}
|
||||
} else if (ratio_components.size() == 2) {
|
||||
bool numer_ok, denom_ok;
|
||||
|
||||
double num = ratio_components.at(0).toDouble(&numer_ok);
|
||||
double den = ratio_components.at(1).toDouble(&denom_ok);
|
||||
|
||||
if (numer_ok
|
||||
&& denom_ok
|
||||
&& num > 0
|
||||
&& den > 0) {
|
||||
// Exit loop and set this ratio
|
||||
if (ok_in) {
|
||||
*ok_in = true;
|
||||
}
|
||||
return num / den;
|
||||
}
|
||||
}
|
||||
|
||||
QMessageBox::warning(parent,
|
||||
QCoreApplication::translate("RatioDialog", "Invalid custom ratio"),
|
||||
QCoreApplication::translate("RatioDialog", "Failed to parse \"%1\" into an aspect ratio. Please format a "
|
||||
"rational fraction with a ':' or a '/' separator.").arg(s),
|
||||
QMessageBox::Ok);
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -0,0 +1,36 @@
|
||||
/***
|
||||
|
||||
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 RATIODIALOG_H
|
||||
#define RATIODIALOG_H
|
||||
|
||||
#include <QInputDialog>
|
||||
|
||||
#include "common/rational.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
double GetFloatRatioFromUser(QWidget* parent,
|
||||
const QString& title,
|
||||
bool* ok_in);
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // RATIODIALOG_H
|
||||
@@ -64,6 +64,9 @@ struct XMLNodeData {
|
||||
QList<BlockLink> block_links;
|
||||
QHash<quintptr, Item*> item_ptrs;
|
||||
|
||||
QString real_project_url;
|
||||
QString saved_project_url;
|
||||
|
||||
};
|
||||
|
||||
void XMLConnectNodes(const XMLNodeData& xml_node_data, QUndoCommand* command = nullptr);
|
||||
|
||||
+71
-59
@@ -31,6 +31,7 @@
|
||||
#include "common/filefunctions.h"
|
||||
#include "common/xmlutils.h"
|
||||
#include "core.h"
|
||||
#include "ui/style/style.h"
|
||||
#include "window/mainwindow/mainwindow.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
@@ -42,6 +43,11 @@ Config::Config()
|
||||
SetDefaults();
|
||||
}
|
||||
|
||||
void Config::SetEntryInternal(const QString &key, NodeParam::DataType type, const QVariant &data)
|
||||
{
|
||||
config_map_[key] = {type, data};
|
||||
}
|
||||
|
||||
QString Config::GetConfigFilePath()
|
||||
{
|
||||
return QDir(FileFunctions::GetConfigurationLocation()).filePath(QStringLiteral("config.xml"));
|
||||
@@ -55,66 +61,67 @@ Config &Config::Current()
|
||||
void Config::SetDefaults()
|
||||
{
|
||||
config_map_.clear();
|
||||
config_map_["TimecodeDisplay"] = Timecode::kTimecodeDropFrame;
|
||||
config_map_["DefaultStillLength"] = QVariant::fromValue(rational(2));
|
||||
config_map_["HoverFocus"] = false;
|
||||
config_map_["AudioScrubbing"] = true;
|
||||
config_map_["AutorecoveryInterval"] = 1;
|
||||
config_map_["Language"] = "en_US";
|
||||
config_map_["ScrollZooms"] = false;
|
||||
config_map_["EnableSeekToImport"] = false;
|
||||
config_map_["EditToolAlsoSeeks"] = false;
|
||||
config_map_["EditToolSelectsLinks"] = false;
|
||||
config_map_["EnableDragFilesToTimeline"] = true;
|
||||
config_map_["InvertTimelineScrollAxes"] = true;
|
||||
config_map_["SelectAlsoSeeks"] = false;
|
||||
config_map_["PasteSeeks"] = true;
|
||||
config_map_["SelectAlsoSeeks"] = false;
|
||||
config_map_["SetNameWithMarker"] = false;
|
||||
config_map_["AutoSeekToBeginning"] = true;
|
||||
config_map_["DropFileOnMediaToReplace"] = false;
|
||||
config_map_["AddDefaultEffectsToClips"] = true;
|
||||
config_map_["AutoscaleByDefault"] = false;
|
||||
config_map_["Autoscroll"] = AutoScroll::kPage;
|
||||
config_map_["AutoSelectDivider"] = true;
|
||||
config_map_["SetNameWithMarker"] = false;
|
||||
config_map_["RectifiedWaveforms"] = false;
|
||||
config_map_["DropWithoutSequenceBehavior"] = TimelineWidget::kDWSAsk;
|
||||
config_map_["Loop"] = false;
|
||||
SetEntryInternal(QStringLiteral("Style"), NodeParam::kString, StyleManager::kDefaultStyle);
|
||||
SetEntryInternal(QStringLiteral("TimecodeDisplay"), NodeParam::kInt, Timecode::kTimecodeDropFrame);
|
||||
SetEntryInternal(QStringLiteral("DefaultStillLength"), NodeParam::kRational, QVariant::fromValue(rational(2)));
|
||||
SetEntryInternal(QStringLiteral("HoverFocus"), NodeParam::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("AudioScrubbing"), NodeParam::kBoolean, true);
|
||||
SetEntryInternal(QStringLiteral("AutorecoveryInterval"), NodeParam::kInt, 1);
|
||||
SetEntryInternal(QStringLiteral("DiskCacheSaveInterval"), NodeParam::kInt, 10000);
|
||||
SetEntryInternal(QStringLiteral("Language"), NodeParam::kString, QLocale::system().name());
|
||||
SetEntryInternal(QStringLiteral("ScrollZooms"), NodeParam::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("EnableSeekToImport"), NodeParam::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("EditToolAlsoSeeks"), NodeParam::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("EditToolSelectsLinks"), NodeParam::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("EnableDragFilesToTimeline"), NodeParam::kBoolean, true);
|
||||
SetEntryInternal(QStringLiteral("InvertTimelineScrollAxes"), NodeParam::kBoolean, true);
|
||||
SetEntryInternal(QStringLiteral("SelectAlsoSeeks"), NodeParam::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("PasteSeeks"), NodeParam::kBoolean, true);
|
||||
SetEntryInternal(QStringLiteral("SelectAlsoSeeks"), NodeParam::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("SetNameWithMarker"), NodeParam::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("AutoSeekToBeginning"), NodeParam::kBoolean, true);
|
||||
SetEntryInternal(QStringLiteral("DropFileOnMediaToReplace"), NodeParam::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("AddDefaultEffectsToClips"), NodeParam::kBoolean, true);
|
||||
SetEntryInternal(QStringLiteral("AutoscaleByDefault"), NodeParam::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("Autoscroll"), NodeParam::kInt, AutoScroll::kPage);
|
||||
SetEntryInternal(QStringLiteral("AutoSelectDivider"), NodeParam::kBoolean, true);
|
||||
SetEntryInternal(QStringLiteral("SetNameWithMarker"), NodeParam::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("RectifiedWaveforms"), NodeParam::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("DropWithoutSequenceBehavior"), NodeParam::kInt, TimelineWidget::kDWSAsk);
|
||||
SetEntryInternal(QStringLiteral("Loop"), NodeParam::kBoolean, false);
|
||||
|
||||
config_map_["AutoCacheInterval"] = 250;
|
||||
SetEntryInternal(QStringLiteral("AutoCacheInterval"), NodeParam::kInt, 250);
|
||||
|
||||
config_map_["NodeCatColor0"] = QVariant::fromValue(Color(0.75f, 0.75f, 0.75f));
|
||||
config_map_["NodeCatColor1"] = QVariant::fromValue(Color(0.25f, 0.25f, 0.25f));
|
||||
config_map_["NodeCatColor2"] = QVariant::fromValue(Color(0.75f, 0.75f, 0.25f));
|
||||
config_map_["NodeCatColor3"] = QVariant::fromValue(Color(0.75f, 0.25f, 0.75f));
|
||||
config_map_["NodeCatColor4"] = QVariant::fromValue(Color(0.25f, 0.75f, 0.75f));
|
||||
config_map_["NodeCatColor5"] = QVariant::fromValue(Color(0.50f, 0.50f, 0.50f));
|
||||
config_map_["NodeCatColor6"] = QVariant::fromValue(Color(0.25f, 0.75f, 0.25f));
|
||||
config_map_["NodeCatColor7"] = QVariant::fromValue(Color(0.25f, 0.25f, 0.75f));
|
||||
config_map_["NodeCatColor8"] = QVariant::fromValue(Color(0.75f, 0.25f, 0.25f));
|
||||
SetEntryInternal(QStringLiteral("NodeCatColor0"), NodeParam::kColor, QVariant::fromValue(Color(0.75f, 0.75f, 0.75f)));
|
||||
SetEntryInternal(QStringLiteral("NodeCatColor1"), NodeParam::kColor, QVariant::fromValue(Color(0.25f, 0.25f, 0.25f)));
|
||||
SetEntryInternal(QStringLiteral("NodeCatColor2"), NodeParam::kColor, QVariant::fromValue(Color(0.75f, 0.75f, 0.25f)));
|
||||
SetEntryInternal(QStringLiteral("NodeCatColor3"), NodeParam::kColor, QVariant::fromValue(Color(0.75f, 0.25f, 0.75f)));
|
||||
SetEntryInternal(QStringLiteral("NodeCatColor4"), NodeParam::kColor, QVariant::fromValue(Color(0.25f, 0.75f, 0.75f)));
|
||||
SetEntryInternal(QStringLiteral("NodeCatColor5"), NodeParam::kColor, QVariant::fromValue(Color(0.50f, 0.50f, 0.50f)));
|
||||
SetEntryInternal(QStringLiteral("NodeCatColor6"), NodeParam::kColor, QVariant::fromValue(Color(0.25f, 0.75f, 0.25f)));
|
||||
SetEntryInternal(QStringLiteral("NodeCatColor7"), NodeParam::kColor, QVariant::fromValue(Color(0.25f, 0.25f, 0.75f)));
|
||||
SetEntryInternal(QStringLiteral("NodeCatColor8"), NodeParam::kColor, QVariant::fromValue(Color(0.75f, 0.25f, 0.25f)));
|
||||
|
||||
config_map_["AudioOutput"] = QString();
|
||||
config_map_["AudioInput"] = QString();
|
||||
SetEntryInternal(QStringLiteral("AudioOutput"), NodeParam::kString, QString());
|
||||
SetEntryInternal(QStringLiteral("AudioInput"), NodeParam::kString, QString());
|
||||
|
||||
config_map_["DiskCachePath"] = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
|
||||
config_map_["DiskCacheSize"] = 20.0;
|
||||
config_map_["DiskCacheBehind"] = QVariant::fromValue(rational(1));
|
||||
config_map_["DiskCacheAhead"] = QVariant::fromValue(rational(5));
|
||||
config_map_["ClearDiskCacheOnClose"] = false;
|
||||
SetEntryInternal(QStringLiteral("DiskCacheBehind"), NodeParam::kRational, QVariant::fromValue(rational(1)));
|
||||
SetEntryInternal(QStringLiteral("DiskCacheAhead"), NodeParam::kRational, QVariant::fromValue(rational(5)));
|
||||
|
||||
config_map_["DefaultSequenceWidth"] = 1920;
|
||||
config_map_["DefaultSequenceHeight"] = 1080;
|
||||
config_map_["DefaultSequenceFrameRate"] = QVariant::fromValue(rational(1001, 30000));
|
||||
config_map_["DefaultSequenceAudioFrequency"] = 48000;
|
||||
config_map_["DefaultSequenceAudioLayout"] = QVariant::fromValue(static_cast<uint64_t>(AV_CH_LAYOUT_STEREO));
|
||||
config_map_["DefaultSequencePreviewFormat"] = PixelFormat::PIX_FMT_RGBA16F;
|
||||
SetEntryInternal(QStringLiteral("DefaultSequenceWidth"), NodeParam::kInt, 1920);
|
||||
SetEntryInternal(QStringLiteral("DefaultSequenceHeight"), NodeParam::kInt, 1080);
|
||||
SetEntryInternal(QStringLiteral("DefaultSequencePixelAspect"), NodeParam::kRational, QVariant::fromValue(rational(1)));
|
||||
SetEntryInternal(QStringLiteral("DefaultSequenceFrameRate"), NodeParam::kRational, QVariant::fromValue(rational(1001, 30000)));
|
||||
SetEntryInternal(QStringLiteral("DefaultSequenceInterlacing"), NodeParam::kInt, VideoParams::kInterlaceNone);
|
||||
SetEntryInternal(QStringLiteral("DefaultSequenceAudioFrequency"), NodeParam::kInt, 48000);
|
||||
SetEntryInternal(QStringLiteral("DefaultSequenceAudioLayout"), NodeParam::kInt, QVariant::fromValue(static_cast<int64_t>(AV_CH_LAYOUT_STEREO)));
|
||||
SetEntryInternal(QStringLiteral("DefaultSequencePreviewFormat"), NodeParam::kInt, PixelFormat::PIX_FMT_RGBA16F);
|
||||
|
||||
// Online/offline settings
|
||||
config_map_["OnlinePixelFormat"] = PixelFormat::PIX_FMT_RGBA32F;
|
||||
config_map_["OfflinePixelFormat"] = PixelFormat::PIX_FMT_RGBA16F;
|
||||
config_map_["OnlineOCIOMethod"] = ColorManager::kOCIOAccurate;
|
||||
config_map_["OfflineOCIOMethod"] = ColorManager::kOCIOFast;
|
||||
SetEntryInternal(QStringLiteral("OnlinePixelFormat"), NodeParam::kInt, PixelFormat::PIX_FMT_RGBA32F);
|
||||
SetEntryInternal(QStringLiteral("OfflinePixelFormat"), NodeParam::kInt, PixelFormat::PIX_FMT_RGBA16F);
|
||||
SetEntryInternal(QStringLiteral("OnlineOCIOMethod"), NodeParam::kInt, ColorManager::kOCIOAccurate);
|
||||
SetEntryInternal(QStringLiteral("OfflineOCIOMethod"), NodeParam::kInt, ColorManager::kOCIOFast);
|
||||
}
|
||||
|
||||
void Config::Load()
|
||||
@@ -156,7 +163,7 @@ void Config::Load()
|
||||
|
||||
double config_fr = value.toDouble();
|
||||
|
||||
QList<rational> supported_frame_rates = Core::SupportedFrameRates();
|
||||
const QVector<rational>& supported_frame_rates = VideoParams::kSupportedFrameRates;
|
||||
|
||||
rational match = supported_frame_rates.first();
|
||||
double match_diff = qAbs(match.toDouble() - config_fr);
|
||||
@@ -174,7 +181,7 @@ void Config::Load()
|
||||
|
||||
current_config_[key] = QVariant::fromValue(match.flipped());
|
||||
} else {
|
||||
current_config_[key] = value;
|
||||
current_config_[key] = NodeInput::StringToValue(current_config_.GetConfigEntryType(key), value, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,10 +226,10 @@ void Config::Save()
|
||||
// Anything after the hyphen is considered "unimportant" information
|
||||
writer.writeTextElement("Version", QCoreApplication::applicationVersion().split('-').first());
|
||||
|
||||
QMapIterator<QString, QVariant> iterator(current_config_.config_map_);
|
||||
QMapIterator<QString, ConfigEntry> iterator(current_config_.config_map_);
|
||||
while (iterator.hasNext()) {
|
||||
iterator.next();
|
||||
writer.writeTextElement(iterator.key(), iterator.value().toString());
|
||||
writer.writeTextElement(iterator.key(), NodeInput::ValueToString(iterator.value().type, iterator.value().data, false));
|
||||
}
|
||||
|
||||
writer.writeEndElement(); // Configuration
|
||||
@@ -234,12 +241,17 @@ void Config::Save()
|
||||
|
||||
QVariant Config::operator[](const QString &key) const
|
||||
{
|
||||
return config_map_[key];
|
||||
return config_map_[key].data;
|
||||
}
|
||||
|
||||
QVariant &Config::operator[](const QString &key)
|
||||
{
|
||||
return config_map_[key];
|
||||
return config_map_[key].data;
|
||||
}
|
||||
|
||||
NodeParam::DataType Config::GetConfigEntryType(const QString &key) const
|
||||
{
|
||||
return config_map_[key].type;
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
+11
-1
@@ -26,6 +26,7 @@
|
||||
#include <QVariant>
|
||||
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "node/param.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -43,10 +44,19 @@ public:
|
||||
|
||||
QVariant& operator[](const QString&);
|
||||
|
||||
NodeParam::DataType GetConfigEntryType(const QString& key) const;
|
||||
|
||||
private:
|
||||
Config();
|
||||
|
||||
QMap<QString, QVariant> config_map_;
|
||||
struct ConfigEntry {
|
||||
NodeParam::DataType type;
|
||||
QVariant data;
|
||||
};
|
||||
|
||||
void SetEntryInternal(const QString& key, NodeParam::DataType type, const QVariant& data);
|
||||
|
||||
QMap<QString, ConfigEntry> config_map_;
|
||||
|
||||
static Config current_config_;
|
||||
|
||||
|
||||
+222
-195
@@ -51,7 +51,6 @@
|
||||
#include "render/diskmanager.h"
|
||||
#include "render/pixelformat.h"
|
||||
#include "render/shaderinfo.h"
|
||||
#include "task/cache/cache.h"
|
||||
#include "task/project/import/import.h"
|
||||
#include "task/project/load/load.h"
|
||||
#include "task/project/save/save.h"
|
||||
@@ -81,21 +80,17 @@ Core *Core::instance()
|
||||
return &instance_;
|
||||
}
|
||||
|
||||
bool Core::Start()
|
||||
int Core::execute(QCoreApplication* a)
|
||||
{
|
||||
// Reset config (Config sets to default on construction already, but we do it again here as a workaround that fixes
|
||||
// the fact that some of the config paths set by default rely on the app name having been set (in main())
|
||||
Config::Current().SetDefaults();
|
||||
int exit_code = 1;
|
||||
|
||||
//
|
||||
// Parse command line arguments
|
||||
//
|
||||
|
||||
QCoreApplication* app = QCoreApplication::instance();
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
QCommandLineOption help_option = parser.addHelpOption();
|
||||
QCommandLineOption version_option = parser.addVersionOption();
|
||||
|
||||
// Project from command line option
|
||||
// FIXME: What's the correct way to make a visually "optional" positional argument, or is manually adding square
|
||||
@@ -111,7 +106,15 @@ bool Core::Start()
|
||||
parser.addOption(headless_export_option);
|
||||
|
||||
// Parse options
|
||||
parser.process(*app);
|
||||
parser.process(*a);
|
||||
|
||||
if (parser.isSet(help_option) || parser.isSet(version_option)) {
|
||||
// These options don't launch any of the application proper
|
||||
return a->exec();
|
||||
}
|
||||
|
||||
// Start core
|
||||
OLIVE_NAMESPACE::Core::instance()->Start();
|
||||
|
||||
QStringList args = parser.positionalArguments();
|
||||
|
||||
@@ -120,6 +123,67 @@ bool Core::Start()
|
||||
startup_project_ = args.first();
|
||||
}
|
||||
|
||||
gui_active_ = !parser.isSet(headless_export_option);
|
||||
|
||||
if (gui_active_) {
|
||||
|
||||
// Start GUI
|
||||
StartGUI(parser.isSet(fullscreen_option));
|
||||
|
||||
// If we have a startup
|
||||
QMetaObject::invokeMethod(this, "OpenStartupProject", Qt::QueuedConnection);
|
||||
|
||||
// Run application loop and receive exit code
|
||||
exit_code = a->exec();
|
||||
|
||||
} else {
|
||||
|
||||
if (parser.isSet(headless_export_option)) {
|
||||
// Start a headless export
|
||||
if (StartHeadlessExport()) {
|
||||
exit_code = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Clear core memory
|
||||
OLIVE_NAMESPACE::Core::instance()->Stop();
|
||||
|
||||
return exit_code;
|
||||
}
|
||||
|
||||
void Core::DeclareTypesForQt()
|
||||
{
|
||||
qRegisterMetaType<rational>();
|
||||
qRegisterMetaType<OpenGLTexturePtr>();
|
||||
qRegisterMetaType<OpenGLTextureCache::ReferencePtr>();
|
||||
qRegisterMetaType<NodeValue>();
|
||||
qRegisterMetaType<NodeValueTable>();
|
||||
qRegisterMetaType<NodeValueDatabase>();
|
||||
qRegisterMetaType<FramePtr>();
|
||||
qRegisterMetaType<SampleBufferPtr>();
|
||||
qRegisterMetaType<AudioParams>();
|
||||
qRegisterMetaType<NodeKeyframe::Type>();
|
||||
qRegisterMetaType<Decoder::RetrieveState>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::TimeRange>();
|
||||
qRegisterMetaType<Color>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::ProjectPtr>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::AudioVisualWaveform>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::SampleJob>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::ShaderJob>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::GenerateJob>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::VideoParams>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::VideoParams::Interlacing>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::MainWindowLayoutInfo>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::RenderTicketPtr>();
|
||||
}
|
||||
|
||||
void Core::Start()
|
||||
{
|
||||
// Load application config
|
||||
Config::Load();
|
||||
|
||||
// Declare custom types for Qt signal/slot system
|
||||
DeclareTypesForQt();
|
||||
|
||||
@@ -132,68 +196,20 @@ bool Core::Start()
|
||||
// Initialize task manager
|
||||
TaskManager::CreateInstance();
|
||||
|
||||
// Load application config
|
||||
Config::Load();
|
||||
|
||||
// Initialize OpenGL service
|
||||
OpenGLProxy::CreateInstance();
|
||||
|
||||
//
|
||||
// Start application
|
||||
//
|
||||
|
||||
qInfo() << "Using Qt version:" << qVersion();
|
||||
|
||||
gui_active_ = !parser.isSet(headless_export_option);
|
||||
|
||||
if (gui_active_) {
|
||||
|
||||
// Start GUI
|
||||
StartGUI(parser.isSet(fullscreen_option));
|
||||
|
||||
// Load startup project
|
||||
if (!startup_project_.isEmpty() && !QFileInfo::exists(startup_project_)) {
|
||||
QMessageBox::warning(main_window(),
|
||||
tr("Failed to open startup file"),
|
||||
tr("The project \"%1\" doesn't exist. A new project will be started instead.").arg(startup_project_),
|
||||
QMessageBox::Ok);
|
||||
|
||||
startup_project_.clear();
|
||||
}
|
||||
|
||||
if (startup_project_.isEmpty()) {
|
||||
// If no load project is set, create a new one on open
|
||||
CreateNewProject();
|
||||
} else {
|
||||
OpenProjectInternal(startup_project_);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
if (parser.isSet(headless_export_option)) {
|
||||
|
||||
if (startup_project_.isEmpty()) {
|
||||
qCritical().noquote() << tr("You must specify a project file to export");
|
||||
} else {
|
||||
OpenProjectInternal(startup_project_);
|
||||
|
||||
qDebug() << "Ready for exporting!";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Error fallback
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Stop()
|
||||
{
|
||||
// Save Config
|
||||
//Config::Save();
|
||||
Config::Save();
|
||||
|
||||
// Save recently opened projects
|
||||
{
|
||||
@@ -209,6 +225,8 @@ void Core::Stop()
|
||||
}
|
||||
}
|
||||
|
||||
OpenGLProxy::DestroyInstance();
|
||||
|
||||
MenuShared::DestroyInstance();
|
||||
|
||||
TaskManager::DestroyInstance();
|
||||
@@ -263,16 +281,26 @@ const Tool::Item &Core::tool() const
|
||||
return tool_;
|
||||
}
|
||||
|
||||
const Tool::AddableObject &Core::selected_addable_object() const
|
||||
const Tool::AddableObject &Core::GetSelectedAddableObject() const
|
||||
{
|
||||
return addable_object_;
|
||||
}
|
||||
|
||||
const QString &Core::GetSelectedTransition() const
|
||||
{
|
||||
return selected_transition_;
|
||||
}
|
||||
|
||||
void Core::SetSelectedAddableObject(const Tool::AddableObject &obj)
|
||||
{
|
||||
addable_object_ = obj;
|
||||
}
|
||||
|
||||
void Core::SetSelectedTransitionObject(const QString &obj)
|
||||
{
|
||||
selected_transition_ = obj;
|
||||
}
|
||||
|
||||
void Core::ClearOpenRecentList()
|
||||
{
|
||||
recent_projects_.clear();
|
||||
@@ -490,9 +518,11 @@ void Core::AddOpenProject(ProjectPtr p)
|
||||
void Core::AddOpenProjectFromTask(Task *task)
|
||||
{
|
||||
QList<ProjectPtr> projects = static_cast<ProjectLoadTask*>(task)->GetLoadedProjects();
|
||||
QList<MainWindowLayoutInfo> layouts = static_cast<ProjectLoadTask*>(task)->GetLoadedLayouts();
|
||||
|
||||
foreach (ProjectPtr p, projects) {
|
||||
AddOpenProject(p);
|
||||
for (int i=0; i<projects.size(); i++) {
|
||||
AddOpenProject(projects.at(i));
|
||||
main_window_->LoadLayout(layouts.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,33 +569,110 @@ void Core::ProjectWasModified(bool e)
|
||||
}
|
||||
}
|
||||
|
||||
void Core::DeclareTypesForQt()
|
||||
bool Core::StartHeadlessExport()
|
||||
{
|
||||
qRegisterMetaType<rational>();
|
||||
qRegisterMetaType<OpenGLTexturePtr>();
|
||||
qRegisterMetaType<OpenGLTextureCache::ReferencePtr>();
|
||||
qRegisterMetaType<NodeValue>();
|
||||
qRegisterMetaType<NodeValueTable>();
|
||||
qRegisterMetaType<NodeValueDatabase>();
|
||||
qRegisterMetaType<FramePtr>();
|
||||
qRegisterMetaType<SampleBufferPtr>();
|
||||
qRegisterMetaType<AudioParams>();
|
||||
qRegisterMetaType<NodeKeyframe::Type>();
|
||||
qRegisterMetaType<Decoder::RetrieveState>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::TimeRange>();
|
||||
qRegisterMetaType<Color>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::ProjectPtr>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::AudioVisualWaveform>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::SampleJob>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::ShaderJob>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::GenerateJob>();
|
||||
if (startup_project_.isEmpty()) {
|
||||
qCritical().noquote() << tr("You must specify a project file to export");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!QFileInfo::exists(startup_project_)) {
|
||||
qCritical().noquote() << tr("Specified project does not exist");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start a load task and try running it
|
||||
ProjectLoadTask plm(startup_project_);
|
||||
CLITaskDialog task_dialog(&plm);
|
||||
|
||||
if (task_dialog.Run()) {
|
||||
ProjectPtr p = plm.GetLoadedProjects().first();
|
||||
QList<ItemPtr> items = p->get_items_of_type(Item::kSequence);
|
||||
|
||||
// Check if this project contains sequences
|
||||
if (items.isEmpty()) {
|
||||
qCritical().noquote() << tr("Project contains no sequences, nothing to export");
|
||||
return false;
|
||||
}
|
||||
|
||||
SequencePtr sequence = nullptr;
|
||||
|
||||
// Check if this project contains multiple sequences
|
||||
if (items.size() > 1) {
|
||||
qInfo().noquote() << tr("This project has multiple sequences. Which do you wish to export?");
|
||||
for (int i=0;i<items.size();i++) {
|
||||
std::cout << "[" << i << "] " << items.at(i)->name().toStdString();
|
||||
}
|
||||
|
||||
QTextStream stream(stdin);
|
||||
QString sequence_read;
|
||||
int sequence_index = -1;
|
||||
QString quit_code = QStringLiteral("q");
|
||||
std::string prompt = tr("Enter number (or %1 to cancel): ").arg(quit_code).toStdString();
|
||||
forever {
|
||||
std::cout << prompt;
|
||||
|
||||
stream.readLineInto(&sequence_read);
|
||||
|
||||
if (!QString::compare(sequence_read, quit_code, Qt::CaseInsensitive)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok;
|
||||
sequence_index = sequence_read.toInt(&ok);
|
||||
|
||||
if (ok && sequence_index >= 0 && sequence_index < items.size()) {
|
||||
break;
|
||||
} else {
|
||||
qCritical().noquote() << tr("Invalid sequence number");
|
||||
}
|
||||
}
|
||||
|
||||
sequence = std::static_pointer_cast<Sequence>(items.at(sequence_index));
|
||||
} else {
|
||||
sequence = std::static_pointer_cast<Sequence>(items.first());
|
||||
}
|
||||
|
||||
ExportParams params;
|
||||
ExportTask export_task(sequence->viewer_output(), p->color_manager(), params);
|
||||
CLITaskDialog export_dialog(&export_task);
|
||||
if (export_dialog.Run()) {
|
||||
qInfo().noquote() << tr("Export succeeded");
|
||||
return true;
|
||||
} else {
|
||||
qInfo().noquote() << tr("Export failed: %1").arg(export_task.GetError());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
qCritical().noquote() << tr("Project failed to load: %1").arg(plm.GetError());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::OpenStartupProject()
|
||||
{
|
||||
// Load startup project
|
||||
if (!startup_project_.isEmpty() && !QFileInfo::exists(startup_project_)) {
|
||||
QMessageBox::warning(main_window_,
|
||||
tr("Failed to open startup file"),
|
||||
tr("The project \"%1\" doesn't exist. A new project will be started instead.").arg(startup_project_),
|
||||
QMessageBox::Ok);
|
||||
|
||||
startup_project_.clear();
|
||||
}
|
||||
|
||||
if (startup_project_.isEmpty()) {
|
||||
// If no load project is set, create a new one on open
|
||||
CreateNewProject();
|
||||
} else {
|
||||
OpenProjectInternal(startup_project_);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::StartGUI(bool full_screen)
|
||||
{
|
||||
// Set UI style
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
StyleManager::SetStyle(StyleManager::DefaultStyle());
|
||||
StyleManager::Init();
|
||||
|
||||
// Set up shared menus
|
||||
MenuShared::CreateInstance();
|
||||
@@ -764,90 +871,6 @@ bool Core::CloseAllExceptActiveProject()
|
||||
return true;
|
||||
}
|
||||
|
||||
QList<rational> Core::SupportedFrameRates()
|
||||
{
|
||||
QList<rational> frame_rates;
|
||||
|
||||
frame_rates.append(rational(10, 1)); // 10 FPS
|
||||
frame_rates.append(rational(15, 1)); // 15 FPS
|
||||
frame_rates.append(rational(24000, 1001)); // 23.976 FPS
|
||||
frame_rates.append(rational(24, 1)); // 24 FPS
|
||||
frame_rates.append(rational(25, 1)); // 25 FPS
|
||||
frame_rates.append(rational(30000, 1001)); // 29.97 FPS
|
||||
frame_rates.append(rational(30, 1)); // 30 FPS
|
||||
frame_rates.append(rational(48000, 1001)); // 47.952 FPS
|
||||
frame_rates.append(rational(48, 1)); // 48 FPS
|
||||
frame_rates.append(rational(50, 1)); // 50 FPS
|
||||
frame_rates.append(rational(60000, 1001)); // 59.94 FPS
|
||||
frame_rates.append(rational(60, 1)); // 60 FPS
|
||||
|
||||
return frame_rates;
|
||||
}
|
||||
|
||||
QList<int> Core::SupportedSampleRates()
|
||||
{
|
||||
QList<int> sample_rates;
|
||||
|
||||
sample_rates.append(8000); // 8000 Hz
|
||||
sample_rates.append(11025); // 11025 Hz
|
||||
sample_rates.append(16000); // 16000 Hz
|
||||
sample_rates.append(22050); // 22050 Hz
|
||||
sample_rates.append(24000); // 24000 Hz
|
||||
sample_rates.append(32000); // 32000 Hz
|
||||
sample_rates.append(44100); // 44100 Hz
|
||||
sample_rates.append(48000); // 48000 Hz
|
||||
sample_rates.append(88200); // 88200 Hz
|
||||
sample_rates.append(96000); // 96000 Hz
|
||||
|
||||
return sample_rates;
|
||||
}
|
||||
|
||||
QList<uint64_t> Core::SupportedChannelLayouts()
|
||||
{
|
||||
QList<uint64_t> channel_layouts;
|
||||
|
||||
channel_layouts.append(AV_CH_LAYOUT_MONO);
|
||||
channel_layouts.append(AV_CH_LAYOUT_STEREO);
|
||||
channel_layouts.append(AV_CH_LAYOUT_2_1);
|
||||
channel_layouts.append(AV_CH_LAYOUT_5POINT1);
|
||||
channel_layouts.append(AV_CH_LAYOUT_7POINT1);
|
||||
|
||||
return channel_layouts;
|
||||
}
|
||||
|
||||
QList<int> Core::SupportedDividers()
|
||||
{
|
||||
return {1, 2, 3, 4, 6, 8, 12, 16};
|
||||
}
|
||||
|
||||
QString Core::FrameRateToString(const rational &frame_rate)
|
||||
{
|
||||
return tr("%1 FPS").arg(frame_rate.toDouble());
|
||||
}
|
||||
|
||||
QString Core::SampleRateToString(const int &sample_rate)
|
||||
{
|
||||
return tr("%1 Hz").arg(sample_rate);
|
||||
}
|
||||
|
||||
QString Core::ChannelLayoutToString(const uint64_t &layout)
|
||||
{
|
||||
switch (layout) {
|
||||
case AV_CH_LAYOUT_MONO:
|
||||
return tr("Mono");
|
||||
case AV_CH_LAYOUT_STEREO:
|
||||
return tr("Stereo");
|
||||
case AV_CH_LAYOUT_2_1:
|
||||
return tr("2.1");
|
||||
case AV_CH_LAYOUT_5POINT1:
|
||||
return tr("5.1");
|
||||
case AV_CH_LAYOUT_7POINT1:
|
||||
return tr("7.1");
|
||||
default:
|
||||
return tr("Unknown (0x%1)").arg(layout, 1, 16);
|
||||
}
|
||||
}
|
||||
|
||||
QString Core::GetProjectFilter()
|
||||
{
|
||||
return QStringLiteral("%1 (*.ove)").arg(tr("Olive Project"));
|
||||
@@ -877,6 +900,11 @@ bool Core::SaveProjectAs(ProjectPtr p)
|
||||
GetProjectFilter());
|
||||
|
||||
if (!fn.isEmpty()) {
|
||||
QString extension(QStringLiteral(".ove"));
|
||||
if (!fn.endsWith(extension, Qt::CaseInsensitive)) {
|
||||
fn.append(extension);
|
||||
}
|
||||
|
||||
p->set_filename(fn);
|
||||
|
||||
SaveProjectInternal(p);
|
||||
@@ -915,21 +943,11 @@ void Core::OpenProjectInternal(const QString &filename)
|
||||
|
||||
ProjectLoadTask* plm = new ProjectLoadTask(filename);
|
||||
|
||||
if (gui_active_) {
|
||||
TaskDialog* task_dialog = new TaskDialog(plm, tr("Load Project"), main_window());
|
||||
|
||||
TaskDialog* task_dialog = new TaskDialog(plm, tr("Load Project"), main_window());
|
||||
connect(task_dialog, &TaskDialog::TaskSucceeded, this, &Core::AddOpenProjectFromTask);
|
||||
|
||||
connect(task_dialog, &TaskDialog::TaskSucceeded, this, &Core::AddOpenProjectFromTask);
|
||||
|
||||
task_dialog->open();
|
||||
|
||||
} else {
|
||||
|
||||
//connect(plm, &ProjectLoadManager::ProjectLoaded, this, &Core::AddOpenProject);
|
||||
|
||||
CLITaskDialog task_dialog(plm);
|
||||
|
||||
}
|
||||
task_dialog->open();
|
||||
}
|
||||
|
||||
int Core::CountFilesInFileList(const QFileInfoList &filenames)
|
||||
@@ -1164,23 +1182,32 @@ void Core::CacheActiveSequence(bool in_out_only)
|
||||
TimeBasedPanel* p = PanelManager::instance()->MostRecentlyFocused<TimeBasedPanel>();
|
||||
|
||||
if (p && p->GetConnectedViewer()) {
|
||||
CacheTask* task = new CacheTask(p->GetConnectedViewer(),
|
||||
p->GetConnectedViewer()->video_params(),
|
||||
p->GetConnectedViewer()->audio_params(),
|
||||
in_out_only);
|
||||
// Hacky but works for now
|
||||
|
||||
// Stop any current auto-cache tasks
|
||||
ViewerWidget::StopAllBackgroundCacheTasks(true);
|
||||
ViewerWidget::SetBackgroundCacheTask(task);
|
||||
// Find Viewer attached to this TimeBasedPanel
|
||||
QList<ViewerPanel*> all_viewers = PanelManager::instance()->GetPanelsOfType<ViewerPanel>();
|
||||
|
||||
TaskDialog* dialog = new TaskDialog(task, tr("Caching Sequence"), main_window_);
|
||||
ViewerPanel* found_panel = nullptr;
|
||||
|
||||
connect(dialog,
|
||||
&TaskDialog::TaskSucceeded,
|
||||
this,
|
||||
[] { ViewerWidget::SetBackgroundCacheTask(nullptr); });
|
||||
foreach (ViewerPanel* viewer, all_viewers) {
|
||||
if (viewer->GetConnectedViewer() == p->GetConnectedViewer()) {
|
||||
found_panel = viewer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dialog->open();
|
||||
if (found_panel) {
|
||||
if (in_out_only) {
|
||||
found_panel->CacheSequenceInOut();
|
||||
} else {
|
||||
found_panel->CacheEntireSequence();
|
||||
}
|
||||
} else {
|
||||
QMessageBox::critical(main_window_,
|
||||
tr("Failed to cache sequence"),
|
||||
tr("No active viewer found with this sequence."),
|
||||
QMessageBox::Ok);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+28
-43
@@ -66,12 +66,14 @@ public:
|
||||
*/
|
||||
static Core* instance();
|
||||
|
||||
int execute(QCoreApplication *a);
|
||||
|
||||
/**
|
||||
* @brief Start Olive Core
|
||||
*
|
||||
* Main application launcher. Parses command line arguments and constructs main window (if entering a GUI mode).
|
||||
*/
|
||||
bool Start();
|
||||
void Start();
|
||||
|
||||
/**
|
||||
* @brief Stop Olive Core
|
||||
@@ -112,7 +114,12 @@ public:
|
||||
/**
|
||||
* @brief Get the currently selected object that the add tool should make (if the add tool is active)
|
||||
*/
|
||||
const Tool::AddableObject& selected_addable_object() const;
|
||||
const Tool::AddableObject& GetSelectedAddableObject() const;
|
||||
|
||||
/**
|
||||
* @brief Get the currently selected node that the transition tool should make (if the transition tool is active)
|
||||
*/
|
||||
const QString& GetSelectedTransition() const;
|
||||
|
||||
/**
|
||||
* @brief Get current snapping value
|
||||
@@ -162,42 +169,6 @@ public:
|
||||
|
||||
static QString PasteStringFromClipboard();
|
||||
|
||||
/**
|
||||
* @brief Return a list of supported frame rates in rational form
|
||||
*
|
||||
* These rationals can be flipped to create a timebase in this frame rate.
|
||||
*/
|
||||
static QList<rational> SupportedFrameRates();
|
||||
|
||||
/**
|
||||
* @brief Return a list of supported sample rates in integer form
|
||||
*/
|
||||
static QList<int> SupportedSampleRates();
|
||||
/**
|
||||
* @brief Return a list of supported channel layouts as or'd flags
|
||||
*/
|
||||
static QList<uint64_t> SupportedChannelLayouts();
|
||||
|
||||
/**
|
||||
* @brief Return a list of supported dividers
|
||||
*/
|
||||
static QList<int> SupportedDividers();
|
||||
|
||||
/**
|
||||
* @brief Convert rational frame rate (i.e. flipped timebase) to a user-friendly string
|
||||
*/
|
||||
static QString FrameRateToString(const rational& frame_rate);
|
||||
|
||||
/**
|
||||
* @brief Convert integer sample rate to a user-friendly string
|
||||
*/
|
||||
static QString SampleRateToString(const int &sample_rate);
|
||||
|
||||
/**
|
||||
* @brief Convert channel layout to a user-friendly string
|
||||
*/
|
||||
static QString ChannelLayoutToString(const uint64_t &layout);
|
||||
|
||||
/**
|
||||
* @brief Recursively count files in a file/directory list
|
||||
*/
|
||||
@@ -339,6 +310,11 @@ public slots:
|
||||
*/
|
||||
void SetSelectedAddableObject(const Tool::AddableObject& obj);
|
||||
|
||||
/**
|
||||
* @brief Set the currently selected object that the add tool should make
|
||||
*/
|
||||
void SetSelectedTransitionObject(const QString& obj);
|
||||
|
||||
/**
|
||||
* @brief Clears the list of recently opened/saved projects
|
||||
*/
|
||||
@@ -405,11 +381,6 @@ private:
|
||||
*/
|
||||
void PushRecentlyOpenedProject(const QString &s);
|
||||
|
||||
/**
|
||||
* @brief Internal project open
|
||||
*/
|
||||
void OpenProjectInternal(const QString& filename);
|
||||
|
||||
/**
|
||||
* @brief Declare custom types/classes for Qt's signal/slot system
|
||||
*
|
||||
@@ -459,6 +430,11 @@ private:
|
||||
*/
|
||||
Tool::AddableObject addable_object_;
|
||||
|
||||
/**
|
||||
* @brief Currently selected transition
|
||||
*/
|
||||
QString selected_transition_;
|
||||
|
||||
/**
|
||||
* @brief Current snapping setting
|
||||
*/
|
||||
@@ -507,6 +483,15 @@ private slots:
|
||||
|
||||
void ProjectWasModified(bool e);
|
||||
|
||||
bool StartHeadlessExport();
|
||||
|
||||
void OpenStartupProject();
|
||||
|
||||
/**
|
||||
* @brief Internal project open
|
||||
*/
|
||||
void OpenProjectInternal(const QString& filename);
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
add_subdirectory(about)
|
||||
add_subdirectory(actionsearch)
|
||||
add_subdirectory(color)
|
||||
add_subdirectory(diskcache)
|
||||
add_subdirectory(export)
|
||||
add_subdirectory(footageproperties)
|
||||
add_subdirectory(keyframeproperties)
|
||||
|
||||
@@ -16,9 +16,7 @@
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
task/cache/cache.h
|
||||
task/cache/cache.cpp
|
||||
task/cache/footagecache.h
|
||||
task/cache/footagecache.cpp
|
||||
dialog/diskcache/diskcachedialog.h
|
||||
dialog/diskcache/diskcachedialog.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,107 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "diskcachedialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "config/config.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
DiskCacheDialog::DiskCacheDialog(DiskCacheFolder *folder, QWidget* parent) :
|
||||
QDialog(parent),
|
||||
folder_(folder)
|
||||
{
|
||||
QGridLayout* layout = new QGridLayout(this);
|
||||
|
||||
int row = 0;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Disk Cache: %1").arg(folder->GetPath())), row, 0, 1, 2);
|
||||
setWindowTitle(tr("Disk Cache Settings"));
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Maximum Disk Cache:")), row, 0);
|
||||
|
||||
maximum_cache_slider_ = new FloatSlider();
|
||||
maximum_cache_slider_->SetFormat(tr("%1 GB"));
|
||||
maximum_cache_slider_->SetMinimum(1.0);
|
||||
maximum_cache_slider_->SetValue(static_cast<double>(folder->GetLimit()) / static_cast<double>(kBytesInGigabyte));
|
||||
layout->addWidget(maximum_cache_slider_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
clear_cache_btn_ = new QPushButton(tr("Clear Disk Cache"));
|
||||
connect(clear_cache_btn_, &QPushButton::clicked, this, &DiskCacheDialog::ClearDiskCache);
|
||||
layout->addWidget(clear_cache_btn_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
clear_disk_cache_ = new QCheckBox(tr("Automatically clear disk cache on close"));
|
||||
clear_disk_cache_->setChecked(folder->GetClearOnClose());
|
||||
layout->addWidget(clear_disk_cache_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &DiskCacheDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &DiskCacheDialog::reject);
|
||||
layout->addWidget(buttons, row, 0, 1, 2);
|
||||
}
|
||||
|
||||
void DiskCacheDialog::accept()
|
||||
{
|
||||
qint64 new_disk_cache_limit = qRound64(maximum_cache_slider_->GetValue() * kBytesInGigabyte);
|
||||
if (new_disk_cache_limit != folder_->GetLimit()) {
|
||||
folder_->SetLimit(new_disk_cache_limit);
|
||||
}
|
||||
|
||||
if (folder_->GetClearOnClose() != clear_disk_cache_->isChecked()) {
|
||||
folder_->SetClearOnClose(clear_disk_cache_->isChecked());
|
||||
}
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void DiskCacheDialog::ClearDiskCache()
|
||||
{
|
||||
if (QMessageBox::question(this,
|
||||
tr("Clear Disk Cache"),
|
||||
tr("Are you sure you want to clear the disk cache in '%1'?").arg(Config::Current()["DiskCachePath"].toString()),
|
||||
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
|
||||
clear_cache_btn_->setEnabled(false);
|
||||
|
||||
if (DiskManager::instance()->ClearDiskCache(folder_->GetPath())) {
|
||||
clear_cache_btn_->setText(tr("Disk Cache Cleared"));
|
||||
} else {
|
||||
QMessageBox::information(this,
|
||||
tr("Clear Disk Cache"),
|
||||
tr("Disk cache failed to fully clear. You may have to delete the cache files manually."),
|
||||
QMessageBox::Ok);
|
||||
clear_cache_btn_->setText(tr("Disk Cache Partially Cleared"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -0,0 +1,58 @@
|
||||
/***
|
||||
|
||||
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 DISKCACHEDIALOG_H
|
||||
#define DISKCACHEDIALOG_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialog>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "render/diskmanager.h"
|
||||
#include "widget/slider/floatslider.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class DiskCacheDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
DiskCacheDialog(DiskCacheFolder* folder, QWidget* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
virtual void accept() override;
|
||||
|
||||
private:
|
||||
DiskCacheFolder* folder_;
|
||||
|
||||
FloatSlider* maximum_cache_slider_;
|
||||
|
||||
QCheckBox* clear_disk_cache_;
|
||||
|
||||
QPushButton* clear_cache_btn_;
|
||||
|
||||
private slots:
|
||||
void ClearDiskCache();
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // DISKCACHEDIALOG_H
|
||||
@@ -177,9 +177,11 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
|
||||
video_tab_->width_slider()->SetDefaultValue(viewer_node_->video_params().width());
|
||||
video_tab_->height_slider()->SetValue(viewer_node_->video_params().height());
|
||||
video_tab_->height_slider()->SetDefaultValue(viewer_node_->video_params().height());
|
||||
video_tab_->set_frame_rate(viewer_node_->video_params().time_base().flipped());
|
||||
audio_tab_->set_sample_rate(viewer_node_->audio_params().sample_rate());
|
||||
audio_tab_->set_channel_layout(viewer_node_->audio_params().channel_layout());
|
||||
video_tab_->frame_rate_combobox()->SetFrameRate(viewer_node_->video_params().time_base().flipped());
|
||||
video_tab_->pixel_aspect_combobox()->SetPixelAspectRatio(viewer_node_->video_params().pixel_aspect_ratio());
|
||||
video_tab_->interlaced_combobox()->SetInterlaceMode(viewer_node_->video_params().interlacing());
|
||||
audio_tab_->sample_rate_combobox()->SetSampleRate(viewer_node_->audio_params().sample_rate());
|
||||
audio_tab_->channel_layout_combobox()->SetChannelLayout(viewer_node_->audio_params().channel_layout());
|
||||
|
||||
video_aspect_ratio_ = static_cast<double>(viewer_node_->video_params().width()) / static_cast<double>(viewer_node_->video_params().height());
|
||||
|
||||
@@ -203,11 +205,6 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
|
||||
this,
|
||||
&ExportDialog::ResolutionChanged);
|
||||
|
||||
connect(video_tab_->codec_combobox(),
|
||||
static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
this,
|
||||
&ExportDialog::VideoCodecChanged);
|
||||
|
||||
connect(video_tab_,
|
||||
&ExportVideoTab::ColorSpaceChanged,
|
||||
preview_viewer_,
|
||||
@@ -232,8 +229,8 @@ void ExportDialog::StartExport()
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate if the entered filename contains the correct extension (the extension is necessary for both FFmpeg and
|
||||
// OIIO to determine the output format)
|
||||
// Validate if the entered filename contains the correct extension (the extension is necessary
|
||||
// for both FFmpeg and OIIO to determine the output format)
|
||||
QString necessary_ext = QStringLiteral(".%1").arg(ExportFormat::GetExtension(static_cast<ExportFormat::Format>(format_combobox_->currentIndex())));
|
||||
|
||||
// If it doesn't, see if the user wants to append it automatically. If not, we don't abort the export.
|
||||
@@ -356,7 +353,6 @@ void ExportDialog::FormatChanged(int index)
|
||||
foreach (ExportCodec::Codec vcodec, ExportFormat::GetVideoCodecs(current_format)) {
|
||||
video_tab_->codec_combobox()->addItem(ExportCodec::GetCodecName(vcodec), vcodec);
|
||||
}
|
||||
VideoCodecChanged();
|
||||
|
||||
audio_tab_->codec_combobox()->clear();
|
||||
foreach (ExportCodec::Codec acodec, ExportFormat::GetAudioCodecs(current_format)) {
|
||||
@@ -396,17 +392,6 @@ void ExportDialog::ResolutionChanged()
|
||||
UpdateViewerDimensions();
|
||||
}
|
||||
|
||||
void ExportDialog::VideoCodecChanged()
|
||||
{
|
||||
ExportCodec::Codec codec = static_cast<ExportCodec::Codec>(video_tab_->codec_combobox()->currentData().toInt());
|
||||
|
||||
if (codec == ExportCodec::kCodecH264) {
|
||||
video_tab_->SetCodecSection(video_tab_->h264_section());
|
||||
} else if (ExportCodec::IsCodecAStillImage(codec)) {
|
||||
video_tab_->SetCodecSection(video_tab_->image_section());
|
||||
}
|
||||
}
|
||||
|
||||
void ExportDialog::LoadPresets()
|
||||
{
|
||||
|
||||
@@ -440,12 +425,14 @@ ExportParams ExportDialog::GenerateParams() const
|
||||
|
||||
VideoParams video_render_params(static_cast<int>(video_tab_->width_slider()->GetValue()),
|
||||
static_cast<int>(video_tab_->height_slider()->GetValue()),
|
||||
video_tab_->frame_rate().flipped(),
|
||||
video_tab_->frame_rate_combobox()->GetFrameRate().flipped(),
|
||||
PixelFormat::instance()->GetConfiguredFormatForMode(render_mode),
|
||||
video_tab_->pixel_aspect_combobox()->GetPixelAspectRatio(),
|
||||
video_tab_->interlaced_combobox()->GetInterlaceMode(),
|
||||
render_mode);
|
||||
|
||||
AudioParams audio_render_params(audio_tab_->sample_rate_combobox()->currentData().toInt(),
|
||||
audio_tab_->channel_layout_combobox()->currentData().toULongLong(),
|
||||
audio_tab_->channel_layout_combobox()->GetChannelLayout(),
|
||||
SampleFormat::kInternalFormat);
|
||||
|
||||
ExportParams params;
|
||||
@@ -457,7 +444,7 @@ ExportParams ExportDialog::GenerateParams() const
|
||||
}
|
||||
|
||||
if (video_enabled_->isChecked()) {
|
||||
ExportCodec::Codec video_codec = static_cast<ExportCodec::Codec>(video_tab_->codec_combobox()->currentData().toInt());
|
||||
ExportCodec::Codec video_codec = video_tab_->GetSelectedCodec();
|
||||
params.EnableVideo(video_render_params, video_codec);
|
||||
|
||||
params.set_video_threads(video_tab_->threads());
|
||||
@@ -465,6 +452,8 @@ ExportParams ExportDialog::GenerateParams() const
|
||||
video_tab_->GetCodecSection()->AddOpts(¶ms);
|
||||
|
||||
params.set_color_transform(video_tab_->CurrentOCIOColorSpace());
|
||||
|
||||
params.set_video_pix_fmt(video_tab_->pix_fmt());
|
||||
}
|
||||
|
||||
if (audio_enabled_->isChecked()) {
|
||||
@@ -477,8 +466,8 @@ ExportParams ExportDialog::GenerateParams() const
|
||||
|
||||
void ExportDialog::UpdateViewerDimensions()
|
||||
{
|
||||
preview_viewer_->SetOverrideSize(static_cast<int>(video_tab_->width_slider()->GetValue()),
|
||||
static_cast<int>(video_tab_->height_slider()->GetValue()));
|
||||
preview_viewer_->SetViewerResolution(static_cast<int>(video_tab_->width_slider()->GetValue()),
|
||||
static_cast<int>(video_tab_->height_slider()->GetValue()));
|
||||
|
||||
QMatrix4x4 transform =
|
||||
ExportParams::GenerateMatrix(static_cast<ExportParams::VideoScalingMethod>(video_tab_->scaling_method_combobox()->currentData().toInt()),
|
||||
|
||||
@@ -81,8 +81,6 @@ private slots:
|
||||
|
||||
void ResolutionChanged();
|
||||
|
||||
void VideoCodecChanged();
|
||||
|
||||
void UpdateViewerDimensions();
|
||||
|
||||
void StartExport();
|
||||
|
||||
@@ -2,42 +2,61 @@
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
ExportAdvancedVideoDialog::ExportAdvancedVideoDialog(QWidget *parent) :
|
||||
ExportAdvancedVideoDialog::ExportAdvancedVideoDialog(const QList<QString> &pix_fmts, QWidget *parent) :
|
||||
QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("Advanced"));
|
||||
|
||||
QGridLayout* layout = new QGridLayout(this);
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
|
||||
int row = 0;
|
||||
{
|
||||
// Pixel Settings
|
||||
QGroupBox* pixel_group = new QGroupBox();
|
||||
layout->addWidget(pixel_group);
|
||||
pixel_group->setTitle(tr("Pixel"));
|
||||
|
||||
layout->addWidget(new QLabel(tr("Threads:")), row, 0);
|
||||
QGridLayout* pixel_layout = new QGridLayout(pixel_group);
|
||||
|
||||
thread_slider_ = new IntegerSlider();
|
||||
thread_slider_->SetMinimum(0);
|
||||
thread_slider_->SetDefaultValue(0);
|
||||
layout->addWidget(thread_slider_, row, 1);
|
||||
int row = 0;
|
||||
|
||||
row++;
|
||||
pixel_layout->addWidget(new QLabel(tr("Pixel Format:")), row, 0);
|
||||
|
||||
pixel_format_combobox_ = new QComboBox();
|
||||
pixel_format_combobox_->addItems(pix_fmts);
|
||||
pixel_layout->addWidget(pixel_format_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
}
|
||||
|
||||
{
|
||||
// Performance Settings
|
||||
QGroupBox* performance_group = new QGroupBox();
|
||||
layout->addWidget(performance_group);
|
||||
performance_group->setTitle(tr("Performance"));
|
||||
|
||||
QGridLayout* performance_layout = new QGridLayout(performance_group);
|
||||
|
||||
int row = 0;
|
||||
|
||||
performance_layout->addWidget(new QLabel(tr("Threads:")), row, 0);
|
||||
|
||||
thread_slider_ = new IntegerSlider();
|
||||
thread_slider_->SetMinimum(0);
|
||||
thread_slider_->SetDefaultValue(0);
|
||||
performance_layout->addWidget(thread_slider_, row, 1);
|
||||
|
||||
row++;
|
||||
}
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &ExportAdvancedVideoDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &ExportAdvancedVideoDialog::reject);
|
||||
layout->addWidget(buttons, row, 0, 1, 2);
|
||||
}
|
||||
|
||||
int ExportAdvancedVideoDialog::threads() const
|
||||
{
|
||||
return static_cast<int>(thread_slider_->GetValue());
|
||||
}
|
||||
|
||||
void ExportAdvancedVideoDialog::set_threads(int t)
|
||||
{
|
||||
thread_slider_->SetValue(t);
|
||||
layout->addWidget(buttons);
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef EXPORTADVANCEDVIDEODIALOG_H
|
||||
#define EXPORTADVANCEDVIDEODIALOG_H
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
|
||||
#include "widget/slider/integerslider.h"
|
||||
@@ -11,14 +12,34 @@ class ExportAdvancedVideoDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExportAdvancedVideoDialog(QWidget* parent = nullptr);
|
||||
ExportAdvancedVideoDialog(const QList<QString>& pix_fmts,
|
||||
QWidget* parent = nullptr);
|
||||
|
||||
int threads() const;
|
||||
void set_threads(int t);
|
||||
int threads() const
|
||||
{
|
||||
return static_cast<int>(thread_slider_->GetValue());
|
||||
}
|
||||
|
||||
void set_threads(int t)
|
||||
{
|
||||
thread_slider_->SetValue(t);
|
||||
}
|
||||
|
||||
QString pix_fmt() const
|
||||
{
|
||||
return pixel_format_combobox_->currentText();
|
||||
}
|
||||
|
||||
void set_pix_fmt(const QString& s)
|
||||
{
|
||||
pixel_format_combobox_->setCurrentText(s);
|
||||
}
|
||||
|
||||
private:
|
||||
IntegerSlider* thread_slider_;
|
||||
|
||||
QComboBox* pixel_format_combobox_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -46,22 +46,14 @@ ExportAudioTab::ExportAudioTab(QWidget* parent) :
|
||||
|
||||
layout->addWidget(new QLabel(tr("Sample Rate:")), row, 0);
|
||||
|
||||
sample_rate_combobox_ = new QComboBox();
|
||||
sample_rates_ = Core::SupportedSampleRates();
|
||||
foreach (const int& sr, sample_rates_) {
|
||||
sample_rate_combobox_->addItem(Core::SampleRateToString(sr), sr);
|
||||
}
|
||||
sample_rate_combobox_ = new SampleRateComboBox();
|
||||
layout->addWidget(sample_rate_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Channel Layout:")), row, 0);
|
||||
|
||||
channel_layout_combobox_ = new QComboBox();
|
||||
channel_layouts_ = Core::SupportedChannelLayouts();
|
||||
foreach (const uint64_t& ch_layout, channel_layouts_) {
|
||||
channel_layout_combobox_->addItem(Core::ChannelLayoutToString(ch_layout), QVariant::fromValue(ch_layout));
|
||||
}
|
||||
channel_layout_combobox_ = new ChannelLayoutComboBox();
|
||||
layout->addWidget(channel_layout_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
@@ -72,29 +64,4 @@ ExportAudioTab::ExportAudioTab(QWidget* parent) :
|
||||
outer_layout->addStretch();
|
||||
}
|
||||
|
||||
QComboBox *ExportAudioTab::codec_combobox() const
|
||||
{
|
||||
return codec_combobox_;
|
||||
}
|
||||
|
||||
QComboBox *ExportAudioTab::sample_rate_combobox() const
|
||||
{
|
||||
return sample_rate_combobox_;
|
||||
}
|
||||
|
||||
QComboBox *ExportAudioTab::channel_layout_combobox() const
|
||||
{
|
||||
return channel_layout_combobox_;
|
||||
}
|
||||
|
||||
void ExportAudioTab::set_sample_rate(int rate)
|
||||
{
|
||||
sample_rate_combobox_->setCurrentIndex(sample_rates_.indexOf(rate));
|
||||
}
|
||||
|
||||
void ExportAudioTab::set_channel_layout(uint64_t layout)
|
||||
{
|
||||
channel_layout_combobox_->setCurrentIndex(channel_layouts_.indexOf(layout));
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <QWidget>
|
||||
|
||||
#include "common/define.h"
|
||||
#include "widget/standardcombos/standardcombos.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -33,20 +34,26 @@ class ExportAudioTab : public QWidget
|
||||
public:
|
||||
ExportAudioTab(QWidget* parent = nullptr);
|
||||
|
||||
QComboBox* codec_combobox() const;
|
||||
QComboBox* sample_rate_combobox() const;
|
||||
QComboBox* channel_layout_combobox() const;
|
||||
QComboBox* codec_combobox() const
|
||||
{
|
||||
return codec_combobox_;
|
||||
}
|
||||
|
||||
void set_sample_rate(int rate);
|
||||
void set_channel_layout(uint64_t layout);
|
||||
SampleRateComboBox* sample_rate_combobox() const
|
||||
{
|
||||
return sample_rate_combobox_;
|
||||
}
|
||||
|
||||
ChannelLayoutComboBox* channel_layout_combobox() const
|
||||
{
|
||||
return channel_layout_combobox_;
|
||||
}
|
||||
|
||||
private:
|
||||
QComboBox* codec_combobox_;
|
||||
QComboBox* sample_rate_combobox_;
|
||||
QComboBox* channel_layout_combobox_;
|
||||
SampleRateComboBox* sample_rate_combobox_;
|
||||
ChannelLayoutComboBox* channel_layout_combobox_;
|
||||
|
||||
QList<int> sample_rates_;
|
||||
QList<uint64_t> channel_layouts_;
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -49,71 +49,6 @@ ExportVideoTab::ExportVideoTab(ColorManager* color_manager, QWidget *parent) :
|
||||
outer_layout->addStretch();
|
||||
}
|
||||
|
||||
QComboBox *ExportVideoTab::codec_combobox() const
|
||||
{
|
||||
return codec_combobox_;
|
||||
}
|
||||
|
||||
IntegerSlider *ExportVideoTab::width_slider() const
|
||||
{
|
||||
return width_slider_;
|
||||
}
|
||||
|
||||
IntegerSlider *ExportVideoTab::height_slider() const
|
||||
{
|
||||
return height_slider_;
|
||||
}
|
||||
|
||||
QCheckBox *ExportVideoTab::maintain_aspect_checkbox() const
|
||||
{
|
||||
return maintain_aspect_checkbox_;
|
||||
}
|
||||
|
||||
QComboBox *ExportVideoTab::scaling_method_combobox() const
|
||||
{
|
||||
return scaling_method_combobox_;
|
||||
}
|
||||
|
||||
const rational &ExportVideoTab::frame_rate() const
|
||||
{
|
||||
return frame_rates_.at(frame_rate_combobox_->currentIndex());
|
||||
}
|
||||
|
||||
void ExportVideoTab::set_frame_rate(const rational &frame_rate)
|
||||
{
|
||||
frame_rate_combobox_->setCurrentIndex(frame_rates_.indexOf(frame_rate));
|
||||
}
|
||||
|
||||
QString ExportVideoTab::CurrentOCIOColorSpace()
|
||||
{
|
||||
return color_space_chooser_->input();
|
||||
}
|
||||
|
||||
CodecSection *ExportVideoTab::GetCodecSection() const
|
||||
{
|
||||
return static_cast<CodecSection*>(codec_stack_->currentWidget());
|
||||
}
|
||||
|
||||
void ExportVideoTab::SetCodecSection(CodecSection *section)
|
||||
{
|
||||
codec_stack_->setCurrentWidget(section);
|
||||
}
|
||||
|
||||
ImageSection *ExportVideoTab::image_section() const
|
||||
{
|
||||
return image_section_;
|
||||
}
|
||||
|
||||
H264Section *ExportVideoTab::h264_section() const
|
||||
{
|
||||
return h264_section_;
|
||||
}
|
||||
|
||||
const int &ExportVideoTab::threads() const
|
||||
{
|
||||
return threads_;
|
||||
}
|
||||
|
||||
QWidget* ExportVideoTab::SetupResolutionSection()
|
||||
{
|
||||
int row = 0;
|
||||
@@ -163,14 +98,23 @@ QWidget* ExportVideoTab::SetupResolutionSection()
|
||||
|
||||
layout->addWidget(new QLabel(tr("Frame Rate:")), row, 0);
|
||||
|
||||
frame_rate_combobox_ = new QComboBox();
|
||||
frame_rates_ = Core::SupportedFrameRates();
|
||||
foreach (const rational& fr, frame_rates_) {
|
||||
frame_rate_combobox_->addItem(Core::FrameRateToString(fr));
|
||||
}
|
||||
|
||||
frame_rate_combobox_ = new FrameRateComboBox();
|
||||
layout->addWidget(frame_rate_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Pixel Aspect Ratio:")), row, 0);
|
||||
|
||||
pixel_aspect_combobox_ = new PixelAspectRatioComboBox();
|
||||
layout->addWidget(pixel_aspect_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Interlacing:")), row, 0);
|
||||
|
||||
interlaced_combobox_ = new InterlacedComboBox();
|
||||
layout->addWidget(interlaced_combobox_, row, 1);
|
||||
|
||||
return resolution_group;
|
||||
}
|
||||
|
||||
@@ -194,6 +138,10 @@ QWidget *ExportVideoTab::SetupCodecSection()
|
||||
|
||||
codec_combobox_ = new QComboBox();
|
||||
codec_layout->addWidget(codec_combobox_, row, 1);
|
||||
connect(codec_combobox_,
|
||||
static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
this,
|
||||
&ExportVideoTab::VideoCodecChanged);
|
||||
|
||||
row++;
|
||||
|
||||
@@ -222,13 +170,33 @@ void ExportVideoTab::MaintainAspectRatioChanged(bool val)
|
||||
|
||||
void ExportVideoTab::OpenAdvancedDialog()
|
||||
{
|
||||
ExportAdvancedVideoDialog d(this);
|
||||
// Find export formats compatible with this encoder
|
||||
QStringList pixel_formats = ExportCodec::GetPixelFormatsForCodec(GetSelectedCodec());
|
||||
|
||||
ExportAdvancedVideoDialog d(pixel_formats, this);
|
||||
|
||||
d.set_threads(threads_);
|
||||
d.set_pix_fmt(pix_fmt_);
|
||||
|
||||
if (d.exec() == QDialog::Accepted) {
|
||||
threads_ = d.threads();
|
||||
pix_fmt_ = d.pix_fmt();
|
||||
}
|
||||
}
|
||||
|
||||
void ExportVideoTab::VideoCodecChanged()
|
||||
{
|
||||
ExportCodec::Codec codec = GetSelectedCodec();
|
||||
|
||||
if (codec == ExportCodec::kCodecH264) {
|
||||
SetCodecSection(h264_section());
|
||||
} else if (ExportCodec::IsCodecAStillImage(codec)) {
|
||||
SetCodecSection(image_section());
|
||||
}
|
||||
|
||||
// Set default pixel format
|
||||
pix_fmt_ = ExportCodec::GetPixelFormatsForCodec(codec).first();
|
||||
qDebug() << "Set default pix fmt" << pix_fmt_;
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "render/colormanager.h"
|
||||
#include "widget/colorwheel/colorspacechooser.h"
|
||||
#include "widget/slider/integerslider.h"
|
||||
#include "widget/standardcombos/standardcombos.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -40,24 +41,87 @@ class ExportVideoTab : public QWidget
|
||||
public:
|
||||
ExportVideoTab(ColorManager* color_manager, QWidget* parent = nullptr);
|
||||
|
||||
QComboBox* codec_combobox() const;
|
||||
ExportCodec::Codec GetSelectedCodec() const
|
||||
{
|
||||
return static_cast<ExportCodec::Codec>(codec_combobox()->currentData().toInt());
|
||||
}
|
||||
|
||||
IntegerSlider* width_slider() const;
|
||||
IntegerSlider* height_slider() const;
|
||||
QCheckBox* maintain_aspect_checkbox() const;
|
||||
QComboBox* scaling_method_combobox() const;
|
||||
QComboBox* codec_combobox() const
|
||||
{
|
||||
return codec_combobox_;
|
||||
}
|
||||
|
||||
const rational& frame_rate() const;
|
||||
void set_frame_rate(const rational& frame_rate);
|
||||
IntegerSlider* width_slider() const
|
||||
{
|
||||
return width_slider_;
|
||||
}
|
||||
|
||||
QString CurrentOCIOColorSpace();
|
||||
IntegerSlider* height_slider() const
|
||||
{
|
||||
return height_slider_;
|
||||
}
|
||||
|
||||
CodecSection* GetCodecSection() const;
|
||||
void SetCodecSection(CodecSection* section);
|
||||
ImageSection* image_section() const;
|
||||
H264Section* h264_section() const;
|
||||
QCheckBox* maintain_aspect_checkbox() const
|
||||
{
|
||||
return maintain_aspect_checkbox_;
|
||||
}
|
||||
|
||||
const int& threads() const;
|
||||
QComboBox* scaling_method_combobox() const
|
||||
{
|
||||
return scaling_method_combobox_;
|
||||
}
|
||||
|
||||
FrameRateComboBox* frame_rate_combobox() const
|
||||
{
|
||||
return frame_rate_combobox_;
|
||||
}
|
||||
|
||||
QString CurrentOCIOColorSpace()
|
||||
{
|
||||
return color_space_chooser_->input();
|
||||
}
|
||||
|
||||
CodecSection* GetCodecSection() const
|
||||
{
|
||||
return static_cast<CodecSection*>(codec_stack_->currentWidget());
|
||||
}
|
||||
|
||||
void SetCodecSection(CodecSection* section)
|
||||
{
|
||||
codec_stack_->setCurrentWidget(section);
|
||||
}
|
||||
|
||||
ImageSection* image_section() const
|
||||
{
|
||||
return image_section_;
|
||||
}
|
||||
|
||||
H264Section* h264_section() const
|
||||
{
|
||||
return h264_section_;
|
||||
}
|
||||
|
||||
InterlacedComboBox* interlaced_combobox() const
|
||||
{
|
||||
return interlaced_combobox_;
|
||||
}
|
||||
|
||||
PixelAspectRatioComboBox* pixel_aspect_combobox() const
|
||||
{
|
||||
return pixel_aspect_combobox_;
|
||||
}
|
||||
|
||||
const int& threads() const
|
||||
{
|
||||
return threads_;
|
||||
}
|
||||
|
||||
const QString& pix_fmt() const {
|
||||
return pix_fmt_;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void VideoCodecChanged();
|
||||
|
||||
signals:
|
||||
void ColorSpaceChanged(const QString& colorspace);
|
||||
@@ -68,7 +132,7 @@ private:
|
||||
QWidget* SetupCodecSection();
|
||||
|
||||
QComboBox* codec_combobox_;
|
||||
QComboBox* frame_rate_combobox_;
|
||||
FrameRateComboBox* frame_rate_combobox_;
|
||||
QCheckBox* maintain_aspect_checkbox_;
|
||||
QComboBox* scaling_method_combobox_;
|
||||
|
||||
@@ -81,12 +145,15 @@ private:
|
||||
IntegerSlider* width_slider_;
|
||||
IntegerSlider* height_slider_;
|
||||
|
||||
QList<rational> frame_rates_;
|
||||
|
||||
ColorManager* color_manager_;
|
||||
|
||||
InterlacedComboBox* interlaced_combobox_;
|
||||
PixelAspectRatioComboBox* pixel_aspect_combobox_;
|
||||
|
||||
int threads_;
|
||||
|
||||
QString pix_fmt_;
|
||||
|
||||
private slots:
|
||||
void MaintainAspectRatioChanged(bool val);
|
||||
|
||||
|
||||
@@ -22,11 +22,13 @@
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <OpenColorIO/OpenColorIO.h>
|
||||
namespace OCIO = OCIO_NAMESPACE::v1;
|
||||
|
||||
#include "core.h"
|
||||
#include "project/item/footage/footage.h"
|
||||
#include "project/project.h"
|
||||
#include "undo/undostack.h"
|
||||
@@ -41,6 +43,23 @@ VideoStreamProperties::VideoStreamProperties(ImageStreamPtr stream) :
|
||||
|
||||
int row = 0;
|
||||
|
||||
video_layout->addWidget(new QLabel(tr("Pixel Aspect:")), row, 0);
|
||||
|
||||
pixel_aspect_combo_ = new PixelAspectRatioComboBox();
|
||||
pixel_aspect_combo_->SetPixelAspectRatio(stream->pixel_aspect_ratio());
|
||||
video_layout->addWidget(pixel_aspect_combo_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
video_layout->addWidget(new QLabel(tr("Interlacing:")), row, 0);
|
||||
|
||||
video_interlace_combo_ = new InterlacedComboBox();
|
||||
video_interlace_combo_->SetInterlaceMode(stream->interlacing());
|
||||
|
||||
video_layout->addWidget(video_interlace_combo_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
video_layout->addWidget(new QLabel(tr("Color Space:")), row, 0);
|
||||
|
||||
video_color_space_ = new QComboBox();
|
||||
@@ -91,6 +110,14 @@ VideoStreamProperties::VideoStreamProperties(ImageStreamPtr stream) :
|
||||
imgseq_end_time_->SetValue(video_stream->start_time() + video_stream->duration() - 1);
|
||||
imgseq_layout->addWidget(imgseq_end_time_, imgseq_row, 1);
|
||||
|
||||
imgseq_row++;
|
||||
|
||||
imgseq_layout->addWidget(new QLabel(tr("Frame Rate:")), imgseq_row, 0);
|
||||
|
||||
imgseq_frame_rate_ = new FrameRateComboBox();
|
||||
imgseq_frame_rate_->SetFrameRate(video_stream->frame_rate());
|
||||
imgseq_layout->addWidget(imgseq_frame_rate_, imgseq_row, 1);
|
||||
|
||||
video_layout->addWidget(imgseq_group, row, 0, 1, 2);
|
||||
}
|
||||
}
|
||||
@@ -104,11 +131,15 @@ void VideoStreamProperties::Accept(QUndoCommand *parent)
|
||||
}
|
||||
|
||||
if (video_premultiply_alpha_->isChecked() != stream_->premultiplied_alpha()
|
||||
|| set_colorspace != stream_->colorspace(false)) {
|
||||
|| set_colorspace != stream_->colorspace(false)
|
||||
|| static_cast<VideoParams::Interlacing>(video_interlace_combo_->currentIndex()) != stream_->interlacing()
|
||||
|| pixel_aspect_combo_->GetPixelAspectRatio() != stream_->pixel_aspect_ratio()) {
|
||||
|
||||
new VideoStreamChangeCommand(stream_,
|
||||
video_premultiply_alpha_->isChecked(),
|
||||
set_colorspace,
|
||||
static_cast<VideoParams::Interlacing>(video_interlace_combo_->currentIndex()),
|
||||
pixel_aspect_combo_->GetPixelAspectRatio(),
|
||||
parent);
|
||||
}
|
||||
|
||||
@@ -118,10 +149,12 @@ void VideoStreamProperties::Accept(QUndoCommand *parent)
|
||||
int64_t new_dur = imgseq_end_time_->GetValue() - imgseq_start_time_->GetValue() + 1;
|
||||
|
||||
if (video_stream->start_time() != imgseq_start_time_->GetValue()
|
||||
|| video_stream->duration() != new_dur) {
|
||||
|| video_stream->duration() != new_dur
|
||||
|| video_stream->frame_rate() != imgseq_frame_rate_->GetFrameRate()) {
|
||||
new ImageSequenceChangeCommand(video_stream,
|
||||
imgseq_start_time_->GetValue(),
|
||||
new_dur,
|
||||
imgseq_frame_rate_->GetFrameRate(),
|
||||
parent);
|
||||
}
|
||||
}
|
||||
@@ -150,11 +183,15 @@ bool VideoStreamProperties::IsImageSequence(ImageStream *stream)
|
||||
VideoStreamProperties::VideoStreamChangeCommand::VideoStreamChangeCommand(ImageStreamPtr stream,
|
||||
bool premultiplied,
|
||||
QString colorspace,
|
||||
VideoParams::Interlacing interlacing,
|
||||
const rational &pixel_ar,
|
||||
QUndoCommand *parent) :
|
||||
UndoCommand(parent),
|
||||
stream_(stream),
|
||||
new_premultiplied_(premultiplied),
|
||||
new_colorspace_(colorspace)
|
||||
new_colorspace_(colorspace),
|
||||
new_interlacing_(interlacing),
|
||||
new_pixel_ar_(pixel_ar)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -167,22 +204,29 @@ void VideoStreamProperties::VideoStreamChangeCommand::redo_internal()
|
||||
{
|
||||
old_premultiplied_ = stream_->premultiplied_alpha();
|
||||
old_colorspace_ = stream_->colorspace(false);
|
||||
old_interlacing_ = stream_->interlacing();
|
||||
old_pixel_ar_ = stream_->pixel_aspect_ratio();
|
||||
|
||||
stream_->set_premultiplied_alpha(new_premultiplied_);
|
||||
stream_->set_colorspace(new_colorspace_);
|
||||
stream_->set_interlacing(new_interlacing_);
|
||||
stream_->set_pixel_aspect_ratio(new_pixel_ar_);
|
||||
}
|
||||
|
||||
void VideoStreamProperties::VideoStreamChangeCommand::undo_internal()
|
||||
{
|
||||
stream_->set_premultiplied_alpha(old_premultiplied_);
|
||||
stream_->set_colorspace(old_colorspace_);
|
||||
stream_->set_interlacing(old_interlacing_);
|
||||
stream_->set_pixel_aspect_ratio(old_pixel_ar_);
|
||||
}
|
||||
|
||||
VideoStreamProperties::ImageSequenceChangeCommand::ImageSequenceChangeCommand(VideoStreamPtr video_stream, int64_t start_index, int64_t duration, QUndoCommand *parent) :
|
||||
VideoStreamProperties::ImageSequenceChangeCommand::ImageSequenceChangeCommand(VideoStreamPtr video_stream, int64_t start_index, int64_t duration, const rational &frame_rate, QUndoCommand *parent) :
|
||||
UndoCommand(parent),
|
||||
video_stream_(video_stream),
|
||||
new_start_index_(start_index),
|
||||
new_duration_(duration)
|
||||
new_duration_(duration),
|
||||
new_frame_rate_(frame_rate)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -198,12 +242,18 @@ void VideoStreamProperties::ImageSequenceChangeCommand::redo_internal()
|
||||
|
||||
old_duration_ = video_stream_->duration();
|
||||
video_stream_->set_duration(new_duration_);
|
||||
|
||||
old_frame_rate_ = video_stream_->frame_rate();
|
||||
video_stream_->set_frame_rate(new_frame_rate_);
|
||||
video_stream_->set_timebase(new_frame_rate_.flipped());
|
||||
}
|
||||
|
||||
void VideoStreamProperties::ImageSequenceChangeCommand::undo_internal()
|
||||
{
|
||||
video_stream_->set_start_time(old_start_index_);
|
||||
video_stream_->set_duration(old_duration_);
|
||||
video_stream_->set_frame_rate(old_frame_rate_);
|
||||
video_stream_->set_timebase(old_frame_rate_.flipped());
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "streamproperties.h"
|
||||
#include "undo/undocommand.h"
|
||||
#include "widget/slider/integerslider.h"
|
||||
#include "widget/standardcombos/standardcombos.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -58,6 +59,11 @@ private:
|
||||
*/
|
||||
QComboBox* video_color_space_;
|
||||
|
||||
/**
|
||||
* @brief Setting for video interlacing
|
||||
*/
|
||||
InterlacedComboBox* video_interlace_combo_;
|
||||
|
||||
/**
|
||||
* @brief Sets the start index for image sequences
|
||||
*/
|
||||
@@ -68,11 +74,23 @@ private:
|
||||
*/
|
||||
IntegerSlider* imgseq_end_time_;
|
||||
|
||||
/**
|
||||
* @brief Sets the frame rate for image sequences
|
||||
*/
|
||||
FrameRateComboBox* imgseq_frame_rate_;
|
||||
|
||||
/**
|
||||
* @brief Sets the pixel aspect ratio of the stream
|
||||
*/
|
||||
PixelAspectRatioComboBox* pixel_aspect_combo_;
|
||||
|
||||
class VideoStreamChangeCommand : public UndoCommand {
|
||||
public:
|
||||
VideoStreamChangeCommand(ImageStreamPtr stream,
|
||||
bool premultiplied,
|
||||
QString colorspace,
|
||||
VideoParams::Interlacing interlacing,
|
||||
const rational& pixel_ar,
|
||||
QUndoCommand* parent = nullptr);
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
@@ -86,9 +104,13 @@ private:
|
||||
|
||||
bool new_premultiplied_;
|
||||
QString new_colorspace_;
|
||||
VideoParams::Interlacing new_interlacing_;
|
||||
rational new_pixel_ar_;
|
||||
|
||||
bool old_premultiplied_;
|
||||
QString old_colorspace_;
|
||||
VideoParams::Interlacing old_interlacing_;
|
||||
rational old_pixel_ar_;
|
||||
|
||||
};
|
||||
|
||||
@@ -97,6 +119,7 @@ private:
|
||||
ImageSequenceChangeCommand(VideoStreamPtr video_stream,
|
||||
int64_t start_index,
|
||||
int64_t duration,
|
||||
const rational& frame_rate,
|
||||
QUndoCommand* parent = nullptr);
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
@@ -114,7 +137,11 @@ private:
|
||||
int64_t new_duration_;
|
||||
int64_t old_duration_;
|
||||
|
||||
rational new_frame_rate_;
|
||||
rational old_frame_rate_;
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -44,19 +44,21 @@ PreferencesAppearanceTab::PreferencesAppearanceTab()
|
||||
// Appearance -> Theme
|
||||
appearance_layout->addWidget(new QLabel(tr("Theme")), row, 0);
|
||||
|
||||
style_ = new QComboBox();
|
||||
style_combobox_ = new QComboBox();
|
||||
|
||||
style_list_ = StyleManager::ListInternal();
|
||||
{
|
||||
const QMap<QString, QString>& themes = StyleManager::available_themes();
|
||||
QMap<QString, QString>::const_iterator i;
|
||||
for (i=themes.cbegin(); i!=themes.cend(); i++) {
|
||||
style_combobox_->addItem(i.value(), i.key());
|
||||
|
||||
foreach (const StyleDescriptor& s, style_list_) {
|
||||
style_->addItem(s.name(), s.path());
|
||||
|
||||
if (s.path() == StyleManager::GetStyle()) {
|
||||
style_->setCurrentIndex(style_->count()-1);
|
||||
if (StyleManager::GetStyle() == i.key()) {
|
||||
style_combobox_->setCurrentIndex(style_combobox_->count()-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
appearance_layout->addWidget(style_, row, 1);
|
||||
appearance_layout->addWidget(style_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
@@ -89,7 +91,7 @@ PreferencesAppearanceTab::PreferencesAppearanceTab()
|
||||
|
||||
void PreferencesAppearanceTab::Accept()
|
||||
{
|
||||
QString style_path = style_->currentData().toString();
|
||||
QString style_path = style_combobox_->currentData().toString();
|
||||
|
||||
if (style_path != StyleManager::GetStyle()) {
|
||||
StyleManager::SetStyle(style_path);
|
||||
|
||||
@@ -49,14 +49,7 @@ private:
|
||||
/**
|
||||
* @brief UI widget for selecting the current UI style
|
||||
*/
|
||||
QComboBox* style_;
|
||||
|
||||
/**
|
||||
* @brief List of internal styles
|
||||
*/
|
||||
QList<StyleDescriptor> style_list_;
|
||||
|
||||
QString custom_style_path_;
|
||||
QComboBox* style_combobox_;
|
||||
|
||||
QList<QColor> colors_;
|
||||
|
||||
|
||||
@@ -27,12 +27,15 @@
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "render/diskmanager.h"
|
||||
#include "common/filefunctions.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
PreferencesDiskTab::PreferencesDiskTab()
|
||||
{
|
||||
// Get default disk cache folder
|
||||
default_disk_cache_folder_ = DiskManager::instance()->GetDefaultCacheFolder();
|
||||
|
||||
QVBoxLayout* outer_layout = new QVBoxLayout(this);
|
||||
|
||||
QGroupBox* disk_management_group = new QGroupBox(tr("Disk Management"));
|
||||
@@ -44,37 +47,19 @@ PreferencesDiskTab::PreferencesDiskTab()
|
||||
|
||||
disk_management_layout->addWidget(new QLabel(tr("Disk Cache Location:")), row, 0);
|
||||
|
||||
disk_cache_location_ = new QLineEdit();
|
||||
disk_cache_location_->setText(Config::Current()["DiskCachePath"].toString());
|
||||
connect(disk_cache_location_, &QLineEdit::textChanged, this, &PreferencesDiskTab::DiskCacheLineEditChanged);
|
||||
disk_cache_location_ = new PathWidget(default_disk_cache_folder_->GetPath());
|
||||
disk_management_layout->addWidget(disk_cache_location_, row, 1);
|
||||
|
||||
QPushButton* browse_btn = new QPushButton(tr("Browse"));
|
||||
connect(browse_btn, &QPushButton::clicked, this, &PreferencesDiskTab::BrowseDiskCachePath);
|
||||
disk_management_layout->addWidget(browse_btn, row, 2);
|
||||
|
||||
row++;
|
||||
|
||||
disk_management_layout->addWidget(new QLabel(tr("Maximum Disk Cache:")), row, 0);
|
||||
|
||||
maximum_cache_slider_ = new FloatSlider();
|
||||
maximum_cache_slider_->SetFormat(tr("%1 GB"));
|
||||
maximum_cache_slider_->SetMinimum(1.0);
|
||||
maximum_cache_slider_->SetValue(Config::Current()["DiskCacheSize"].toDouble());
|
||||
disk_management_layout->addWidget(maximum_cache_slider_, row, 1, 1, 2);
|
||||
QPushButton* disk_cache_settings_btn = new QPushButton(tr("Disk Cache Settings"));
|
||||
connect(disk_cache_settings_btn, &QPushButton::clicked, this, [this](){
|
||||
DiskManager::instance()->ShowDiskCacheSettingsDialog(disk_cache_location_->text(), this);
|
||||
});
|
||||
disk_management_layout->addWidget(disk_cache_settings_btn, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
clear_cache_btn_ = new QPushButton(tr("Clear Disk Cache"));
|
||||
connect(clear_cache_btn_, &QPushButton::clicked, this, &PreferencesDiskTab::ClearDiskCache);
|
||||
disk_management_layout->addWidget(clear_cache_btn_, row, 1, 1, 2);
|
||||
|
||||
row++;
|
||||
|
||||
clear_disk_cache_ = new QCheckBox(tr("Automatically clear disk cache on close"));
|
||||
clear_disk_cache_->setChecked(Config::Current()["ClearDiskCacheOnClose"].toBool());
|
||||
disk_management_layout->addWidget(clear_disk_cache_, row, 1, 1, 2);
|
||||
|
||||
QGroupBox* cache_behavior = new QGroupBox(tr("Cache Behavior"));
|
||||
outer_layout->addWidget(cache_behavior);
|
||||
QGridLayout* cache_behavior_layout = new QGridLayout(cache_behavior);
|
||||
@@ -100,53 +85,36 @@ PreferencesDiskTab::PreferencesDiskTab()
|
||||
outer_layout->addStretch();
|
||||
}
|
||||
|
||||
bool PreferencesDiskTab::Validate()
|
||||
{
|
||||
if (disk_cache_location_->text() != default_disk_cache_folder_->GetPath()) {
|
||||
// Disk cache location is changing
|
||||
|
||||
// Check if the user is okay with invalidating the current cache
|
||||
if (!DiskManager::ShowDiskCacheChangeConfirmationDialog(this)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check validity of the new path
|
||||
if (!FileFunctions::DirectoryIsValid(disk_cache_location_->text(), true)) {
|
||||
QMessageBox::critical(this,
|
||||
tr("Disk Cache"),
|
||||
tr("Failed to set disk cache location. Access was denied."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PreferencesDiskTab::Accept()
|
||||
{
|
||||
Config::Current()["DiskCachePath"] = disk_cache_location_->text();
|
||||
Config::Current()["DiskCacheSize"] = maximum_cache_slider_->GetValue();
|
||||
Config::Current()["ClearDiskCacheOnClose"] = clear_disk_cache_->isChecked();
|
||||
if (disk_cache_location_->text() != default_disk_cache_folder_->GetPath()) {
|
||||
default_disk_cache_folder_->SetPath(disk_cache_location_->text());
|
||||
}
|
||||
|
||||
Config::Current()["DiskCacheBehind"] = QVariant::fromValue(rational::fromDouble(cache_behind_slider_->GetValue()));
|
||||
Config::Current()["DiskCacheAhead"] = QVariant::fromValue(rational::fromDouble(cache_ahead_slider_->GetValue()));
|
||||
}
|
||||
|
||||
void PreferencesDiskTab::DiskCacheLineEditChanged()
|
||||
{
|
||||
QString entered_dir = disk_cache_location_->text();
|
||||
|
||||
if (!entered_dir.isEmpty() && !QDir(entered_dir).exists()) {
|
||||
disk_cache_location_->setStyleSheet(QStringLiteral("color: red;"));
|
||||
} else {
|
||||
disk_cache_location_->setStyleSheet(QString());
|
||||
}
|
||||
}
|
||||
|
||||
void PreferencesDiskTab::BrowseDiskCachePath()
|
||||
{
|
||||
QString dir = QFileDialog::getExistingDirectory(this, tr("Browse for disk cache path"), disk_cache_location_->text());
|
||||
|
||||
if (!dir.isEmpty()) {
|
||||
disk_cache_location_->setText(dir);
|
||||
}
|
||||
}
|
||||
|
||||
void PreferencesDiskTab::ClearDiskCache()
|
||||
{
|
||||
if (QMessageBox::question(this,
|
||||
tr("Clear Disk Cache"),
|
||||
tr("Are you sure you want to clear the disk cache in '%1'?").arg(Config::Current()["DiskCachePath"].toString()),
|
||||
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
|
||||
clear_cache_btn_->setEnabled(false);
|
||||
|
||||
if (DiskManager::instance()->ClearDiskCache(false)) {
|
||||
clear_cache_btn_->setText(tr("Disk Cache Cleared"));
|
||||
} else {
|
||||
QMessageBox::information(this,
|
||||
tr("Clear Disk Cache"),
|
||||
tr("Disk cache failed to fully clear. You may have to delete the cache files manually."),
|
||||
QMessageBox::Ok);
|
||||
clear_cache_btn_->setText(tr("Disk Cache Partially Cleared"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -26,7 +26,9 @@
|
||||
#include <QPushButton>
|
||||
|
||||
#include "preferencestab.h"
|
||||
#include "render/diskmanager.h"
|
||||
#include "widget/slider/floatslider.h"
|
||||
#include "widget/path/pathwidget.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -36,27 +38,18 @@ class PreferencesDiskTab : public PreferencesTab
|
||||
public:
|
||||
PreferencesDiskTab();
|
||||
|
||||
virtual bool Validate() override;
|
||||
|
||||
virtual void Accept() override;
|
||||
|
||||
private:
|
||||
QLineEdit* disk_cache_location_;
|
||||
|
||||
FloatSlider* maximum_cache_slider_;
|
||||
PathWidget* disk_cache_location_;
|
||||
|
||||
FloatSlider* cache_ahead_slider_;
|
||||
|
||||
FloatSlider* cache_behind_slider_;
|
||||
|
||||
QCheckBox* clear_disk_cache_;
|
||||
|
||||
QPushButton* clear_cache_btn_;
|
||||
|
||||
private slots:
|
||||
void DiskCacheLineEditChanged();
|
||||
|
||||
void BrowseDiskCachePath();
|
||||
|
||||
void ClearDiskCache();
|
||||
DiskCacheFolder* default_disk_cache_folder_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "projectproperties.h"
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileDialog>
|
||||
#include <QGroupBox>
|
||||
@@ -29,9 +30,11 @@
|
||||
#include <OpenColorIO/OpenColorIO.h>
|
||||
namespace OCIO = OCIO_NAMESPACE::v1;
|
||||
|
||||
#include "common/filefunctions.h"
|
||||
#include "config/config.h"
|
||||
#include "core.h"
|
||||
#include "render/colormanager.h"
|
||||
#include "render/diskmanager.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -51,7 +54,10 @@ ProjectPropertiesDialog::ProjectPropertiesDialog(Project* p, QWidget *parent) :
|
||||
// Color management group
|
||||
QWidget* color_group = new QWidget();
|
||||
|
||||
QGridLayout* color_layout = new QGridLayout(color_group);
|
||||
QVBoxLayout* color_outer_layout = new QVBoxLayout(color_group);
|
||||
|
||||
QGridLayout* color_layout = new QGridLayout();
|
||||
color_outer_layout->addLayout(color_layout);
|
||||
|
||||
int row = 0;
|
||||
|
||||
@@ -80,26 +86,58 @@ ProjectPropertiesDialog::ProjectPropertiesDialog(Project* p, QWidget *parent) :
|
||||
OCIOFilenameUpdated();
|
||||
|
||||
tabs->addTab(color_group, tr("Color Management"));
|
||||
|
||||
color_outer_layout->addStretch();
|
||||
}
|
||||
|
||||
|
||||
|
||||
{
|
||||
// Paths group
|
||||
QWidget* paths_group = new QWidget();
|
||||
// Cache group
|
||||
QWidget* cache_group = new QWidget();
|
||||
|
||||
QGridLayout* paths_layout = new QGridLayout(paths_group);
|
||||
QVBoxLayout* cache_layout = new QVBoxLayout(cache_group);
|
||||
|
||||
cache_path_ = new PathWidget(working_project_->cache_path(), this);
|
||||
QButtonGroup* disk_cache_btn_group = new QButtonGroup();
|
||||
|
||||
int row = 0;
|
||||
disk_cache_use_default_btn_ = new QRadioButton(tr("Use Default Location"));
|
||||
disk_cache_store_alongside_project_btn_ = new QRadioButton(tr("Store Alongside Project"));
|
||||
disk_cache_use_custom_btn_ = new QRadioButton(tr("Use Custom Location:"));
|
||||
|
||||
paths_layout->addWidget(new QLabel(tr("Cache Path:")), row, 0);
|
||||
paths_layout->addWidget(cache_path_->path_edit(), row, 1);
|
||||
paths_layout->addWidget(cache_path_->browse_btn(), row, 2);
|
||||
paths_layout->addWidget(cache_path_->default_box(), row, 3);
|
||||
disk_cache_btn_group->addButton(disk_cache_use_default_btn_);
|
||||
disk_cache_btn_group->addButton(disk_cache_store_alongside_project_btn_);
|
||||
disk_cache_btn_group->addButton(disk_cache_use_custom_btn_);
|
||||
|
||||
tabs->addTab(paths_group, tr("Paths"));
|
||||
cache_layout->addWidget(disk_cache_use_default_btn_);
|
||||
cache_layout->addWidget(disk_cache_store_alongside_project_btn_);
|
||||
cache_layout->addWidget(disk_cache_use_custom_btn_);
|
||||
|
||||
cache_path_ = new PathWidget(working_project_->cache_path(false), this);
|
||||
cache_path_->setEnabled(false);
|
||||
cache_layout->addWidget(cache_path_);
|
||||
|
||||
connect(disk_cache_use_custom_btn_, &QRadioButton::toggled, cache_path_, &PathWidget::setEnabled);
|
||||
|
||||
if (working_project_->cache_path(false).isEmpty()) {
|
||||
disk_cache_use_default_btn_->setChecked(true);
|
||||
} else {
|
||||
disk_cache_use_custom_btn_->setChecked(true);
|
||||
}
|
||||
|
||||
cache_layout->addWidget(cache_path_);
|
||||
|
||||
QPushButton* disk_cache_settings_btn = new QPushButton(tr("Disk Cache Settings"));
|
||||
connect(disk_cache_settings_btn, &QPushButton::clicked, this, [this](){
|
||||
if (disk_cache_use_default_btn_->isChecked()) {
|
||||
DiskManager::instance()->ShowDiskCacheSettingsDialog(DiskManager::instance()->GetDefaultCacheFolder(), this);
|
||||
} else if (disk_cache_store_alongside_project_btn_->isChecked()) {
|
||||
// FIXME:
|
||||
QMessageBox::information(this, QString(), tr("\"Store alignside project\" functionality not implemented yet"));
|
||||
} else {
|
||||
DiskManager::instance()->ShowDiskCacheSettingsDialog(cache_path_->text(), this);
|
||||
}
|
||||
});
|
||||
cache_layout->addWidget(disk_cache_settings_btn);
|
||||
|
||||
tabs->addTab(cache_group, tr("Disk Cache"));
|
||||
}
|
||||
|
||||
QDialogButtonBox* dialog_btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
|
||||
@@ -122,18 +160,40 @@ void ProjectPropertiesDialog::accept()
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cache_path_->PathIsValid(true)) {
|
||||
QMessageBox mb(this);
|
||||
mb.setWindowModality(Qt::WindowModal);
|
||||
mb.setIcon(QMessageBox::Critical);
|
||||
mb.setWindowTitle(tr("Invalid path"));
|
||||
mb.setText(tr("The cache path is invalid. Please check it and try again."));
|
||||
mb.addButton(QMessageBox::Ok);
|
||||
mb.exec();
|
||||
QString new_cache_path;
|
||||
|
||||
if (disk_cache_use_default_btn_->isChecked()) {
|
||||
// Keep new cache path empty, which means default
|
||||
} else if (disk_cache_store_alongside_project_btn_->isChecked()) {
|
||||
// FIXME:
|
||||
QMessageBox::information(this, QString(), tr("\"Store alignside project\" functionality not implemented yet"));
|
||||
return;
|
||||
} else {
|
||||
if (!FileFunctions::DirectoryIsValid(cache_path_->text(), true)) {
|
||||
QMessageBox mb(this);
|
||||
mb.setWindowModality(Qt::WindowModal);
|
||||
mb.setIcon(QMessageBox::Critical);
|
||||
mb.setWindowTitle(tr("Invalid path"));
|
||||
mb.setText(tr("The cache path is invalid. Please check it and try again."));
|
||||
mb.addButton(QMessageBox::Ok);
|
||||
mb.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set new path to the text as entered
|
||||
new_cache_path = cache_path_->text();
|
||||
}
|
||||
|
||||
working_project_->set_cache_path(cache_path_->path_edit()->text());
|
||||
if (new_cache_path != working_project_->cache_path(false)) {
|
||||
// Check if the user is okay with invalidating the current cache
|
||||
if (!DiskManager::ShowDiskCacheChangeConfirmationDialog(this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
working_project_->set_cache_path(new_cache_path);
|
||||
|
||||
emit DiskManager::instance()->InvalidateProject(working_project_);
|
||||
}
|
||||
|
||||
// This should ripple changes throughout the program that the color config has changed, therefore must be done last
|
||||
working_project_->color_manager()->SetConfigAndDefaultInput(ocio_filename_->text(),
|
||||
@@ -176,7 +236,6 @@ void ProjectPropertiesDialog::OCIOFilenameUpdated()
|
||||
default_input_colorspace_->setCurrentIndex(default_input_colorspace_->count()-1);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (OCIO::Exception& e) {
|
||||
ocio_config_is_valid_ = false;
|
||||
ocio_filename_->setStyleSheet(QStringLiteral("QLineEdit {color: red;}"));
|
||||
@@ -184,54 +243,4 @@ void ProjectPropertiesDialog::OCIOFilenameUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
PathWidget::PathWidget(const QString &path, QWidget *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
path_edit_ = new QLineEdit();
|
||||
path_edit_->setText(path);
|
||||
connect(path_edit_, &QLineEdit::textChanged, this, &PathWidget::LineEditChanged);
|
||||
|
||||
default_box_ = new QCheckBox(tr("Default"));
|
||||
|
||||
browse_btn_ = new QPushButton(tr("Browse"));
|
||||
|
||||
connect(default_box_, &QCheckBox::toggled, this, &PathWidget::DefaultToggled);
|
||||
|
||||
default_box_->setChecked(path.isEmpty());
|
||||
|
||||
connect(browse_btn_, &QPushButton::clicked, this, &PathWidget::BrowseClicked);
|
||||
}
|
||||
|
||||
bool PathWidget::PathIsValid(bool try_to_create) const
|
||||
{
|
||||
return default_box_->isChecked()
|
||||
|| QDir(path_edit_->text()).exists()
|
||||
|| (try_to_create && QDir(path_edit_->text()).mkpath(QStringLiteral(".")));
|
||||
}
|
||||
|
||||
void PathWidget::DefaultToggled(bool e)
|
||||
{
|
||||
path_edit_->setEnabled(!e);
|
||||
}
|
||||
|
||||
void PathWidget::BrowseClicked()
|
||||
{
|
||||
QString dir = QFileDialog::getExistingDirectory(static_cast<QWidget*>(parent()),
|
||||
tr("Browse for path"),
|
||||
path_edit_->text());
|
||||
|
||||
if (!dir.isEmpty()) {
|
||||
path_edit_->setText(dir);
|
||||
}
|
||||
}
|
||||
|
||||
void PathWidget::LineEditChanged()
|
||||
{
|
||||
if (PathIsValid(false)) {
|
||||
path_edit_->setStyleSheet(QString());
|
||||
} else {
|
||||
path_edit_->setStyleSheet(QStringLiteral("QLineEdit {color: red;}"));
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -26,48 +26,13 @@
|
||||
#include <QDialog>
|
||||
#include <QGridLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QRadioButton>
|
||||
|
||||
#include "project/project.h"
|
||||
#include "widget/path/pathwidget.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class PathWidget : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PathWidget(const QString& path,
|
||||
QWidget* parent = nullptr);
|
||||
|
||||
bool PathIsValid(bool try_to_create) const;
|
||||
|
||||
QLineEdit* path_edit() const {
|
||||
return path_edit_;
|
||||
}
|
||||
|
||||
QCheckBox* default_box() const {
|
||||
return default_box_;
|
||||
}
|
||||
|
||||
QPushButton* browse_btn() const {
|
||||
return browse_btn_;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void DefaultToggled(bool e);
|
||||
|
||||
void BrowseClicked();
|
||||
|
||||
void LineEditChanged();
|
||||
|
||||
private:
|
||||
QLineEdit* path_edit_;
|
||||
|
||||
QCheckBox* default_box_;
|
||||
|
||||
QPushButton* browse_btn_;
|
||||
|
||||
};
|
||||
|
||||
class ProjectPropertiesDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -89,6 +54,12 @@ private:
|
||||
QString ocio_config_error_;
|
||||
|
||||
PathWidget* cache_path_;
|
||||
|
||||
QRadioButton* disk_cache_use_default_btn_;
|
||||
|
||||
QRadioButton* disk_cache_store_alongside_project_btn_;
|
||||
|
||||
QRadioButton* disk_cache_use_custom_btn_;
|
||||
|
||||
private slots:
|
||||
void BrowseForOCIOConfig();
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "richtext.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
@@ -29,7 +30,7 @@
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
RichTextDialog::RichTextDialog(const QString &start, QWidget* parent) :
|
||||
RichTextDialog::RichTextDialog(QString start, QWidget* parent) :
|
||||
QDialog(parent)
|
||||
{
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
@@ -37,33 +38,32 @@ RichTextDialog::RichTextDialog(const QString &start, QWidget* parent) :
|
||||
// Create toolbar
|
||||
QHBoxLayout* toolbar_layout = new QHBoxLayout();
|
||||
|
||||
bold_btn_ = CreateToolbarButton(tr("B"), tr("Bold"));
|
||||
bold_btn_ = CreateToolbarButton(tr("B"), tr("Bold"), {QStringLiteral("b"), QStringLiteral("strong")});
|
||||
toolbar_layout->addWidget(bold_btn_);
|
||||
italic_btn_ = CreateToolbarButton(tr("I"), tr("Italic"));
|
||||
italic_btn_ = CreateToolbarButton(tr("I"), tr("Italic"), {QStringLiteral("i"), QStringLiteral("em")});
|
||||
toolbar_layout->addWidget(italic_btn_);
|
||||
underline_btn_ = CreateToolbarButton(tr("U"), tr("Underline"));
|
||||
underline_btn_ = CreateToolbarButton(tr("U"), tr("Underline"), {QStringLiteral("u")});
|
||||
toolbar_layout->addWidget(underline_btn_);
|
||||
strikeout_btn_ = CreateToolbarButton(tr("S"), tr("Strikethrough"));
|
||||
strikeout_btn_ = CreateToolbarButton(tr("S"), tr("Strikethrough"), {QStringLiteral("strike")});
|
||||
toolbar_layout->addWidget(strikeout_btn_);
|
||||
font_combo_ = new QFontComboBox();
|
||||
font_combo_->setToolTip(tr("Font Family"));
|
||||
toolbar_layout->addWidget(font_combo_);
|
||||
size_slider_ = new FloatSlider();
|
||||
size_slider_->SetMinimum(0.1);
|
||||
size_slider_->SetLadderEnabled(true);
|
||||
size_slider_->SetLadderElementCount(1);
|
||||
size_slider_->setToolTip(tr("Font Size"));
|
||||
toolbar_layout->addWidget(size_slider_);
|
||||
|
||||
toolbar_layout->addStretch();
|
||||
|
||||
left_align_btn_ = CreateToolbarButton(tr("L"), tr("Left Align"));
|
||||
left_align_btn_ = CreateToolbarButton(tr("L"), tr("Left Align"), {});
|
||||
toolbar_layout->addWidget(left_align_btn_);
|
||||
center_align_btn_ = CreateToolbarButton(tr("C"), tr("Center Align"));
|
||||
center_align_btn_ = CreateToolbarButton(tr("C"), tr("Center Align"), {});
|
||||
toolbar_layout->addWidget(center_align_btn_);
|
||||
right_align_btn_ = CreateToolbarButton(tr("R"), tr("Right Align"));
|
||||
right_align_btn_ = CreateToolbarButton(tr("R"), tr("Right Align"), {});
|
||||
toolbar_layout->addWidget(right_align_btn_);
|
||||
justify_align_btn_ = CreateToolbarButton(tr("J"), tr("Justify Align"));
|
||||
justify_align_btn_ = CreateToolbarButton(tr("J"), tr("Justify Align"), {});
|
||||
toolbar_layout->addWidget(justify_align_btn_);
|
||||
|
||||
layout->addLayout(toolbar_layout);
|
||||
@@ -72,7 +72,8 @@ RichTextDialog::RichTextDialog(const QString &start, QWidget* parent) :
|
||||
text_edit_ = new QTextEdit();
|
||||
text_edit_->setWordWrapMode(QTextOption::NoWrap);
|
||||
connect(text_edit_, &QTextEdit::cursorPositionChanged, this, &RichTextDialog::UpdateButtons);
|
||||
text_edit_->document()->setHtml(start);
|
||||
start.replace(QStringLiteral("<br>"), QStringLiteral("\n"));
|
||||
text_edit_->document()->setPlainText(start);
|
||||
layout->addWidget(text_edit_);
|
||||
|
||||
// Create buttons
|
||||
@@ -82,16 +83,7 @@ RichTextDialog::RichTextDialog(const QString &start, QWidget* parent) :
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &RichTextDialog::reject);
|
||||
|
||||
// Connect font buttons
|
||||
connect(bold_btn_, &QPushButton::clicked, this, [this](bool e){
|
||||
text_edit_->setFontWeight(e ? QFont::Bold : QFont::Normal);
|
||||
});
|
||||
connect(italic_btn_, &QPushButton::clicked, text_edit_, &QTextEdit::setFontItalic);
|
||||
connect(underline_btn_, &QPushButton::clicked, text_edit_, &QTextEdit::setFontUnderline);
|
||||
connect(strikeout_btn_, &QPushButton::clicked, this, [this](bool e){
|
||||
QFont current_font = text_edit_->currentFont();
|
||||
current_font.setStrikeOut(e);
|
||||
text_edit_->setCurrentFont(current_font);
|
||||
});
|
||||
/*
|
||||
connect(size_slider_, &FloatSlider::ValueChanged, text_edit_, &QTextEdit::setFontPointSize);
|
||||
connect(left_align_btn_, &QPushButton::clicked, this, [this](){
|
||||
text_edit_->setAlignment(Qt::AlignLeft);
|
||||
@@ -114,23 +106,221 @@ RichTextDialog::RichTextDialog(const QString &start, QWidget* parent) :
|
||||
connect(font_combo_, &QFontComboBox::currentTextChanged, this, [this](const QString& s){
|
||||
text_edit_->setFontFamily(s);
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
QPushButton *RichTextDialog::CreateToolbarButton(const QString& label, const QString& tooltip)
|
||||
QPushButton *RichTextDialog::CreateToolbarButton(const QString& label, const QString& tooltip, const QStringList &tags)
|
||||
{
|
||||
QPushButton* btn = new QPushButton(label);
|
||||
btn->setCheckable(true);
|
||||
btn->setToolTip(tooltip);
|
||||
btn->setFixedWidth(btn->sizeHint().height());
|
||||
|
||||
if (!tags.isEmpty()) {
|
||||
btn->setProperty("tag", tags);
|
||||
connect(btn, &QPushButton::clicked, this, &RichTextDialog::TagButtonToggled);
|
||||
}
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
int SnapPositionOutsideTags(const QString& text, int pos)
|
||||
{
|
||||
// Look for closest opening bracket before position
|
||||
int opening_bracket_pos = text.lastIndexOf('<', pos - text.size() -1);
|
||||
|
||||
// Look for closest closing bracket before position
|
||||
int closing_bracket_pos = text.indexOf('>', opening_bracket_pos);
|
||||
|
||||
if (opening_bracket_pos > -1 && closing_bracket_pos >= pos) {
|
||||
// Must be inside an angle bracket, snap to closest position outside of bracket
|
||||
closing_bracket_pos++;
|
||||
|
||||
if (pos - opening_bracket_pos < closing_bracket_pos - pos) {
|
||||
// Closer to opening bracket pos
|
||||
return opening_bracket_pos;
|
||||
} else {
|
||||
return closing_bracket_pos;
|
||||
}
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
void RichTextDialog::SetTags(const QStringList &t, bool enabled)
|
||||
{
|
||||
QString s = text_edit_->toPlainText();
|
||||
|
||||
int selection_start, selection_end;
|
||||
|
||||
{
|
||||
QTextCursor c = text_edit_->textCursor();
|
||||
|
||||
if (c.hasSelection()) {
|
||||
selection_start = SnapPositionOutsideTags(s, c.selectionStart());
|
||||
selection_end = SnapPositionOutsideTags(s, c.selectionEnd());
|
||||
|
||||
c.clearSelection();
|
||||
c.setPosition(selection_start, QTextCursor::MoveAnchor);
|
||||
c.setPosition(selection_end, QTextCursor::KeepAnchor);
|
||||
} else {
|
||||
selection_start = SnapPositionOutsideTags(s, c.position());
|
||||
selection_end = selection_start;
|
||||
c.setPosition(selection_start, QTextCursor::MoveAnchor);
|
||||
}
|
||||
|
||||
text_edit_->setTextCursor(c);
|
||||
}
|
||||
|
||||
QString open_tag = CreateOpeningTag(t.first());
|
||||
QString close_tag = CreateClosingTag(t.first());
|
||||
|
||||
// Insert tags
|
||||
QString new_text;
|
||||
|
||||
if (!enabled) {
|
||||
std::swap(open_tag, close_tag);
|
||||
}
|
||||
|
||||
bool open_tag_cancels_out = !QString::compare(s.mid(selection_start - close_tag.size(), close_tag.size()), close_tag, Qt::CaseInsensitive);
|
||||
bool close_tag_cancels_out = !QString::compare(s.mid(selection_end, open_tag.size()), open_tag, Qt::CaseInsensitive);
|
||||
|
||||
QString selected_text = text_edit_->textCursor().selectedText();
|
||||
|
||||
if (open_tag_cancels_out && close_tag_cancels_out) {
|
||||
|
||||
// Both tags cancel each other out, simply remove
|
||||
selection_start -= close_tag.size();
|
||||
|
||||
QTextCursor c = text_edit_->textCursor();
|
||||
c.clearSelection();
|
||||
c.setPosition(selection_start, QTextCursor::MoveAnchor);
|
||||
c.setPosition(selection_end + open_tag.size(), QTextCursor::KeepAnchor);
|
||||
text_edit_->setTextCursor(c);
|
||||
|
||||
selection_end -= close_tag.size();
|
||||
|
||||
new_text = selected_text;
|
||||
|
||||
} else if (open_tag_cancels_out) {
|
||||
|
||||
// Open tag cancels out, shift close tag rather than inserting new tags
|
||||
selection_start -= close_tag.size();
|
||||
|
||||
QTextCursor c = text_edit_->textCursor();
|
||||
c.clearSelection();
|
||||
c.setPosition(selection_start, QTextCursor::MoveAnchor);
|
||||
c.setPosition(selection_end, QTextCursor::KeepAnchor);
|
||||
text_edit_->setTextCursor(c);
|
||||
|
||||
selection_end -= close_tag.size();
|
||||
|
||||
new_text = selected_text;
|
||||
new_text.append(close_tag);
|
||||
|
||||
} else if (close_tag_cancels_out) {
|
||||
|
||||
// Close tag cancels out, shift open tag rather than inserting new tags
|
||||
selection_end += open_tag.size();
|
||||
|
||||
QTextCursor c = text_edit_->textCursor();
|
||||
c.clearSelection();
|
||||
c.setPosition(selection_start, QTextCursor::MoveAnchor);
|
||||
c.setPosition(selection_end, QTextCursor::KeepAnchor);
|
||||
text_edit_->setTextCursor(c);
|
||||
|
||||
selection_start += open_tag.size();
|
||||
|
||||
new_text = open_tag;
|
||||
new_text.append(selected_text);
|
||||
|
||||
} else {
|
||||
// Nothing is cancelled out, simply insert tags
|
||||
new_text = QStringLiteral("%1%2%3").arg(open_tag,
|
||||
selected_text,
|
||||
close_tag);
|
||||
|
||||
selection_start += open_tag.size();
|
||||
selection_end += open_tag.size();
|
||||
}
|
||||
|
||||
text_edit_->insertPlainText(new_text);
|
||||
|
||||
text_edit_->setFocus();
|
||||
|
||||
{
|
||||
// Re-select text
|
||||
QTextCursor c = text_edit_->textCursor();
|
||||
|
||||
c.clearSelection();
|
||||
c.setPosition(selection_start, QTextCursor::MoveAnchor);
|
||||
c.setPosition(selection_end, QTextCursor::KeepAnchor);
|
||||
text_edit_->setTextCursor(c);
|
||||
}
|
||||
}
|
||||
|
||||
QString RichTextDialog::CreateOpeningTag(const QString &s)
|
||||
{
|
||||
return QStringLiteral("<%1>").arg(s);
|
||||
}
|
||||
|
||||
QString RichTextDialog::CreateClosingTag(const QString &s)
|
||||
{
|
||||
return QStringLiteral("</%1>").arg(s);
|
||||
}
|
||||
|
||||
void RichTextDialog::UpdateTagButton(QPushButton *btn,
|
||||
const QString &text,
|
||||
int cursor_pos)
|
||||
{
|
||||
QStringList tags = btn->property("tag").toStringList();
|
||||
foreach (const QString& t, tags) {
|
||||
QString opening = CreateOpeningTag(t);
|
||||
QString closing = CreateClosingTag(t);
|
||||
|
||||
int opening_index = text.lastIndexOf(opening,
|
||||
cursor_pos - text.size() - 1,
|
||||
Qt::CaseInsensitive);
|
||||
int closing_index = text.indexOf(closing,
|
||||
opening_index,
|
||||
Qt::CaseInsensitive);
|
||||
|
||||
if (opening_index > -1 && closing_index + closing.size() > cursor_pos) {
|
||||
btn->setChecked(true);
|
||||
btn->setProperty("foundtag", t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
btn->setChecked(false);
|
||||
btn->setProperty("foundtag", QVariant());
|
||||
}
|
||||
|
||||
void RichTextDialog::TagButtonToggled(bool checked)
|
||||
{
|
||||
QPushButton* src = static_cast<QPushButton*>(sender());
|
||||
QStringList tags;
|
||||
|
||||
if (src->property("foundtag").isNull()) {
|
||||
tags = src->property("tag").toStringList();
|
||||
} else {
|
||||
tags = QStringList({src->property("foundtag").toString()});
|
||||
}
|
||||
|
||||
SetTags(tags, checked);
|
||||
}
|
||||
|
||||
void RichTextDialog::UpdateButtons()
|
||||
{
|
||||
bold_btn_->setChecked(text_edit_->fontWeight() > QFont::Normal);
|
||||
italic_btn_->setChecked(text_edit_->fontItalic());
|
||||
underline_btn_->setChecked(text_edit_->fontUnderline());
|
||||
strikeout_btn_->setChecked(text_edit_->currentFont().strikeOut());
|
||||
QString text = text_edit_->toPlainText();
|
||||
int cursor_pos = text_edit_->textCursor().position();
|
||||
|
||||
UpdateTagButton(bold_btn_, text, cursor_pos);
|
||||
UpdateTagButton(italic_btn_, text, cursor_pos);
|
||||
UpdateTagButton(underline_btn_, text, cursor_pos);
|
||||
UpdateTagButton(strikeout_btn_, text, cursor_pos);
|
||||
|
||||
/*
|
||||
|
||||
// Update font family
|
||||
font_combo_->blockSignals(true);
|
||||
@@ -143,6 +333,7 @@ void RichTextDialog::UpdateButtons()
|
||||
center_align_btn_->setChecked(text_edit_->alignment() == Qt::AlignCenter);
|
||||
right_align_btn_->setChecked(text_edit_->alignment() == Qt::AlignRight);
|
||||
justify_align_btn_->setChecked(text_edit_->alignment() == Qt::AlignJustify);
|
||||
*/
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -34,15 +34,31 @@ class RichTextDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
RichTextDialog(const QString& start, QWidget* parent = nullptr);
|
||||
RichTextDialog(QString start, QWidget* parent = nullptr);
|
||||
|
||||
QString text() const
|
||||
{
|
||||
return text_edit_->document()->toHtml("utf-8");
|
||||
QString s = text_edit_->document()->toPlainText();
|
||||
|
||||
// Convert linebreaks
|
||||
s.replace('\n', QStringLiteral("<br>"));
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
QPushButton* CreateToolbarButton(const QString &label, const QString &tooltip);
|
||||
QPushButton* CreateToolbarButton(const QString &label,
|
||||
const QString &tooltip,
|
||||
const QStringList& tags);
|
||||
|
||||
void SetTags(const QStringList& t, bool enabled);
|
||||
|
||||
static QString CreateOpeningTag(const QString& s);
|
||||
static QString CreateClosingTag(const QString& s);
|
||||
|
||||
static void UpdateTagButton(QPushButton* btn,
|
||||
const QString &text,
|
||||
int cursor_pos);
|
||||
|
||||
QFontDatabase font_db_;
|
||||
|
||||
@@ -60,6 +76,8 @@ private:
|
||||
QPushButton* justify_align_btn_;
|
||||
|
||||
private slots:
|
||||
void TagButtonToggled(bool checked);
|
||||
|
||||
void UpdateButtons();
|
||||
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
dialog/sequence/presetmanager.h
|
||||
dialog/sequence/sequence.h
|
||||
dialog/sequence/sequence.cpp
|
||||
dialog/sequence/sequencedialogparametertab.h
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
/***
|
||||
|
||||
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 PRESETMANAGER_H
|
||||
#define PRESETMANAGER_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QObject>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
#include "common/define.h"
|
||||
#include "common/filefunctions.h"
|
||||
#include "common/xmlutils.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class Preset
|
||||
{
|
||||
public:
|
||||
Preset() = default;
|
||||
|
||||
virtual ~Preset(){}
|
||||
|
||||
const QString& GetName() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
void SetName(const QString& s)
|
||||
{
|
||||
name_ = s;
|
||||
}
|
||||
|
||||
virtual void Load(QXmlStreamReader* reader) = 0;
|
||||
|
||||
virtual void Save(QXmlStreamWriter* writer) const = 0;
|
||||
|
||||
private:
|
||||
QString name_;
|
||||
|
||||
};
|
||||
|
||||
using PresetPtr = std::shared_ptr<Preset>;
|
||||
|
||||
template <typename T>
|
||||
class PresetManager
|
||||
{
|
||||
public:
|
||||
PresetManager(QWidget* parent, const QString& preset_name) :
|
||||
preset_name_(preset_name),
|
||||
parent_(parent)
|
||||
{
|
||||
// Load custom preset data from file
|
||||
QFile preset_file(GetCustomPresetFilename());
|
||||
if (preset_file.open(QFile::ReadOnly)) {
|
||||
QXmlStreamReader reader(&preset_file);
|
||||
|
||||
while (XMLReadNextStartElement(&reader)) {
|
||||
if (reader.name() == QStringLiteral("presets")) {
|
||||
while (XMLReadNextStartElement(&reader)) {
|
||||
if (reader.name() == QStringLiteral("preset")) {
|
||||
PresetPtr p = std::make_shared<T>();
|
||||
|
||||
p->Load(&reader);
|
||||
|
||||
custom_preset_data_.append(p);
|
||||
} else {
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
preset_file.close();
|
||||
}
|
||||
}
|
||||
|
||||
~PresetManager()
|
||||
{
|
||||
// Save custom presets to disk
|
||||
QFile preset_file(GetCustomPresetFilename());
|
||||
if (preset_file.open(QFile::WriteOnly)) {
|
||||
QXmlStreamWriter writer(&preset_file);
|
||||
writer.setAutoFormatting(true);
|
||||
|
||||
writer.writeStartDocument();
|
||||
|
||||
writer.writeStartElement(QStringLiteral("presets"));
|
||||
|
||||
foreach (PresetPtr p, custom_preset_data_) {
|
||||
writer.writeStartElement(QStringLiteral("preset"));
|
||||
|
||||
p->Save(&writer);
|
||||
|
||||
writer.writeEndElement(); // preset
|
||||
}
|
||||
|
||||
writer.writeEndElement(); // presets
|
||||
|
||||
writer.writeEndDocument();
|
||||
|
||||
preset_file.close();
|
||||
}
|
||||
}
|
||||
|
||||
QString GetPresetName(QString start) const
|
||||
{
|
||||
bool ok;
|
||||
|
||||
forever {
|
||||
start = QInputDialog::getText(parent_,
|
||||
QCoreApplication::translate("PresetManager", "Save Preset"),
|
||||
QCoreApplication::translate("PresetManager", "Set preset name:"),
|
||||
QLineEdit::Normal,
|
||||
start,
|
||||
&ok);
|
||||
|
||||
if (!ok) {
|
||||
// Dialog cancelled - leave function entirely
|
||||
return QString();
|
||||
}
|
||||
|
||||
if (start.isEmpty()) {
|
||||
// No preset name entered, start loop over
|
||||
QMessageBox::critical(parent_,
|
||||
QCoreApplication::translate("PresetManager", "Invalid preset name"),
|
||||
QCoreApplication::translate("PresetManager", "You must enter a preset name"),
|
||||
QMessageBox::Ok);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
bool SavePreset(PresetPtr preset)
|
||||
{
|
||||
QString preset_name;
|
||||
int existing_preset;
|
||||
|
||||
forever {
|
||||
preset_name = GetPresetName(preset_name);
|
||||
|
||||
if (preset_name.isEmpty()) {
|
||||
// Dialog cancelled - leave function entirely
|
||||
return false;
|
||||
}
|
||||
|
||||
existing_preset = -1;
|
||||
for (int i=0; i<custom_preset_data_.size(); i++) {
|
||||
if (custom_preset_data_.at(i)->GetName() == preset_name) {
|
||||
existing_preset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existing_preset == -1
|
||||
|| QMessageBox::question(parent_,
|
||||
QCoreApplication::translate("PresetManager", "Preset exists"),
|
||||
QCoreApplication::translate("PresetManager",
|
||||
"A preset with this name already exists. "
|
||||
"Would you like to replace it?")) == QMessageBox::Yes) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
preset->SetName(preset_name);
|
||||
|
||||
if (existing_preset >= 0) {
|
||||
custom_preset_data_.replace(existing_preset, preset);
|
||||
return false;
|
||||
} else {
|
||||
custom_preset_data_.append(preset);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
QString GetCustomPresetFilename() const
|
||||
{
|
||||
return QDir(FileFunctions::GetConfigurationLocation()).filePath(preset_name_);
|
||||
}
|
||||
|
||||
PresetPtr GetPreset(int index)
|
||||
{
|
||||
return custom_preset_data_.at(index);
|
||||
}
|
||||
|
||||
void DeletePreset(int index)
|
||||
{
|
||||
custom_preset_data_.removeAt(index);
|
||||
}
|
||||
|
||||
int GetNumberOfPresets() const
|
||||
{
|
||||
return custom_preset_data_.size();
|
||||
}
|
||||
|
||||
const QVector<PresetPtr>& GetPresetData() const
|
||||
{
|
||||
return custom_preset_data_;
|
||||
}
|
||||
|
||||
private:
|
||||
QVector<PresetPtr> custom_preset_data_;
|
||||
|
||||
QString preset_name_;
|
||||
|
||||
QWidget* parent_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // PRESETMANAGER_H
|
||||
@@ -68,8 +68,8 @@ SequenceDialog::SequenceDialog(Sequence* s, Type t, QWidget* parent) :
|
||||
// Set up dialog buttons
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
buttons->setCenterButtons(true);
|
||||
connect(buttons, SIGNAL(accepted()), this, SLOT(accept()));
|
||||
connect(buttons, SIGNAL(rejected()), this, SLOT(reject()));
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &SequenceDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &SequenceDialog::reject);
|
||||
layout->addWidget(buttons);
|
||||
|
||||
// Set window title based on type
|
||||
@@ -102,24 +102,17 @@ void SequenceDialog::accept()
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the rational at the combobox's index (which will be correct provided AddFrameRate() was used at all time)
|
||||
rational video_time_base = parameter_tab_->GetSelectedVideoFrameRate().flipped();
|
||||
|
||||
// Get the rational at the combobox's index (which will be correct provided AddFrameRate() was used at all time)
|
||||
int audio_sample_rate = parameter_tab_->GetSelectedAudioSampleRate();
|
||||
|
||||
// Get the audio channel layout value
|
||||
uint64_t channels = parameter_tab_->GetSelectedAudioChannelLayout();
|
||||
|
||||
// Generate video and audio parameter structs from data
|
||||
VideoParams video_params = VideoParams(parameter_tab_->GetSelectedVideoWidth(),
|
||||
parameter_tab_->GetSelectedVideoHeight(),
|
||||
video_time_base,
|
||||
parameter_tab_->GetSelectedVideoFrameRate().flipped(),
|
||||
parameter_tab_->GetSelectedPreviewFormat(),
|
||||
parameter_tab_->GetSelectedVideoPixelAspect(),
|
||||
parameter_tab_->GetSelectedVideoInterlacingMode(),
|
||||
parameter_tab_->GetSelectedPreviewResolution());
|
||||
|
||||
AudioParams audio_params = AudioParams(audio_sample_rate,
|
||||
channels,
|
||||
AudioParams audio_params = AudioParams(parameter_tab_->GetSelectedAudioSampleRate(),
|
||||
parameter_tab_->GetSelectedAudioChannelLayout(),
|
||||
SampleFormat::kInternalFormat);
|
||||
|
||||
if (make_undoable_) {
|
||||
|
||||
@@ -35,8 +35,16 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg
|
||||
video_layout->addWidget(video_height_field_, row, 1);
|
||||
row++;
|
||||
video_layout->addWidget(new QLabel(tr("Frame Rate:")), row, 0);
|
||||
video_frame_rate_field_ = new QComboBox();
|
||||
video_frame_rate_field_ = new FrameRateComboBox();
|
||||
video_layout->addWidget(video_frame_rate_field_, row, 1);
|
||||
row++;
|
||||
video_layout->addWidget(new QLabel(tr("Pixel Aspect Ratio:")), row, 0);
|
||||
video_pixel_aspect_field_ = new PixelAspectRatioComboBox();
|
||||
video_layout->addWidget(video_pixel_aspect_field_, row, 1);
|
||||
row++;
|
||||
video_layout->addWidget(new QLabel(tr("Interlacing:")));
|
||||
video_interlaced_field_ = new InterlacedComboBox();
|
||||
video_layout->addWidget(video_interlaced_field_, row, 1);
|
||||
layout->addWidget(video_group);
|
||||
|
||||
row = 0;
|
||||
@@ -46,11 +54,11 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg
|
||||
audio_group->setTitle(tr("Audio"));
|
||||
QGridLayout* audio_layout = new QGridLayout(audio_group);
|
||||
audio_layout->addWidget(new QLabel(tr("Sample Rate:")), row, 0);
|
||||
audio_sample_rate_field_ = new QComboBox();
|
||||
audio_sample_rate_field_ = new SampleRateComboBox();
|
||||
audio_layout->addWidget(audio_sample_rate_field_, row, 1);
|
||||
row++;
|
||||
audio_layout->addWidget(new QLabel(tr("Channels:")), row, 0);
|
||||
audio_channels_field_ = new QComboBox();
|
||||
audio_channels_field_ = new ChannelLayoutComboBox();
|
||||
audio_layout->addWidget(audio_channels_field_, row, 1);
|
||||
layout->addWidget(audio_group);
|
||||
|
||||
@@ -61,83 +69,29 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg
|
||||
preview_group->setTitle(tr("Preview"));
|
||||
QGridLayout* preview_layout = new QGridLayout(preview_group);
|
||||
preview_layout->addWidget(new QLabel(tr("Resolution:")), row, 0);
|
||||
preview_resolution_field_ = new QComboBox();
|
||||
preview_resolution_field_ = new VideoDividerComboBox();
|
||||
preview_layout->addWidget(preview_resolution_field_, row, 1);
|
||||
preview_resolution_label_ = new QLabel();
|
||||
preview_layout->addWidget(preview_resolution_label_, row, 2);
|
||||
row++;
|
||||
preview_layout->addWidget(new QLabel(tr("Format:")), row, 0);
|
||||
preview_format_field_ = new QComboBox();
|
||||
preview_format_field_ = new PixelFormatComboBox(true, true);
|
||||
preview_layout->addWidget(preview_format_field_, row, 1, 1, 2);
|
||||
layout->addWidget(preview_group);
|
||||
|
||||
// Set up available frame rates
|
||||
frame_rate_list_ = Core::SupportedFrameRates();
|
||||
foreach (const rational& fr, frame_rate_list_) {
|
||||
video_frame_rate_field_->addItem(Core::FrameRateToString(fr));
|
||||
}
|
||||
|
||||
// Set up available sample rates
|
||||
sample_rate_list_ = Core::SupportedSampleRates();
|
||||
foreach (const int& sr, sample_rate_list_) {
|
||||
audio_sample_rate_field_->addItem(Core::SampleRateToString(sr));
|
||||
}
|
||||
|
||||
// Set up available channel layouts
|
||||
channel_layout_list_ = Core::SupportedChannelLayouts();
|
||||
foreach (const uint64_t& ch_layout, channel_layout_list_) {
|
||||
audio_channels_field_->addItem(Core::ChannelLayoutToString(ch_layout), QVariant::fromValue(ch_layout));
|
||||
}
|
||||
|
||||
// Set up preview dividers
|
||||
divider_list_ = Core::SupportedDividers();
|
||||
foreach (int d, divider_list_) {
|
||||
QString name;
|
||||
|
||||
if (d == 1) {
|
||||
name = tr("Full");
|
||||
} else {
|
||||
name = tr("1/%1").arg(d);
|
||||
}
|
||||
|
||||
preview_resolution_field_->addItem(name);
|
||||
}
|
||||
connect(preview_resolution_field_, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
this, &SequenceDialogParameterTab::UpdatePreviewResolutionLabel);
|
||||
|
||||
// Set up preview formats
|
||||
for (int i=0;i<PixelFormat::PIX_FMT_COUNT;i++) {
|
||||
PixelFormat::Format pix_fmt = static_cast<PixelFormat::Format>(i);
|
||||
|
||||
// We always render with an alpha channel internally
|
||||
if (PixelFormat::FormatHasAlphaChannel(pix_fmt)
|
||||
&& PixelFormat::FormatIsFloat(pix_fmt)) {
|
||||
preview_format_field_->addItem(PixelFormat::GetName(pix_fmt));
|
||||
|
||||
preview_format_list_.append(pix_fmt);
|
||||
}
|
||||
}
|
||||
|
||||
// Set values based on input sequence
|
||||
video_width_field_->SetValue(sequence->video_params().width());
|
||||
video_height_field_->SetValue(sequence->video_params().height());
|
||||
video_frame_rate_field_->SetFrameRate(sequence->video_params().time_base().flipped());
|
||||
video_pixel_aspect_field_->SetPixelAspectRatio(sequence->video_params().pixel_aspect_ratio());
|
||||
video_interlaced_field_->SetInterlaceMode(sequence->video_params().interlacing());
|
||||
preview_resolution_field_->SetDivider(sequence->video_params().divider());
|
||||
preview_format_field_->SetPixelFormat(sequence->video_params().format());
|
||||
audio_sample_rate_field_->SetSampleRate(sequence->audio_params().sample_rate());
|
||||
audio_channels_field_->SetChannelLayout(sequence->audio_params().channel_layout());
|
||||
|
||||
int frame_rate_index = frame_rate_list_.indexOf(sequence->video_params().time_base().flipped());
|
||||
video_frame_rate_field_->setCurrentIndex(frame_rate_index);
|
||||
|
||||
int sample_rate_index = sample_rate_list_.indexOf(sequence->audio_params().sample_rate());
|
||||
audio_sample_rate_field_->setCurrentIndex(sample_rate_index);
|
||||
|
||||
for (int i=0;i<audio_channels_field_->count();i++) {
|
||||
if (audio_channels_field_->itemData(i).toULongLong() == sequence->audio_params().channel_layout()) {
|
||||
audio_channels_field_->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
preview_resolution_field_->setCurrentIndex(divider_list_.indexOf(sequence->video_params().divider()));
|
||||
|
||||
preview_format_field_->setCurrentIndex(preview_format_list_.indexOf(sequence->video_params().format()));
|
||||
connect(preview_resolution_field_, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
this, &SequenceDialogParameterTab::UpdatePreviewResolutionLabel);
|
||||
|
||||
layout->addStretch();
|
||||
|
||||
@@ -148,62 +102,31 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg
|
||||
UpdatePreviewResolutionLabel();
|
||||
}
|
||||
|
||||
int SequenceDialogParameterTab::GetSelectedVideoWidth() const
|
||||
{
|
||||
return video_width_field_->GetValue();
|
||||
}
|
||||
|
||||
int SequenceDialogParameterTab::GetSelectedVideoHeight() const
|
||||
{
|
||||
return video_height_field_->GetValue();
|
||||
}
|
||||
|
||||
const rational &SequenceDialogParameterTab::GetSelectedVideoFrameRate() const
|
||||
{
|
||||
return frame_rate_list_.at(video_frame_rate_field_->currentIndex());
|
||||
}
|
||||
|
||||
int SequenceDialogParameterTab::GetSelectedAudioSampleRate() const
|
||||
{
|
||||
return sample_rate_list_.at(audio_sample_rate_field_->currentIndex());
|
||||
}
|
||||
|
||||
uint64_t SequenceDialogParameterTab::GetSelectedAudioChannelLayout() const
|
||||
{
|
||||
return audio_channels_field_->currentData().toULongLong();
|
||||
}
|
||||
|
||||
int SequenceDialogParameterTab::GetSelectedPreviewResolution() const
|
||||
{
|
||||
return divider_list_.at(preview_resolution_field_->currentIndex());
|
||||
}
|
||||
|
||||
PixelFormat::Format SequenceDialogParameterTab::GetSelectedPreviewFormat() const
|
||||
{
|
||||
return preview_format_list_.at(preview_format_field_->currentIndex());
|
||||
}
|
||||
|
||||
void SequenceDialogParameterTab::PresetChanged(const SequencePreset &preset)
|
||||
{
|
||||
video_width_field_->SetValue(preset.width);
|
||||
video_height_field_->SetValue(preset.height);
|
||||
video_frame_rate_field_->setCurrentIndex(frame_rate_list_.indexOf(preset.frame_rate));
|
||||
audio_sample_rate_field_->setCurrentIndex(sample_rate_list_.indexOf(preset.sample_rate));
|
||||
audio_channels_field_->setCurrentIndex(channel_layout_list_.indexOf(preset.channel_layout));
|
||||
preview_resolution_field_->setCurrentIndex(divider_list_.indexOf(preset.preview_divider));
|
||||
preview_format_field_->setCurrentIndex(preview_format_list_.indexOf(preset.preview_format));
|
||||
video_width_field_->SetValue(preset.width());
|
||||
video_height_field_->SetValue(preset.height());
|
||||
video_frame_rate_field_->SetFrameRate(preset.frame_rate());
|
||||
video_pixel_aspect_field_->SetPixelAspectRatio(preset.pixel_aspect());
|
||||
video_interlaced_field_->SetInterlaceMode(preset.interlacing());
|
||||
audio_sample_rate_field_->SetSampleRate(preset.sample_rate());
|
||||
audio_channels_field_->SetChannelLayout(preset.channel_layout());
|
||||
preview_resolution_field_->SetDivider(preset.preview_divider());
|
||||
preview_format_field_->SetPixelFormat(preset.preview_format());
|
||||
}
|
||||
|
||||
void SequenceDialogParameterTab::SavePresetClicked()
|
||||
{
|
||||
emit SaveParametersAsPreset({QString(),
|
||||
static_cast<int>(video_width_field_->GetValue()),
|
||||
static_cast<int>(video_height_field_->GetValue()),
|
||||
frame_rate_list_.at(video_frame_rate_field_->currentIndex()),
|
||||
sample_rate_list_.at(audio_sample_rate_field_->currentIndex()),
|
||||
channel_layout_list_.at(audio_channels_field_->currentIndex()),
|
||||
divider_list_.at(preview_resolution_field_->currentIndex()),
|
||||
preview_format_list_.at(preview_format_field_->currentIndex())});
|
||||
GetSelectedVideoWidth(),
|
||||
GetSelectedVideoHeight(),
|
||||
GetSelectedVideoFrameRate(),
|
||||
GetSelectedVideoPixelAspect(),
|
||||
GetSelectedVideoInterlacingMode(),
|
||||
GetSelectedAudioSampleRate(),
|
||||
GetSelectedAudioChannelLayout(),
|
||||
GetSelectedPreviewResolution(),
|
||||
GetSelectedPreviewFormat()});
|
||||
}
|
||||
|
||||
void SequenceDialogParameterTab::UpdatePreviewResolutionLabel()
|
||||
@@ -211,7 +134,9 @@ void SequenceDialogParameterTab::UpdatePreviewResolutionLabel()
|
||||
VideoParams test_param(video_width_field_->GetValue(),
|
||||
video_height_field_->GetValue(),
|
||||
PixelFormat::PIX_FMT_INVALID,
|
||||
divider_list_.at(preview_resolution_field_->currentIndex()));
|
||||
rational(1),
|
||||
VideoParams::kInterlaceNone,
|
||||
preview_resolution_field_->currentData().toInt());
|
||||
|
||||
preview_resolution_label_->setText(tr("(%1x%2)").arg(QString::number(test_param.effective_width()),
|
||||
QString::number(test_param.effective_height())));
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "project/item/sequence/sequence.h"
|
||||
#include "sequencepreset.h"
|
||||
#include "widget/slider/integerslider.h"
|
||||
#include "widget/standardcombos/standardcombos.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -17,19 +18,50 @@ class SequenceDialogParameterTab : public QWidget
|
||||
public:
|
||||
SequenceDialogParameterTab(Sequence* sequence, QWidget* parent = nullptr);
|
||||
|
||||
int GetSelectedVideoWidth() const;
|
||||
int GetSelectedVideoWidth() const
|
||||
{
|
||||
return video_width_field_->GetValue();
|
||||
}
|
||||
|
||||
int GetSelectedVideoHeight() const;
|
||||
int GetSelectedVideoHeight() const
|
||||
{
|
||||
return video_height_field_->GetValue();
|
||||
}
|
||||
|
||||
const rational& GetSelectedVideoFrameRate() const;
|
||||
rational GetSelectedVideoFrameRate() const
|
||||
{
|
||||
return video_frame_rate_field_->GetFrameRate();
|
||||
}
|
||||
|
||||
int GetSelectedAudioSampleRate() const;
|
||||
rational GetSelectedVideoPixelAspect() const
|
||||
{
|
||||
return video_pixel_aspect_field_->GetPixelAspectRatio();
|
||||
}
|
||||
|
||||
uint64_t GetSelectedAudioChannelLayout() const;
|
||||
VideoParams::Interlacing GetSelectedVideoInterlacingMode() const
|
||||
{
|
||||
return video_interlaced_field_->GetInterlaceMode();
|
||||
}
|
||||
|
||||
int GetSelectedPreviewResolution() const;
|
||||
int GetSelectedAudioSampleRate() const
|
||||
{
|
||||
return audio_sample_rate_field_->GetSampleRate();
|
||||
}
|
||||
|
||||
PixelFormat::Format GetSelectedPreviewFormat() const;
|
||||
uint64_t GetSelectedAudioChannelLayout() const
|
||||
{
|
||||
return audio_channels_field_->GetChannelLayout();
|
||||
}
|
||||
|
||||
int GetSelectedPreviewResolution() const
|
||||
{
|
||||
return preview_resolution_field_->GetDivider();
|
||||
}
|
||||
|
||||
PixelFormat::Format GetSelectedPreviewFormat() const
|
||||
{
|
||||
return preview_format_field_->GetPixelFormat();
|
||||
}
|
||||
|
||||
public slots:
|
||||
void PresetChanged(const SequencePreset& preset);
|
||||
@@ -42,27 +74,21 @@ private:
|
||||
|
||||
IntegerSlider* video_height_field_;
|
||||
|
||||
QComboBox* video_frame_rate_field_;
|
||||
FrameRateComboBox* video_frame_rate_field_;
|
||||
|
||||
QComboBox* audio_sample_rate_field_;
|
||||
PixelAspectRatioComboBox* video_pixel_aspect_field_;
|
||||
|
||||
QComboBox* audio_channels_field_;
|
||||
InterlacedComboBox* video_interlaced_field_;
|
||||
|
||||
QComboBox* preview_resolution_field_;
|
||||
SampleRateComboBox* audio_sample_rate_field_;
|
||||
|
||||
ChannelLayoutComboBox* audio_channels_field_;
|
||||
|
||||
VideoDividerComboBox* preview_resolution_field_;
|
||||
|
||||
QLabel* preview_resolution_label_;
|
||||
|
||||
QComboBox* preview_format_field_;
|
||||
|
||||
QList<rational> frame_rate_list_;
|
||||
|
||||
QList<int> sample_rate_list_;
|
||||
|
||||
QList<uint64_t> channel_layout_list_;
|
||||
|
||||
QList<int> divider_list_;
|
||||
|
||||
QList<PixelFormat::Format> preview_format_list_;
|
||||
PixelFormatComboBox* preview_format_field_;
|
||||
|
||||
private slots:
|
||||
void SavePresetClicked();
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "sequencedialogpresettab.h"
|
||||
|
||||
#include <QDir>
|
||||
@@ -11,6 +31,7 @@
|
||||
|
||||
#include "common/filefunctions.h"
|
||||
#include "node/input.h"
|
||||
#include "render/videoparams.h"
|
||||
#include "ui/icons/icons.h"
|
||||
#include "widget/menu/menu.h"
|
||||
|
||||
@@ -20,8 +41,11 @@ const int kDataIsPreset = Qt::UserRole;
|
||||
const int kDataPresetIsCustomRole = Qt::UserRole + 1;
|
||||
const int kDataPresetDataRole = Qt::UserRole + 2;
|
||||
|
||||
const PixelFormat::Format kDefaultPreviewFormat = PixelFormat::PIX_FMT_RGBA16F;
|
||||
|
||||
SequenceDialogPresetTab::SequenceDialogPresetTab(QWidget* parent) :
|
||||
QWidget(parent)
|
||||
QWidget(parent),
|
||||
PresetManager<SequencePreset>(this, QStringLiteral("sequencepresets"))
|
||||
{
|
||||
QVBoxLayout* outer_layout = new QVBoxLayout(this);
|
||||
outer_layout->setMargin(0);
|
||||
@@ -44,127 +68,25 @@ SequenceDialogPresetTab::SequenceDialogPresetTab(QWidget* parent) :
|
||||
preset_tree_->addTopLevelItem(CreateHDPresetFolder(tr("1080p"), 1920, 1080, 3));
|
||||
preset_tree_->addTopLevelItem(CreateHDPresetFolder(tr("720p"), 1280, 720, 2));
|
||||
|
||||
preset_tree_->addTopLevelItem(CreateSDPresetFolder(tr("NTSC"), 720, 480, rational(30000, 1001), 1));
|
||||
preset_tree_->addTopLevelItem(CreateSDPresetFolder(tr("PAL"), 720, 576, rational(25, 1), 1));
|
||||
preset_tree_->addTopLevelItem(CreateSDPresetFolder(tr("NTSC"), 720, 480, rational(30000, 1001),
|
||||
VideoParams::kPixelAspectNTSCStandard,
|
||||
VideoParams::kPixelAspectNTSCWidescreen, 1));
|
||||
preset_tree_->addTopLevelItem(CreateSDPresetFolder(tr("PAL"), 720, 576, rational(25, 1),
|
||||
VideoParams::kPixelAspectPALStandard,
|
||||
VideoParams::kPixelAspectPALWidescreen, 1));
|
||||
|
||||
// Load custom presets
|
||||
QFile preset_file(GetCustomPresetFilename());
|
||||
if (preset_file.open(QFile::ReadOnly)) {
|
||||
QXmlStreamReader reader(&preset_file);
|
||||
|
||||
while (XMLReadNextStartElement(&reader)) {
|
||||
if (reader.name() == QStringLiteral("presets")) {
|
||||
while (XMLReadNextStartElement(&reader)) {
|
||||
if (reader.name() == QStringLiteral("preset")) {
|
||||
SequencePreset p;
|
||||
|
||||
while (XMLReadNextStartElement(&reader)) {
|
||||
if (reader.name() == QStringLiteral("name")) {
|
||||
p.name = reader.readElementText();
|
||||
} else if (reader.name() == QStringLiteral("width")) {
|
||||
p.width = reader.readElementText().toInt();
|
||||
} else if (reader.name() == QStringLiteral("height")) {
|
||||
p.height = reader.readElementText().toInt();
|
||||
} else if (reader.name() == QStringLiteral("framerate")) {
|
||||
p.frame_rate = rational::fromString(reader.readElementText());
|
||||
} else if (reader.name() == QStringLiteral("samplerate")) {
|
||||
p.sample_rate = reader.readElementText().toInt();
|
||||
} else if (reader.name() == QStringLiteral("chlayout")) {
|
||||
p.channel_layout = reader.readElementText().toULongLong();
|
||||
} else if (reader.name() == QStringLiteral("divider")) {
|
||||
p.preview_divider = reader.readElementText().toInt();
|
||||
} else if (reader.name() == QStringLiteral("format")) {
|
||||
p.preview_format = static_cast<PixelFormat::Format>(reader.readElementText().toInt());
|
||||
} else {
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
AddItem(my_presets_folder_, p, true);
|
||||
} else {
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
preset_file.close();
|
||||
}
|
||||
}
|
||||
|
||||
SequenceDialogPresetTab::~SequenceDialogPresetTab()
|
||||
{
|
||||
// Save custom presets to disk
|
||||
QFile preset_file(GetCustomPresetFilename());
|
||||
if (preset_file.open(QFile::WriteOnly)) {
|
||||
QXmlStreamWriter writer(&preset_file);
|
||||
writer.setAutoFormatting(true);
|
||||
|
||||
writer.writeStartDocument();
|
||||
|
||||
writer.writeStartElement(QStringLiteral("presets"));
|
||||
|
||||
foreach (const SequencePreset& p, custom_preset_data_) {
|
||||
writer.writeStartElement(QStringLiteral("preset"));
|
||||
|
||||
writer.writeTextElement(QStringLiteral("name"), p.name);
|
||||
writer.writeTextElement(QStringLiteral("width"), QString::number(p.width));
|
||||
writer.writeTextElement(QStringLiteral("height"), QString::number(p.height));
|
||||
writer.writeTextElement(QStringLiteral("framerate"), p.frame_rate.toString());
|
||||
writer.writeTextElement(QStringLiteral("samplerate"), QString::number(p.sample_rate));
|
||||
writer.writeTextElement(QStringLiteral("chlayout"), QString::number(p.channel_layout));
|
||||
writer.writeTextElement(QStringLiteral("divider"), QString::number(p.preview_divider));
|
||||
writer.writeTextElement(QStringLiteral("format"), QString::number(p.preview_format));
|
||||
|
||||
writer.writeEndElement(); // preset
|
||||
}
|
||||
|
||||
writer.writeEndElement(); // presets
|
||||
|
||||
writer.writeEndDocument();
|
||||
|
||||
preset_file.close();
|
||||
for (int i=0;i<GetNumberOfPresets();i++) {
|
||||
AddCustomItem(my_presets_folder_, GetPreset(i), i);
|
||||
}
|
||||
}
|
||||
|
||||
void SequenceDialogPresetTab::SaveParametersAsPreset(SequencePreset preset)
|
||||
{
|
||||
QString preset_name;
|
||||
int existing_preset;
|
||||
PresetPtr preset_ptr = std::make_shared<SequencePreset>(preset);
|
||||
|
||||
forever {
|
||||
preset_name = GetPresetName(preset_name);
|
||||
|
||||
if (preset_name.isEmpty()) {
|
||||
// Dialog cancelled - leave function entirely
|
||||
return;
|
||||
}
|
||||
|
||||
existing_preset = -1;
|
||||
for (int i=0; i<custom_preset_data_.size(); i++) {
|
||||
if (custom_preset_data_.at(i).name == preset_name) {
|
||||
existing_preset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existing_preset == -1
|
||||
|| QMessageBox::question(this,
|
||||
tr("Preset exists"),
|
||||
tr("A preset with this name already exists. "
|
||||
"Would you like to replace it?")) == QMessageBox::Yes) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
preset.name = preset_name;
|
||||
|
||||
if (existing_preset >= 0) {
|
||||
custom_preset_data_.replace(existing_preset, preset);
|
||||
} else {
|
||||
AddItem(my_presets_folder_, preset, true);
|
||||
if (SavePreset(preset_ptr)) {
|
||||
AddCustomItem(my_presets_folder_, preset_ptr, GetNumberOfPresets() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,101 +101,86 @@ QTreeWidgetItem* SequenceDialogPresetTab::CreateFolder(const QString &name)
|
||||
QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &name, int width, int height, int divider)
|
||||
{
|
||||
QTreeWidgetItem* parent = CreateFolder(name);
|
||||
AddItem(parent, {tr("%1 23.976 FPS").arg(name),
|
||||
width,
|
||||
height,
|
||||
rational(24000, 1001),
|
||||
48000,
|
||||
AV_CH_LAYOUT_STEREO,
|
||||
divider,
|
||||
PixelFormat::PIX_FMT_RGBA16F});
|
||||
AddItem(parent, {tr("%1 25 FPS").arg(name),
|
||||
width,
|
||||
height,
|
||||
rational(25, 1),
|
||||
48000,
|
||||
AV_CH_LAYOUT_STEREO,
|
||||
divider,
|
||||
PixelFormat::PIX_FMT_RGBA16F});
|
||||
AddItem(parent, {tr("%1 29.97 FPS").arg(name),
|
||||
width,
|
||||
height,
|
||||
rational(30000, 1001),
|
||||
48000,
|
||||
AV_CH_LAYOUT_STEREO,
|
||||
divider,
|
||||
PixelFormat::PIX_FMT_RGBA16F});
|
||||
AddItem(parent, {tr("%1 50 FPS").arg(name),
|
||||
width,
|
||||
height,
|
||||
rational(50, 1),
|
||||
48000,
|
||||
AV_CH_LAYOUT_STEREO,
|
||||
divider,
|
||||
PixelFormat::PIX_FMT_RGBA16F});
|
||||
AddItem(parent, {tr("%1 59.94 FPS").arg(name),
|
||||
width,
|
||||
height,
|
||||
rational(60000, 1001),
|
||||
48000,
|
||||
AV_CH_LAYOUT_STEREO,
|
||||
divider,
|
||||
PixelFormat::PIX_FMT_RGBA16F});
|
||||
AddStandardItem(parent, SequencePreset::Create(tr("%1 23.976 FPS").arg(name),
|
||||
width,
|
||||
height,
|
||||
rational(24000, 1001),
|
||||
VideoParams::kPixelAspectSquare,
|
||||
VideoParams::kInterlaceNone,
|
||||
48000,
|
||||
AV_CH_LAYOUT_STEREO,
|
||||
divider,
|
||||
kDefaultPreviewFormat));
|
||||
AddStandardItem(parent, SequencePreset::Create(tr("%1 25 FPS").arg(name),
|
||||
width,
|
||||
height,
|
||||
rational(25, 1),
|
||||
VideoParams::kPixelAspectSquare,
|
||||
VideoParams::kInterlaceNone,
|
||||
48000,
|
||||
AV_CH_LAYOUT_STEREO,
|
||||
divider,
|
||||
kDefaultPreviewFormat));
|
||||
AddStandardItem(parent, SequencePreset::Create(tr("%1 29.97 FPS").arg(name),
|
||||
width,
|
||||
height,
|
||||
rational(30000, 1001),
|
||||
VideoParams::kPixelAspectSquare,
|
||||
VideoParams::kInterlaceNone,
|
||||
48000,
|
||||
AV_CH_LAYOUT_STEREO,
|
||||
divider,
|
||||
kDefaultPreviewFormat));
|
||||
AddStandardItem(parent, SequencePreset::Create(tr("%1 50 FPS").arg(name),
|
||||
width,
|
||||
height,
|
||||
rational(50, 1),
|
||||
VideoParams::kPixelAspectSquare,
|
||||
VideoParams::kInterlaceNone,
|
||||
48000,
|
||||
AV_CH_LAYOUT_STEREO,
|
||||
divider,
|
||||
kDefaultPreviewFormat));
|
||||
AddStandardItem(parent, SequencePreset::Create(tr("%1 59.94 FPS").arg(name),
|
||||
width,
|
||||
height,
|
||||
rational(60000, 1001),
|
||||
VideoParams::kPixelAspectSquare,
|
||||
VideoParams::kInterlaceNone,
|
||||
48000,
|
||||
AV_CH_LAYOUT_STEREO,
|
||||
divider,
|
||||
kDefaultPreviewFormat));
|
||||
return parent;
|
||||
}
|
||||
|
||||
QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder(const QString &name, int width, int height, const rational& frame_rate, int divider)
|
||||
QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder(const QString &name, int width, int height, const rational& frame_rate, const rational &standard_par, const rational &wide_par, int divider)
|
||||
{
|
||||
QTreeWidgetItem* parent = CreateFolder(name);
|
||||
preset_tree_->addTopLevelItem(parent);
|
||||
AddItem(parent, {tr("%1 Standard").arg(name),
|
||||
width,
|
||||
height,
|
||||
frame_rate,
|
||||
48000,
|
||||
AV_CH_LAYOUT_STEREO,
|
||||
divider,
|
||||
PixelFormat::PIX_FMT_RGBA16F});
|
||||
AddItem(parent, {tr("%1 Widescreen").arg(name),
|
||||
width,
|
||||
height,
|
||||
frame_rate,
|
||||
48000,
|
||||
AV_CH_LAYOUT_STEREO,
|
||||
divider,
|
||||
PixelFormat::PIX_FMT_RGBA16F});
|
||||
AddStandardItem(parent, SequencePreset::Create(tr("%1 Standard").arg(name),
|
||||
width,
|
||||
height,
|
||||
frame_rate,
|
||||
standard_par,
|
||||
VideoParams::kInterlacedBottomFirst,
|
||||
48000,
|
||||
AV_CH_LAYOUT_STEREO,
|
||||
divider,
|
||||
kDefaultPreviewFormat));
|
||||
AddStandardItem(parent, SequencePreset::Create(tr("%1 Widescreen").arg(name),
|
||||
width,
|
||||
height,
|
||||
frame_rate,
|
||||
wide_par,
|
||||
VideoParams::kInterlacedBottomFirst,
|
||||
48000,
|
||||
AV_CH_LAYOUT_STEREO,
|
||||
divider,
|
||||
kDefaultPreviewFormat));
|
||||
return parent;
|
||||
}
|
||||
|
||||
QString SequenceDialogPresetTab::GetPresetName(QString start)
|
||||
{
|
||||
bool ok;
|
||||
|
||||
forever {
|
||||
start = QInputDialog::getText(this,
|
||||
tr("Save Preset"),
|
||||
tr("Set preset name:"),
|
||||
QLineEdit::Normal,
|
||||
start,
|
||||
&ok);
|
||||
|
||||
if (!ok) {
|
||||
// Dialog cancelled - leave function entirely
|
||||
return QString();
|
||||
}
|
||||
|
||||
if (start.isEmpty()) {
|
||||
// No preset name entered, start loop over
|
||||
QMessageBox::critical(this, tr("Invalid preset name"),
|
||||
tr("You must enter a preset name"), QMessageBox::Ok);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
QTreeWidgetItem *SequenceDialogPresetTab::GetSelectedItem()
|
||||
{
|
||||
QList<QTreeWidgetItem*> selected_items = preset_tree_->selectedItems();
|
||||
@@ -298,23 +205,28 @@ QTreeWidgetItem *SequenceDialogPresetTab::GetSelectedCustomPreset()
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString SequenceDialogPresetTab::GetCustomPresetFilename()
|
||||
void SequenceDialogPresetTab::AddStandardItem(QTreeWidgetItem *folder, PresetPtr preset, const QString& description)
|
||||
{
|
||||
return QDir(FileFunctions::GetConfigurationLocation()).filePath(QStringLiteral("presets"));
|
||||
int index = default_preset_data_.size();
|
||||
default_preset_data_.append(preset);
|
||||
AddItemInternal(folder, preset, false, index, description);
|
||||
}
|
||||
|
||||
void SequenceDialogPresetTab::AddItem(QTreeWidgetItem *folder, const SequencePreset& preset, bool is_custom, const QString &description)
|
||||
void SequenceDialogPresetTab::AddCustomItem(QTreeWidgetItem *folder, PresetPtr preset, int index, const QString &description)
|
||||
{
|
||||
AddItemInternal(folder, preset, true, index, description);
|
||||
}
|
||||
|
||||
void SequenceDialogPresetTab::AddItemInternal(QTreeWidgetItem *folder, PresetPtr preset, bool is_custom, int index, const QString &description)
|
||||
{
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem();
|
||||
item->setText(0, preset.name);
|
||||
|
||||
item->setText(0, preset->GetName());
|
||||
item->setIcon(0, icon::Video);
|
||||
item->setToolTip(0, description);
|
||||
item->setData(0, kDataIsPreset, true);
|
||||
item->setData(0, kDataPresetIsCustomRole, is_custom);
|
||||
|
||||
QList<SequencePreset>& list = is_custom ? custom_preset_data_ : default_preset_data_;
|
||||
item->setData(0, kDataPresetDataRole, list.size());
|
||||
list.append(preset);
|
||||
item->setData(0, kDataPresetDataRole, index);
|
||||
|
||||
folder->addChild(item);
|
||||
}
|
||||
@@ -326,11 +238,11 @@ void SequenceDialogPresetTab::SelectedItemChanged(QTreeWidgetItem* current, QTre
|
||||
if (current->data(0, kDataIsPreset).toBool()) {
|
||||
int preset_index = current->data(0, kDataPresetDataRole).toInt();
|
||||
|
||||
const SequencePreset& preset_data = (current->data(0, kDataPresetIsCustomRole).toBool())
|
||||
? custom_preset_data_.at(preset_index)
|
||||
PresetPtr preset_data = (current->data(0, kDataPresetIsCustomRole).toBool())
|
||||
? GetPreset(preset_index)
|
||||
: default_preset_data_.at(preset_index);
|
||||
|
||||
emit PresetChanged(preset_data);
|
||||
emit PresetChanged(*static_cast<SequencePreset*>(preset_data.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,7 +287,7 @@ void SequenceDialogPresetTab::DeleteSelectedPreset()
|
||||
}
|
||||
|
||||
// Remove the preset
|
||||
custom_preset_data_.removeAt(preset_index);
|
||||
DeletePreset(preset_index);
|
||||
|
||||
// Delete the item
|
||||
delete sel;
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
/***
|
||||
|
||||
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 SEQUENCEDIALOGPRESETTAB_H
|
||||
#define SEQUENCEDIALOGPRESETTAB_H
|
||||
|
||||
@@ -5,18 +25,17 @@
|
||||
#include <QTreeWidget>
|
||||
#include <QWidget>
|
||||
|
||||
#include "presetmanager.h"
|
||||
#include "sequencepreset.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class SequenceDialogPresetTab : public QWidget
|
||||
class SequenceDialogPresetTab : public QWidget, public PresetManager<SequencePreset>
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SequenceDialogPresetTab(QWidget* parent = nullptr);
|
||||
|
||||
virtual ~SequenceDialogPresetTab() override;
|
||||
|
||||
public slots:
|
||||
void SaveParametersAsPreset(SequencePreset preset);
|
||||
|
||||
@@ -30,26 +49,22 @@ private:
|
||||
|
||||
QTreeWidgetItem *CreateHDPresetFolder(const QString& name, int width, int height, int divider);
|
||||
|
||||
QTreeWidgetItem *CreateSDPresetFolder(const QString& name, int width, int height, const rational &frame_rate, int divider);
|
||||
|
||||
QString GetPresetName(QString start);
|
||||
QTreeWidgetItem *CreateSDPresetFolder(const QString& name, int width, int height, const rational &frame_rate, const rational& standard_par, const rational& wide_par, int divider);
|
||||
|
||||
QTreeWidgetItem* GetSelectedItem();
|
||||
QTreeWidgetItem* GetSelectedCustomPreset();
|
||||
|
||||
static QString GetCustomPresetFilename();
|
||||
void AddStandardItem(QTreeWidgetItem* folder, PresetPtr preset, const QString &description = QString());
|
||||
|
||||
void AddItem(QTreeWidgetItem* folder,
|
||||
const SequencePreset& preset,
|
||||
bool is_custom = false,
|
||||
const QString& description = QString());
|
||||
void AddCustomItem(QTreeWidgetItem* folder, PresetPtr preset, int index, const QString& description = QString());
|
||||
|
||||
void AddItemInternal(QTreeWidgetItem* folder, PresetPtr preset, bool is_custom, int index, const QString& description = QString());
|
||||
|
||||
QTreeWidget* preset_tree_;
|
||||
|
||||
QTreeWidgetItem* my_presets_folder_;
|
||||
|
||||
QList<SequencePreset> default_preset_data_;
|
||||
QList<SequencePreset> custom_preset_data_;
|
||||
QList<PresetPtr> default_preset_data_;
|
||||
|
||||
private slots:
|
||||
void SelectedItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous);
|
||||
|
||||
@@ -21,20 +21,158 @@
|
||||
#ifndef SEQUENCEPARAM_H
|
||||
#define SEQUENCEPARAM_H
|
||||
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
#include "common/rational.h"
|
||||
#include "common/xmlutils.h"
|
||||
#include "dialog/sequence/presetmanager.h"
|
||||
#include "render/pixelformat.h"
|
||||
#include "render/videoparams.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
struct SequencePreset {
|
||||
QString name;
|
||||
int width;
|
||||
int height;
|
||||
rational frame_rate;
|
||||
int sample_rate;
|
||||
uint64_t channel_layout;
|
||||
int preview_divider;
|
||||
PixelFormat::Format preview_format;
|
||||
class SequencePreset : public Preset {
|
||||
public:
|
||||
SequencePreset() = default;
|
||||
|
||||
SequencePreset(const QString& name,
|
||||
int width,
|
||||
int height,
|
||||
const rational& frame_rate,
|
||||
const rational& pixel_aspect,
|
||||
VideoParams::Interlacing interlacing,
|
||||
int sample_rate,
|
||||
uint64_t channel_layout,
|
||||
int preview_divider,
|
||||
PixelFormat::Format preview_format) :
|
||||
width_(width),
|
||||
height_(height),
|
||||
frame_rate_(frame_rate),
|
||||
pixel_aspect_(pixel_aspect),
|
||||
interlacing_(interlacing),
|
||||
sample_rate_(sample_rate),
|
||||
channel_layout_(channel_layout),
|
||||
preview_divider_(preview_divider),
|
||||
preview_format_(preview_format)
|
||||
{
|
||||
SetName(name);
|
||||
}
|
||||
|
||||
static PresetPtr Create(const QString& name,
|
||||
int width,
|
||||
int height,
|
||||
const rational& frame_rate,
|
||||
const rational& pixel_aspect,
|
||||
VideoParams::Interlacing interlacing,
|
||||
int sample_rate,
|
||||
uint64_t channel_layout,
|
||||
int preview_divider,
|
||||
PixelFormat::Format preview_format)
|
||||
{
|
||||
return std::make_shared<SequencePreset>(name, width, height, frame_rate, pixel_aspect,
|
||||
interlacing, sample_rate, channel_layout,
|
||||
preview_divider, preview_format);
|
||||
}
|
||||
|
||||
virtual void Load(QXmlStreamReader* reader) override
|
||||
{
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("name")) {
|
||||
SetName(reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("width")) {
|
||||
width_ = reader->readElementText().toInt();
|
||||
} else if (reader->name() == QStringLiteral("height")) {
|
||||
height_ = reader->readElementText().toInt();
|
||||
} else if (reader->name() == QStringLiteral("framerate")) {
|
||||
frame_rate_ = rational::fromString(reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("pixelaspect")) {
|
||||
pixel_aspect_ = rational::fromString(reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("interlacing")) {
|
||||
interlacing_ = static_cast<VideoParams::Interlacing>(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("samplerate")) {
|
||||
sample_rate_ = reader->readElementText().toInt();
|
||||
} else if (reader->name() == QStringLiteral("chlayout")) {
|
||||
channel_layout_ = reader->readElementText().toULongLong();
|
||||
} else if (reader->name() == QStringLiteral("divider")) {
|
||||
preview_divider_ = reader->readElementText().toInt();
|
||||
} else if (reader->name() == QStringLiteral("format")) {
|
||||
preview_format_ = static_cast<PixelFormat::Format>(reader->readElementText().toInt());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Save(QXmlStreamWriter* writer) const override
|
||||
{
|
||||
writer->writeTextElement(QStringLiteral("name"), GetName());
|
||||
writer->writeTextElement(QStringLiteral("width"), QString::number(width_));
|
||||
writer->writeTextElement(QStringLiteral("height"), QString::number(height_));
|
||||
writer->writeTextElement(QStringLiteral("framerate"), frame_rate_.toString());
|
||||
writer->writeTextElement(QStringLiteral("pixelaspect"), pixel_aspect_.toString());
|
||||
writer->writeTextElement(QStringLiteral("interlacing_"), QString::number(interlacing_));
|
||||
writer->writeTextElement(QStringLiteral("samplerate"), QString::number(sample_rate_));
|
||||
writer->writeTextElement(QStringLiteral("chlayout"), QString::number(channel_layout_));
|
||||
writer->writeTextElement(QStringLiteral("divider"), QString::number(preview_divider_));
|
||||
writer->writeTextElement(QStringLiteral("format"), QString::number(preview_format_));
|
||||
}
|
||||
|
||||
int width() const
|
||||
{
|
||||
return width_;
|
||||
}
|
||||
|
||||
int height() const
|
||||
{
|
||||
return height_;
|
||||
}
|
||||
|
||||
const rational& frame_rate() const
|
||||
{
|
||||
return frame_rate_;
|
||||
}
|
||||
|
||||
const rational& pixel_aspect() const
|
||||
{
|
||||
return pixel_aspect_;
|
||||
}
|
||||
|
||||
VideoParams::Interlacing interlacing() const
|
||||
{
|
||||
return interlacing_;
|
||||
}
|
||||
|
||||
int sample_rate() const
|
||||
{
|
||||
return sample_rate_;
|
||||
}
|
||||
|
||||
uint64_t channel_layout() const
|
||||
{
|
||||
return channel_layout_;
|
||||
}
|
||||
|
||||
int preview_divider() const
|
||||
{
|
||||
return preview_divider_;
|
||||
}
|
||||
|
||||
PixelFormat::Format preview_format() const
|
||||
{
|
||||
return preview_format_;
|
||||
}
|
||||
|
||||
private:
|
||||
int width_;
|
||||
int height_;
|
||||
rational frame_rate_;
|
||||
rational pixel_aspect_;
|
||||
VideoParams::Interlacing interlacing_;
|
||||
int sample_rate_;
|
||||
uint64_t channel_layout_;
|
||||
int preview_divider_;
|
||||
PixelFormat::Format preview_format_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
+11
-21
@@ -32,17 +32,18 @@ extern "C" {
|
||||
}
|
||||
|
||||
#include <csignal>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QSurfaceFormat>
|
||||
|
||||
#include "core.h"
|
||||
#include "common/crashhandler.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
signal(SIGSEGV, OLIVE_NAMESPACE::crash_handler);
|
||||
signal(SIGABRT, OLIVE_NAMESPACE::crash_handler);
|
||||
#ifdef USE_CRASHPAD
|
||||
#include "common/crashpadinterface.h"
|
||||
#endif // USE_CRASHPAD
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// Set OpenGL display profile (3.2 Core)
|
||||
QSurfaceFormat format;
|
||||
format.setVersion(3, 2);
|
||||
@@ -86,23 +87,12 @@ int main(int argc, char *argv[]) {
|
||||
avfilter_register_all();
|
||||
#endif
|
||||
|
||||
int exit_code;
|
||||
|
||||
// Start core
|
||||
if (OLIVE_NAMESPACE::Core::instance()->Start()) {
|
||||
|
||||
// Run application loop and receive exit code
|
||||
exit_code = a.exec();
|
||||
|
||||
} else {
|
||||
|
||||
// Core failed to start, exit now
|
||||
exit_code = 1;
|
||||
|
||||
// Enable Google Crashpad if compiled with it
|
||||
#ifdef USE_CRASHPAD
|
||||
if (!InitializeCrashpad()) {
|
||||
qWarning() << "Failed to initialize Crashpad handler";
|
||||
}
|
||||
#endif // USE_CRASHPAD
|
||||
|
||||
// Clear core memory
|
||||
OLIVE_NAMESPACE::Core::instance()->Stop();
|
||||
|
||||
return exit_code;
|
||||
return OLIVE_NAMESPACE::Core::instance()->execute(&a);
|
||||
}
|
||||
|
||||
@@ -355,16 +355,6 @@ NodeInput *Block::speed_input() const
|
||||
return speed_input_;
|
||||
}
|
||||
|
||||
void Block::InvalidateCache(const TimeRange &range, NodeInput *from, NodeInput *source)
|
||||
{
|
||||
if (range.out() <= in() || range.in() >= out()) {
|
||||
// Ignore this range
|
||||
return;
|
||||
}
|
||||
|
||||
Node::InvalidateCache(TimeRange(qMax(range.in(), in()), qMin(range.out(), out())), from, source);
|
||||
}
|
||||
|
||||
void Block::Hash(QCryptographicHash &, const rational &) const
|
||||
{
|
||||
// A block does nothing by default, so we hash nothing
|
||||
|
||||
@@ -88,8 +88,6 @@ public:
|
||||
NodeInput* media_in_input() const;
|
||||
NodeInput* speed_input() const;
|
||||
|
||||
virtual void InvalidateCache(const TimeRange& range, NodeInput* from, NodeInput* source) override;
|
||||
|
||||
virtual void Hash(QCryptographicHash &hash, const rational &time) const override;
|
||||
|
||||
public slots:
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(crossdissolve)
|
||||
add_subdirectory(diptocolor)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/block/transition/transition.h
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/block/transition/crossdissolve/crossdissolvetransition.h
|
||||
node/block/transition/crossdissolve/crossdissolvetransition.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,89 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "crossdissolvetransition.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
CrossDissolveTransition::CrossDissolveTransition()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Node *CrossDissolveTransition::copy() const
|
||||
{
|
||||
return new CrossDissolveTransition();
|
||||
}
|
||||
|
||||
QString CrossDissolveTransition::Name() const
|
||||
{
|
||||
return tr("Cross Dissolve");
|
||||
}
|
||||
|
||||
QString CrossDissolveTransition::id() const
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.crossdissolve");
|
||||
}
|
||||
|
||||
QList<Node::CategoryID> CrossDissolveTransition::Category() const
|
||||
{
|
||||
return {kCategoryTransition};
|
||||
}
|
||||
|
||||
QString CrossDissolveTransition::Description() const
|
||||
{
|
||||
return tr("Smoothly transition between two clips.");
|
||||
}
|
||||
|
||||
ShaderCode CrossDissolveTransition::GetShaderCode(const QString &shader_id) const
|
||||
{
|
||||
Q_UNUSED(shader_id)
|
||||
|
||||
return ShaderCode(Node::ReadFileAsString(":/shaders/crossdissolve.frag"), QString());
|
||||
}
|
||||
|
||||
void CrossDissolveTransition::SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const
|
||||
{
|
||||
for (int i=0; i<out_samples->sample_count(); i++) {
|
||||
double this_sample_time = out_samples->audio_params().samples_to_time(i).toDouble() + time_in;
|
||||
double progress = GetTotalProgress(this_sample_time);
|
||||
|
||||
for (int j=0; j<out_samples->audio_params().channel_count(); j++) {
|
||||
out_samples->data()[j][i] = 0;
|
||||
|
||||
if (from_samples) {
|
||||
if (i < from_samples->sample_count()) {
|
||||
out_samples->data()[j][i] += from_samples->data()[j][i] * TransformCurve(1.0 - progress);
|
||||
}
|
||||
}
|
||||
|
||||
if (to_samples) {
|
||||
// Offset input samples from the end
|
||||
int in_index = i - (out_samples->sample_count() - to_samples->sample_count());
|
||||
|
||||
if (in_index >= 0) {
|
||||
out_samples->data()[j][i] += to_samples->data()[j][in_index] * TransformCurve(progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -0,0 +1,51 @@
|
||||
/***
|
||||
|
||||
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 CROSSDISSOLVETRANSITION_H
|
||||
#define CROSSDISSOLVETRANSITION_H
|
||||
|
||||
#include "node/block/transition/transition.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class CrossDissolveTransition : public TransitionBlock
|
||||
{
|
||||
public:
|
||||
CrossDissolveTransition();
|
||||
|
||||
virtual Node* copy() const override;
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
virtual QList<CategoryID> Category() const override;
|
||||
virtual QString Description() const override;
|
||||
|
||||
//virtual void Retranslate() override;
|
||||
|
||||
virtual ShaderCode GetShaderCode(const QString& shader_id) const override;
|
||||
|
||||
protected:
|
||||
virtual void SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const override;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // CROSSDISSOLVETRANSITION_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/block/transition/diptocolor/diptocolortransition.h
|
||||
node/block/transition/diptocolor/diptocolortransition.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,68 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "diptocolortransition.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
DipToColorTransition::DipToColorTransition()
|
||||
{
|
||||
color_input_ = new NodeInput(QStringLiteral("color_in"), NodeParam::kColor, QVariant::fromValue(Color(0, 0, 0)));
|
||||
AddInput(color_input_);
|
||||
}
|
||||
|
||||
Node *DipToColorTransition::copy() const
|
||||
{
|
||||
return new DipToColorTransition();
|
||||
}
|
||||
|
||||
QString DipToColorTransition::Name() const
|
||||
{
|
||||
return tr("Dip To Color");
|
||||
}
|
||||
|
||||
QString DipToColorTransition::id() const
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.diptocolor");
|
||||
}
|
||||
|
||||
QList<Node::CategoryID> DipToColorTransition::Category() const
|
||||
{
|
||||
return {kCategoryTransition};
|
||||
}
|
||||
|
||||
QString DipToColorTransition::Description() const
|
||||
{
|
||||
return tr("Transition between clips by dipping to a color.");
|
||||
}
|
||||
|
||||
ShaderCode DipToColorTransition::GetShaderCode(const QString &shader_id) const
|
||||
{
|
||||
Q_UNUSED(shader_id)
|
||||
|
||||
return ShaderCode(Node::ReadFileAsString(":/shaders/diptoblack.frag"), QString());
|
||||
}
|
||||
|
||||
void DipToColorTransition::ShaderJobEvent(NodeValueDatabase &value, ShaderJob &job) const
|
||||
{
|
||||
job.InsertValue(color_input_, value);
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -0,0 +1,52 @@
|
||||
/***
|
||||
|
||||
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 DIPTOCOLORTRANSITION_H
|
||||
#define DIPTOCOLORTRANSITION_H
|
||||
|
||||
#include "node/block/transition/transition.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class DipToColorTransition : public TransitionBlock
|
||||
{
|
||||
public:
|
||||
DipToColorTransition();
|
||||
|
||||
virtual Node* copy() const override;
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
virtual QList<CategoryID> Category() const override;
|
||||
virtual QString Description() const override;
|
||||
|
||||
virtual ShaderCode GetShaderCode(const QString& shader_id) const override;
|
||||
|
||||
protected:
|
||||
virtual void ShaderJobEvent(NodeValueDatabase &value, ShaderJob& job) const override;
|
||||
|
||||
private:
|
||||
NodeInput* color_input_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // DIPTOCOLORTRANSITION_H
|
||||
@@ -28,17 +28,22 @@ TransitionBlock::TransitionBlock() :
|
||||
connected_out_block_(nullptr),
|
||||
connected_in_block_(nullptr)
|
||||
{
|
||||
out_block_input_ = new NodeInput("out_block_in", NodeParam::kBuffer);
|
||||
out_block_input_ = new NodeInput(QStringLiteral("out_block_in"), NodeParam::kBuffer);
|
||||
out_block_input_->set_is_keyframable(false);
|
||||
connect(out_block_input_, &NodeParam::EdgeAdded, this, &TransitionBlock::BlockConnected);
|
||||
connect(out_block_input_, &NodeParam::EdgeRemoved, this, &TransitionBlock::BlockDisconnected);
|
||||
AddInput(out_block_input_);
|
||||
|
||||
in_block_input_ = new NodeInput("in_block_in", NodeParam::kBuffer);
|
||||
in_block_input_ = new NodeInput(QStringLiteral("in_block_in"), NodeParam::kBuffer);
|
||||
in_block_input_->set_is_keyframable(false);
|
||||
connect(in_block_input_, &NodeParam::EdgeAdded, this, &TransitionBlock::BlockConnected);
|
||||
connect(in_block_input_, &NodeParam::EdgeRemoved, this, &TransitionBlock::BlockDisconnected);
|
||||
AddInput(in_block_input_);
|
||||
|
||||
curve_input_ = new NodeInput(QStringLiteral("curve_in"), NodeParam::kCombo);
|
||||
curve_input_->set_is_keyframable(false);
|
||||
curve_input_->set_connectable(false);
|
||||
AddInput(curve_input_);
|
||||
}
|
||||
|
||||
Block::Type TransitionBlock::type() const
|
||||
@@ -62,6 +67,10 @@ void TransitionBlock::Retranslate()
|
||||
|
||||
out_block_input_->set_name(tr("From"));
|
||||
in_block_input_->set_name(tr("To"));
|
||||
curve_input_->set_name(tr("Curve"));
|
||||
|
||||
// These must correspond to the CurveType enum
|
||||
curve_input_->set_combobox_strings({ tr("Linear"), tr("Exponential"), tr("Logarithmic") });
|
||||
}
|
||||
|
||||
rational TransitionBlock::in_offset() const
|
||||
@@ -106,12 +115,12 @@ Block *TransitionBlock::connected_in_block() const
|
||||
return connected_in_block_;
|
||||
}
|
||||
|
||||
double TransitionBlock::GetTotalProgress(const rational &time) const
|
||||
double TransitionBlock::GetTotalProgress(const double &time) const
|
||||
{
|
||||
return GetInternalTransitionTime(time) / length().toDouble();
|
||||
}
|
||||
|
||||
double TransitionBlock::GetOutProgress(const rational &time) const
|
||||
double TransitionBlock::GetOutProgress(const double &time) const
|
||||
{
|
||||
if (out_offset() == 0) {
|
||||
return 0;
|
||||
@@ -120,7 +129,7 @@ double TransitionBlock::GetOutProgress(const rational &time) const
|
||||
return clamp(1.0 - (GetInternalTransitionTime(time) / out_offset().toDouble()), 0.0, 1.0);
|
||||
}
|
||||
|
||||
double TransitionBlock::GetInProgress(const rational &time) const
|
||||
double TransitionBlock::GetInProgress(const double &time) const
|
||||
{
|
||||
if (in_offset() == 0) {
|
||||
return 0;
|
||||
@@ -131,26 +140,36 @@ double TransitionBlock::GetInProgress(const rational &time) const
|
||||
|
||||
void TransitionBlock::Hash(QCryptographicHash &hash, const rational &time) const
|
||||
{
|
||||
double all_prog = GetTotalProgress(time);
|
||||
double in_prog = GetInProgress(time);
|
||||
double out_prog = GetOutProgress(time);
|
||||
Node::Hash(hash, time);
|
||||
|
||||
double time_dbl = time.toDouble();
|
||||
double all_prog = GetTotalProgress(time_dbl);
|
||||
double in_prog = GetInProgress(time_dbl);
|
||||
double out_prog = GetOutProgress(time_dbl);
|
||||
|
||||
hash.addData(reinterpret_cast<const char*>(&all_prog), sizeof(double));
|
||||
hash.addData(reinterpret_cast<const char*>(&in_prog), sizeof(double));
|
||||
hash.addData(reinterpret_cast<const char*>(&out_prog), sizeof(double));
|
||||
|
||||
if (out_block_input_->is_connected()) {
|
||||
out_block_input_->get_connected_node()->Hash(hash, time);
|
||||
}
|
||||
|
||||
if (in_block_input_->is_connected()) {
|
||||
in_block_input_->get_connected_node()->Hash(hash, time);
|
||||
}
|
||||
}
|
||||
|
||||
double TransitionBlock::GetInternalTransitionTime(const rational &time) const
|
||||
double TransitionBlock::GetInternalTransitionTime(const double &time) const
|
||||
{
|
||||
return time.toDouble() - in().toDouble();
|
||||
return time - in().toDouble();
|
||||
}
|
||||
|
||||
void TransitionBlock::InsertTransitionTimes(AcceleratedJob *job, const double &time) const
|
||||
{
|
||||
// Provides total transition progress from 0.0 (start) - 1.0 (end)
|
||||
job->InsertValue(QStringLiteral("ove_tprog_all"),
|
||||
NodeValue(NodeParam::kFloat, GetTotalProgress(time), this));
|
||||
|
||||
// Provides progress of out section from 1.0 (start) - 0.0 (end)
|
||||
job->InsertValue(QStringLiteral("ove_tprog_out"),
|
||||
NodeValue(NodeParam::kFloat, GetOutProgress(time), this));
|
||||
|
||||
// Provides progress of in section from 0.0 (start) - 1.0 (end)
|
||||
job->InsertValue(QStringLiteral("ove_tprog_in"),
|
||||
NodeValue(NodeParam::kFloat, GetInProgress(time), this));
|
||||
}
|
||||
|
||||
void TransitionBlock::BlockConnected(NodeEdgePtr edge)
|
||||
@@ -177,4 +196,127 @@ void TransitionBlock::BlockDisconnected(NodeEdgePtr edge)
|
||||
}
|
||||
}
|
||||
|
||||
NodeValueTable TransitionBlock::Value(NodeValueDatabase &value) const
|
||||
{
|
||||
NodeParam::DataType data_type;
|
||||
|
||||
if (out_block_input()->is_connected()) {
|
||||
data_type = value[out_block_input()].GetWithMeta(NodeParam::kBuffer).type();
|
||||
} else if (in_block_input()->is_connected()) {
|
||||
data_type = value[in_block_input()].GetWithMeta(NodeParam::kBuffer).type();
|
||||
} else {
|
||||
data_type = NodeParam::kNone;
|
||||
}
|
||||
|
||||
NodeParam::DataType job_type;
|
||||
QVariant push_job;
|
||||
|
||||
if (data_type == NodeParam::kTexture) {
|
||||
// This must be a visual transition
|
||||
ShaderJob job;
|
||||
|
||||
job.InsertValue(out_block_input(), value);
|
||||
job.InsertValue(in_block_input(), value);
|
||||
job.InsertValue(curve_input_, value);
|
||||
|
||||
double time = value[QStringLiteral("global")].Get(NodeParam::kFloat, QStringLiteral("time_in")).toDouble();
|
||||
InsertTransitionTimes(&job, time);
|
||||
|
||||
ShaderJobEvent(value, job);
|
||||
|
||||
job_type = NodeParam::kShaderJob;
|
||||
push_job = QVariant::fromValue(job);
|
||||
} else if (data_type == NodeParam::kSamples) {
|
||||
// This must be an audio transition
|
||||
SampleBufferPtr from_samples = value[out_block_input()].Take(NodeParam::kBuffer).value<SampleBufferPtr>();
|
||||
SampleBufferPtr to_samples = value[in_block_input()].Take(NodeParam::kBuffer).value<SampleBufferPtr>();
|
||||
|
||||
if (from_samples || to_samples) {
|
||||
double time_in = value[QStringLiteral("global")].Get(NodeParam::kFloat, QStringLiteral("time_in")).toDouble();
|
||||
double time_out = value[QStringLiteral("global")].Get(NodeParam::kFloat, QStringLiteral("time_out")).toDouble();
|
||||
|
||||
const AudioParams& params = (from_samples) ? from_samples->audio_params() : to_samples->audio_params();
|
||||
|
||||
int nb_samples = params.time_to_samples(time_out - time_in);
|
||||
|
||||
SampleBufferPtr out_samples = SampleBuffer::CreateAllocated(params, nb_samples);
|
||||
SampleJobEvent(from_samples, to_samples, out_samples, time_in);
|
||||
|
||||
job_type = NodeParam::kSamples;
|
||||
push_job = QVariant::fromValue(out_samples);
|
||||
}
|
||||
}
|
||||
|
||||
NodeValueTable table = value.Merge();
|
||||
|
||||
if (!push_job.isNull()) {
|
||||
table.Push(job_type, push_job, this);
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
TransitionBlock *GetBlockTransitionInternal(Block *block, Timeline::MovementMode mode)
|
||||
{
|
||||
// See if this block outputs to a transition
|
||||
foreach (NodeEdgePtr edge, block->output()->edges()) {
|
||||
Node* connected_node = edge->input()->parentNode();
|
||||
|
||||
if (connected_node->IsBlock()) {
|
||||
Block* connected_block = static_cast<Block*>(connected_node);
|
||||
|
||||
if (connected_block->type() == Block::kTransition) {
|
||||
TransitionBlock* connected_transition = static_cast<TransitionBlock*>(connected_block);
|
||||
|
||||
if ((mode == Timeline::kTrimIn && edge->input() == connected_transition->in_block_input())
|
||||
|| (mode == Timeline::kTrimOut && edge->input() == connected_transition->out_block_input())) {
|
||||
return connected_transition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TransitionBlock *TransitionBlock::GetBlockInTransition(Block *block)
|
||||
{
|
||||
return GetBlockTransitionInternal(block, Timeline::kTrimIn);
|
||||
}
|
||||
|
||||
TransitionBlock *TransitionBlock::GetBlockOutTransition(Block *block)
|
||||
{
|
||||
return GetBlockTransitionInternal(block, Timeline::kTrimOut);
|
||||
}
|
||||
|
||||
void TransitionBlock::ShaderJobEvent(NodeValueDatabase &value, ShaderJob &job) const
|
||||
{
|
||||
Q_UNUSED(value)
|
||||
Q_UNUSED(job)
|
||||
}
|
||||
|
||||
void TransitionBlock::SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const
|
||||
{
|
||||
Q_UNUSED(from_samples)
|
||||
Q_UNUSED(to_samples)
|
||||
Q_UNUSED(out_samples)
|
||||
Q_UNUSED(time_in)
|
||||
}
|
||||
|
||||
double TransitionBlock::TransformCurve(double linear) const
|
||||
{
|
||||
switch (static_cast<CurveType>(curve_input_->get_standard_value().toInt())) {
|
||||
case kLinear:
|
||||
break;
|
||||
case kExponential:
|
||||
linear *= linear;
|
||||
break;
|
||||
case kLogarithmic:
|
||||
linear = qSqrt(linear);
|
||||
break;
|
||||
}
|
||||
|
||||
return linear;
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -43,19 +43,42 @@ public:
|
||||
Block* connected_out_block() const;
|
||||
Block* connected_in_block() const;
|
||||
|
||||
double GetTotalProgress(const rational& time) const;
|
||||
double GetOutProgress(const rational& time) const;
|
||||
double GetInProgress(const rational& time) const;
|
||||
double GetTotalProgress(const double &time) const;
|
||||
double GetOutProgress(const double &time) const;
|
||||
double GetInProgress(const double &time) const;
|
||||
|
||||
virtual void Hash(QCryptographicHash& hash, const rational &time) const override;
|
||||
|
||||
virtual NodeValueTable Value(NodeValueDatabase &value) const override;
|
||||
|
||||
static TransitionBlock* GetBlockInTransition(Block* block);
|
||||
|
||||
static TransitionBlock* GetBlockOutTransition(Block* block);
|
||||
|
||||
protected:
|
||||
virtual void ShaderJobEvent(NodeValueDatabase &value, ShaderJob& job) const;
|
||||
|
||||
virtual void SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const;
|
||||
|
||||
double TransformCurve(double linear) const;
|
||||
|
||||
private:
|
||||
double GetInternalTransitionTime(const rational& time) const;
|
||||
enum CurveType {
|
||||
kLinear,
|
||||
kExponential,
|
||||
kLogarithmic
|
||||
};
|
||||
|
||||
double GetInternalTransitionTime(const double &time) const;
|
||||
|
||||
void InsertTransitionTimes(AcceleratedJob* job, const double& time) const;
|
||||
|
||||
NodeInput* out_block_input_;
|
||||
|
||||
NodeInput* in_block_input_;
|
||||
|
||||
NodeInput* curve_input_;
|
||||
|
||||
Block* connected_out_block_;
|
||||
|
||||
Block* connected_in_block_;
|
||||
|
||||
+14
-3
@@ -24,6 +24,8 @@
|
||||
#include "audio/volume/volume.h"
|
||||
#include "block/clip/clip.h"
|
||||
#include "block/gap/gap.h"
|
||||
#include "block/transition/crossdissolve/crossdissolvetransition.h"
|
||||
#include "block/transition/diptocolor/diptocolortransition.h"
|
||||
#include "generator/matrix/matrix.h"
|
||||
#include "generator/polygon/polygon.h"
|
||||
#include "generator/solid/solid.h"
|
||||
@@ -48,7 +50,7 @@ void NodeFactory::Initialize()
|
||||
|
||||
// Add internal types
|
||||
for (int i=0;i<kInternalNodeCount;i++) {
|
||||
library_.append(CreateInternal(static_cast<InternalID>(i)));
|
||||
library_.append(CreateFromFactoryIndex(static_cast<InternalID>(i)));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -63,7 +65,7 @@ void NodeFactory::Destroy()
|
||||
library_.clear();
|
||||
}
|
||||
|
||||
Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item)
|
||||
Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item, Node::CategoryID restrict_to)
|
||||
{
|
||||
Menu* menu = new Menu(parent);
|
||||
menu->setToolTipsVisible(true);
|
||||
@@ -71,6 +73,11 @@ Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item)
|
||||
for (int i=0;i<library_.size();i++) {
|
||||
Node* n = library_.at(i);
|
||||
|
||||
if (restrict_to != Node::kCategoryUnknown && !n->Category().contains(restrict_to)) {
|
||||
// Skip this node
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure nodes are up-to-date with the current translation
|
||||
n->Retranslate();
|
||||
|
||||
@@ -165,7 +172,7 @@ Node *NodeFactory::CreateFromID(const QString &id)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Node *NodeFactory::CreateInternal(const NodeFactory::InternalID &id)
|
||||
Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
|
||||
{
|
||||
switch (id) {
|
||||
case kClipBlock:
|
||||
@@ -204,6 +211,10 @@ Node *NodeFactory::CreateInternal(const NodeFactory::InternalID &id)
|
||||
return new StrokeFilterNode();
|
||||
case kTextGenerator:
|
||||
return new TextGenerator();
|
||||
case kCrossDissolveTransition:
|
||||
return new CrossDissolveTransition();
|
||||
case kDipToColorTransition:
|
||||
return new DipToColorTransition();
|
||||
|
||||
case kInternalNodeCount:
|
||||
break;
|
||||
|
||||
+6
-3
@@ -50,6 +50,8 @@ public:
|
||||
kMerge,
|
||||
kStrokeFilter,
|
||||
kTextGenerator,
|
||||
kCrossDissolveTransition,
|
||||
kDipToColorTransition,
|
||||
|
||||
// Count value
|
||||
kInternalNodeCount
|
||||
@@ -61,7 +63,7 @@ public:
|
||||
|
||||
static void Destroy();
|
||||
|
||||
static Menu* CreateMenu(QWidget *parent, bool create_none_item = false);
|
||||
static Menu* CreateMenu(QWidget *parent, bool create_none_item = false, Node::CategoryID restrict_to = Node::kCategoryUnknown);
|
||||
|
||||
static Node* CreateFromMenuAction(QAction* action);
|
||||
|
||||
@@ -71,10 +73,11 @@ public:
|
||||
|
||||
static Node* CreateFromID(const QString& id);
|
||||
|
||||
private:
|
||||
static Node* CreateInternal(const InternalID& id);
|
||||
static Node* CreateFromFactoryIndex(const InternalID& id);
|
||||
|
||||
private:
|
||||
static QList<Node*> library_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -32,11 +32,9 @@ enum TextVerticalAlign {
|
||||
|
||||
TextGenerator::TextGenerator()
|
||||
{
|
||||
QString default_str = QStringLiteral("<html><div style='font-size:144pt; text-align: center;'>%1</div></html>").arg(tr("Sample Text"));
|
||||
|
||||
text_input_ = new NodeInput(QStringLiteral("text_in"),
|
||||
NodeParam::kText,
|
||||
default_str);
|
||||
tr("Sample Text"));
|
||||
AddInput(text_input_);
|
||||
|
||||
color_input_ = new NodeInput(QStringLiteral("color_in"),
|
||||
@@ -48,6 +46,15 @@ TextGenerator::TextGenerator()
|
||||
NodeParam::kCombo,
|
||||
1);
|
||||
AddInput(valign_input_);
|
||||
|
||||
font_input_ = new NodeInput(QStringLiteral("font_in"),
|
||||
NodeParam::kFont);
|
||||
AddInput(font_input_);
|
||||
|
||||
font_size_input_ = new NodeInput(QStringLiteral("font_size_in"),
|
||||
NodeParam::kFloat,
|
||||
72.0f);
|
||||
AddInput(font_size_input_);
|
||||
}
|
||||
|
||||
Node *TextGenerator::copy() const
|
||||
@@ -78,7 +85,10 @@ QString TextGenerator::Description() const
|
||||
void TextGenerator::Retranslate()
|
||||
{
|
||||
text_input_->set_name(tr("Text"));
|
||||
font_input_->set_name(tr("Font"));
|
||||
font_size_input_->set_name(tr("Font Size"));
|
||||
color_input_->set_name(tr("Color"));
|
||||
valign_input_->set_name(tr("Vertical Align"));
|
||||
valign_input_->set_combobox_strings({tr("Top"), tr("Center"), tr("Bottom")});
|
||||
}
|
||||
|
||||
@@ -88,6 +98,8 @@ NodeValueTable TextGenerator::Value(NodeValueDatabase &value) const
|
||||
job.InsertValue(text_input_, value);
|
||||
job.InsertValue(color_input_, value);
|
||||
job.InsertValue(valign_input_, value);
|
||||
job.InsertValue(font_input_, value);
|
||||
job.InsertValue(font_size_input_, value);
|
||||
job.SetAlphaChannelRequired(true);
|
||||
|
||||
NodeValueTable table = value.Merge();
|
||||
@@ -109,24 +121,45 @@ void TextGenerator::GenerateFrame(FramePtr frame, const GenerateJob& job) const
|
||||
img.fill(0);
|
||||
|
||||
QTextDocument text_doc;
|
||||
|
||||
// Set default font
|
||||
QFont default_font;
|
||||
default_font.setFamily(job.GetValue(font_input_).data().toString());
|
||||
default_font.setPointSizeF(job.GetValue(font_size_input_).data().toFloat());
|
||||
text_doc.setDefaultFont(default_font);
|
||||
|
||||
// Center by default
|
||||
text_doc.setDefaultTextOption(QTextOption(Qt::AlignCenter));
|
||||
|
||||
text_doc.setHtml(job.GetValue(text_input_).data().toString());
|
||||
text_doc.setTextWidth(frame->video_params().width());
|
||||
|
||||
// Align to 80% width because that's considered the "title safe" area
|
||||
int tenth_of_width = frame->video_params().width() / 10;
|
||||
text_doc.setTextWidth(tenth_of_width * 8);
|
||||
|
||||
// Draw rich text onto image
|
||||
QPainter p(&img);
|
||||
p.scale(1.0 / frame->video_params().divider(), 1.0 / frame->video_params().divider());
|
||||
|
||||
TextVerticalAlign valign = static_cast<TextVerticalAlign>(job.GetValue(valign_input_).data().toInt());
|
||||
if (valign != kVerticalAlignTop) {
|
||||
int doc_height = text_doc.size().height();
|
||||
// Push 10% inwards to compensate for title safe area
|
||||
p.translate(tenth_of_width, 0);
|
||||
|
||||
if (valign == kVerticalAlignCenter) {
|
||||
// Center align
|
||||
p.translate(0, frame->video_params().height() / 2 - doc_height / 2);
|
||||
} else {
|
||||
// Must be bottom align
|
||||
p.translate(0, frame->video_params().height() - doc_height);
|
||||
}
|
||||
TextVerticalAlign valign = static_cast<TextVerticalAlign>(job.GetValue(valign_input_).data().toInt());
|
||||
int doc_height = text_doc.size().height();
|
||||
|
||||
switch (valign) {
|
||||
case kVerticalAlignTop:
|
||||
// Push 10% inwards for title safe area
|
||||
p.translate(0, frame->video_params().height() / 10);
|
||||
break;
|
||||
case kVerticalAlignCenter:
|
||||
// Center align
|
||||
p.translate(0, frame->video_params().height() / 2 - doc_height / 2);
|
||||
break;
|
||||
case kVerticalAlignBottom:
|
||||
// Push 10% inwards for title safe area
|
||||
p.translate(0, frame->video_params().height() - doc_height - frame->video_params().height() / 10);
|
||||
break;
|
||||
}
|
||||
|
||||
text_doc.drawContents(&p);
|
||||
|
||||
@@ -50,6 +50,10 @@ private:
|
||||
|
||||
NodeInput* valign_input_;
|
||||
|
||||
NodeInput* font_input_;
|
||||
|
||||
NodeInput* font_size_input_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
+72
-26
@@ -81,13 +81,15 @@ QString NodeInput::name()
|
||||
|
||||
void NodeInput::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled)
|
||||
{
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (cancelled && *cancelled) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (cancelled && *cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (attr.name() == QStringLiteral("keyframing")) {
|
||||
set_is_keyframing(attr.value() == QStringLiteral("1"));
|
||||
if (attr.name() == QStringLiteral("keyframing")) {
|
||||
set_is_keyframing(attr.value() == QStringLiteral("1"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,17 +327,48 @@ void NodeInput::SetDefaultValue(const QVector<QVariant> &default_value)
|
||||
|
||||
QString NodeInput::ValueToString(const QVariant &value) const
|
||||
{
|
||||
return ValueToString(data_type_, value);
|
||||
return ValueToString(data_type_, value, true);
|
||||
}
|
||||
|
||||
QString NodeInput::ValueToString(const DataType& data_type, const QVariant &value)
|
||||
QString NodeInput::ValueToString(const DataType& data_type, const QVariant &value, bool value_is_a_key_track)
|
||||
{
|
||||
switch (data_type) {
|
||||
case kRational:
|
||||
if (!value_is_a_key_track && data_type == kVec2) {
|
||||
QVector2D vec = value.value<QVector2D>();
|
||||
|
||||
return QStringLiteral("%1:%2").arg(QString::number(vec.x()),
|
||||
QString::number(vec.y()));
|
||||
} else if (!value_is_a_key_track && data_type == kVec3) {
|
||||
QVector3D vec = value.value<QVector3D>();
|
||||
|
||||
return QStringLiteral("%1:%2:%3").arg(QString::number(vec.x()),
|
||||
QString::number(vec.y()),
|
||||
QString::number(vec.z()));
|
||||
} else if (!value_is_a_key_track && data_type == kVec4) {
|
||||
QVector4D vec = value.value<QVector4D>();
|
||||
|
||||
return QStringLiteral("%1:%2:%3:%4").arg(QString::number(vec.x()),
|
||||
QString::number(vec.y()),
|
||||
QString::number(vec.z()),
|
||||
QString::number(vec.w()));
|
||||
} else if (!value_is_a_key_track && data_type == kColor) {
|
||||
Color c = value.value<Color>();
|
||||
|
||||
return QStringLiteral("%1:%2:%3:%4").arg(QString::number(c.red()),
|
||||
QString::number(c.green()),
|
||||
QString::number(c.blue()),
|
||||
QString::number(c.alpha()));
|
||||
} else if (data_type == kRational) {
|
||||
return value.value<rational>().toString();
|
||||
case kFootage:
|
||||
} else if (data_type == kFootage) {
|
||||
return QString::number(reinterpret_cast<quintptr>(value.value<StreamPtr>().get()));
|
||||
default:
|
||||
} else if (data_type == kTexture
|
||||
|| data_type == kSamples
|
||||
|| data_type == kBuffer) {
|
||||
// These data types need no XML representation
|
||||
return QString();
|
||||
} else if (data_type == kInt) {
|
||||
return QString::number(value.value<int64_t>());
|
||||
} else {
|
||||
if (value.canConvert<QString>()) {
|
||||
return value.toString();
|
||||
}
|
||||
@@ -344,22 +377,33 @@ QString NodeInput::ValueToString(const DataType& data_type, const QVariant &valu
|
||||
qWarning() << "Failed to convert type" << ToHex(data_type) << "to string";
|
||||
}
|
||||
|
||||
/* fall through */
|
||||
|
||||
// These data types need no XML representation
|
||||
case kTexture:
|
||||
case kSamples:
|
||||
case kBuffer:
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant NodeInput::StringToValue(const DataType& data_type, const QString &string)
|
||||
QVariant NodeInput::StringToValue(const DataType& data_type, const QString &string, bool value_is_a_key_track)
|
||||
{
|
||||
switch (data_type) {
|
||||
case kRational:
|
||||
if (!value_is_a_key_track && data_type == kVec2) {
|
||||
QStringList vals = string.split(':');
|
||||
|
||||
return QVector2D(vals.at(0).toFloat(), vals.at(1).toFloat());
|
||||
} else if (!value_is_a_key_track && data_type == kVec3) {
|
||||
QStringList vals = string.split(':');
|
||||
|
||||
return QVector3D(vals.at(0).toFloat(), vals.at(1).toFloat(), vals.at(2).toFloat());
|
||||
} else if (!value_is_a_key_track && data_type == kVec4) {
|
||||
QStringList vals = string.split(':');
|
||||
|
||||
return QVector4D(vals.at(0).toFloat(), vals.at(1).toFloat(), vals.at(2).toFloat(), vals.at(3).toFloat());
|
||||
} else if (!value_is_a_key_track && data_type == kColor) {
|
||||
QStringList vals = string.split(':');
|
||||
|
||||
return QVariant::fromValue(Color(vals.at(0).toFloat(), vals.at(1).toFloat(), vals.at(2).toFloat(), vals.at(3).toFloat()));
|
||||
} else if (data_type == kInt) {
|
||||
return QVariant::fromValue(string.toLongLong());
|
||||
} else if (data_type == kRational) {
|
||||
return QVariant::fromValue(rational::fromString(string));
|
||||
default:
|
||||
} else {
|
||||
return string;
|
||||
}
|
||||
}
|
||||
@@ -427,7 +471,7 @@ QVariant NodeInput::StringToValue(const QString &string, QList<XMLNodeData::Foot
|
||||
footage_connections.append({this, string.toULongLong()});
|
||||
}
|
||||
|
||||
return StringToValue(data_type_, string);
|
||||
return StringToValue(data_type_, string, true);
|
||||
}
|
||||
|
||||
NodeOutput *NodeInput::get_connected_output() const
|
||||
@@ -1005,7 +1049,7 @@ void NodeInput::set_is_keyframable(bool k)
|
||||
keyframable_ = k;
|
||||
}
|
||||
|
||||
void NodeInput::CopyValues(NodeInput *source, NodeInput *dest, bool include_connections)
|
||||
void NodeInput::CopyValues(NodeInput *source, NodeInput *dest, bool include_connections, bool traverse_arrays)
|
||||
{
|
||||
Q_ASSERT(source->id() == dest->id());
|
||||
|
||||
@@ -1035,8 +1079,10 @@ void NodeInput::CopyValues(NodeInput *source, NodeInput *dest, bool include_conn
|
||||
|
||||
dst_array->SetSize(src_array->GetSize());
|
||||
|
||||
for (int i=0;i<dst_array->GetSize();i++) {
|
||||
CopyValues(src_array->At(i), dst_array->At(i), include_connections);
|
||||
if (traverse_arrays) {
|
||||
for (int i=0;i<dst_array->GetSize();i++) {
|
||||
CopyValues(src_array->At(i), dst_array->At(i), include_connections);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -235,7 +235,7 @@ public:
|
||||
/**
|
||||
* @brief Copy all values including keyframe information and connections from another NodeInput
|
||||
*/
|
||||
static void CopyValues(NodeInput* source, NodeInput* dest, bool include_connections = true);
|
||||
static void CopyValues(NodeInput* source, NodeInput* dest, bool include_connections = true, bool traverse_arrays = true);
|
||||
|
||||
/**
|
||||
* @brief Set an arbitrary property on this input to influence a UI representation's behavior
|
||||
@@ -278,9 +278,9 @@ public:
|
||||
|
||||
void set_combobox_strings(const QStringList& strings);
|
||||
|
||||
static QString ValueToString(const DataType& data_type, const QVariant& value);
|
||||
static QString ValueToString(const DataType& data_type, const QVariant& value, bool value_is_a_key_track);
|
||||
|
||||
static QVariant StringToValue(const DataType &data_type, const QString &string);
|
||||
static QVariant StringToValue(const DataType &data_type, const QString &string, bool value_is_a_key_track);
|
||||
|
||||
void GetDependencies(QList<Node*>& list, bool traverse, bool exclusive_only) const;
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ void NodeInputArray::SetSize(int size)
|
||||
if (size < old_size) {
|
||||
// If the new size is less, delete all extraneous parameters
|
||||
for (int i=size;i<old_size;i++) {
|
||||
sub_params_.at(i)->deleteLater();
|
||||
delete sub_params_.at(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,16 @@ ShaderCode MathNodeBase::GetShaderCodeInternal(const QString &shader_id, NodeInp
|
||||
operation = QStringLiteral("%1 / %2");
|
||||
break;
|
||||
case kOpPower:
|
||||
operation = QStringLiteral("pow(%1, %2)");
|
||||
if (pairing == kPairTextureNumber) {
|
||||
// The "number" in this operation has to be declared a vec4
|
||||
if (type_a & NodeParam::kNumber) {
|
||||
operation = QStringLiteral("pow(%2, vec4(%1))");
|
||||
} else {
|
||||
operation = QStringLiteral("pow(%1, vec4(%2))");
|
||||
}
|
||||
} else {
|
||||
operation = QStringLiteral("pow(%1, %2)");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -285,8 +294,13 @@ NodeValueTable MathNodeBase::ValueInternal(NodeValueDatabase &value, Operation o
|
||||
}
|
||||
} else if (pairing == kPairTextureMatrix) {
|
||||
// Only allow matrix multiplication
|
||||
if (operation != kOpMultiply
|
||||
|| number_val.data().value<QMatrix4x4>().isIdentity()) {
|
||||
bool matrix_is_identity = false;
|
||||
|
||||
// FIXME: The matrix in the shader is transformed around footage+sequence resolution so we
|
||||
// need to do that here to determine if the matrix is truly identity. But to do that,
|
||||
// we need access to the texture parameters which is currently not possible.
|
||||
|
||||
if (operation != kOpMultiply || matrix_is_identity) {
|
||||
operation_is_noop = true;
|
||||
} else {
|
||||
// It's likely an alpha channel will result from this operation
|
||||
|
||||
+55
-7
@@ -24,6 +24,7 @@
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "common/xmlutils.h"
|
||||
#include "project/project.h"
|
||||
#include "project/item/footage/footage.h"
|
||||
@@ -177,6 +178,28 @@ void Node::InvalidateCache(const TimeRange &range, NodeInput *from, NodeInput *s
|
||||
SendInvalidateCache(range, source);
|
||||
}
|
||||
|
||||
void Node::BeginOperation()
|
||||
{
|
||||
foreach (NodeParam* param, params_) {
|
||||
if (param->type() == NodeParam::kOutput) {
|
||||
foreach (NodeEdgePtr edge, param->edges()) {
|
||||
edge->input()->parentNode()->BeginOperation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Node::EndOperation()
|
||||
{
|
||||
foreach (NodeParam* param, params_) {
|
||||
if (param->type() == NodeParam::kOutput) {
|
||||
foreach (NodeEdgePtr edge, param->edges()) {
|
||||
edge->input()->parentNode()->EndOperation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimeRange Node::InputTimeAdjustment(NodeInput *, const TimeRange &input_time) const
|
||||
{
|
||||
// Default behavior is no time adjustment at all
|
||||
@@ -195,10 +218,7 @@ void Node::SendInvalidateCache(const TimeRange &range, NodeInput *source)
|
||||
foreach (NodeParam* param, params_) {
|
||||
// If the Node is an output, relay the signal to any Nodes that are connected to it
|
||||
if (param->type() == NodeParam::kOutput) {
|
||||
|
||||
QVector<NodeEdgePtr> edges = param->edges();
|
||||
|
||||
foreach (NodeEdgePtr edge, edges) {
|
||||
foreach (NodeEdgePtr edge, param->edges()) {
|
||||
NodeInput* connected_input = edge->input();
|
||||
Node* connected_node = connected_input->parentNode();
|
||||
|
||||
@@ -359,11 +379,15 @@ void Node::Hash(QCryptographicHash &hash, const rational& time) const
|
||||
|
||||
// Footage timestamp
|
||||
if (stream->type() == Stream::kVideo) {
|
||||
hash.addData(QStringLiteral("%1/%2").arg(QString::number(input_time.numerator()),
|
||||
QString::number(input_time.denominator())).toUtf8());
|
||||
VideoStreamPtr video_stream = std::static_pointer_cast<VideoStream>(stream);
|
||||
|
||||
hash.addData(QString::number(static_cast<VideoStream*>(stream.get())->start_time()).toUtf8());
|
||||
int64_t video_ts = Timecode::time_to_timestamp(input_time, video_stream->timebase());
|
||||
|
||||
// Add timestamp in units of the video stream's timebase
|
||||
hash.addData(reinterpret_cast<const char*>(&video_ts), sizeof(int64_t));
|
||||
|
||||
// Add start time - used for both image sequences and video streams
|
||||
hash.addData(QString::number(video_stream->start_time()).toUtf8());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -537,6 +561,28 @@ bool Node::OutputsTo(const QString &id, bool recursively) const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Node::OutputsTo(NodeInput *input, bool recursively, bool include_arrays) const
|
||||
{
|
||||
QList<NodeOutput*> outputs = GetOutputs();
|
||||
|
||||
foreach (NodeOutput* output, outputs) {
|
||||
foreach (NodeEdgePtr edge, output->edges()) {
|
||||
NodeInput* connected = edge->input();
|
||||
|
||||
if (connected == input) {
|
||||
return true;
|
||||
} else if (include_arrays && input->IsArray()
|
||||
&& static_cast<NodeInputArray*>(input)->sub_params().contains(connected)) {
|
||||
return true;
|
||||
} else if (recursively && connected->parentNode()->OutputsTo(input, recursively, include_arrays)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Node::InputsFrom(Node *n, bool recursively) const
|
||||
{
|
||||
QList<NodeInput*> inputs = GetInputsIncludingArrays();
|
||||
@@ -649,6 +695,8 @@ QString Node::GetCategoryName(const CategoryID &c)
|
||||
return tr("Generator");
|
||||
case kCategoryChannels:
|
||||
return tr("Channel");
|
||||
case kCategoryTransition:
|
||||
return tr("Transition");
|
||||
case kCategoryUnknown:
|
||||
case kCategoryCount:
|
||||
break;
|
||||
|
||||
@@ -70,6 +70,7 @@ public:
|
||||
kCategoryGeneral,
|
||||
kCategoryTimeline,
|
||||
kCategoryChannels,
|
||||
kCategoryTransition,
|
||||
|
||||
kCategoryCount
|
||||
};
|
||||
@@ -220,6 +221,11 @@ public:
|
||||
*/
|
||||
bool OutputsTo(const QString& id, bool recursively) const;
|
||||
|
||||
/**
|
||||
* @brief Same as OutputsTo(Node*), but for a specific node input rather than just a node.
|
||||
*/
|
||||
bool OutputsTo(NodeInput* input, bool recursively, bool include_arrays) const;
|
||||
|
||||
/**
|
||||
* @brief Returns whether this node ever receives an input from a particular node instance
|
||||
*/
|
||||
@@ -298,6 +304,19 @@ public:
|
||||
*/
|
||||
virtual void InvalidateCache(const TimeRange& range, NodeInput* from, NodeInput* source);
|
||||
|
||||
/**
|
||||
* @brief Limits cache invalidation temporarily
|
||||
*
|
||||
* If you intend to do a number of operations in quick succession, you can optimize it by running
|
||||
* this function with EndOperation().
|
||||
*/
|
||||
virtual void BeginOperation();
|
||||
|
||||
/**
|
||||
* @brief Stops limiting cache invalidation and flushes changes
|
||||
*/
|
||||
virtual void EndOperation();
|
||||
|
||||
/**
|
||||
* @brief Adjusts time that should be sent to nodes connected to certain inputs.
|
||||
*
|
||||
|
||||
@@ -31,10 +31,8 @@ OLIVE_NAMESPACE_ENTER
|
||||
|
||||
TrackOutput::TrackOutput() :
|
||||
track_type_(Timeline::kTrackTypeNone),
|
||||
block_invalidate_cache_stack_(0),
|
||||
index_(-1),
|
||||
locked_(false),
|
||||
queued_length_change_(false)
|
||||
locked_(false)
|
||||
{
|
||||
block_input_ = new NodeInputArray("block_in", NodeParam::kAny);
|
||||
block_input_->set_is_keyframable(false);
|
||||
@@ -252,11 +250,24 @@ const QList<Block *> &TrackOutput::Blocks() const
|
||||
|
||||
void TrackOutput::InvalidateCache(const TimeRange &range, NodeInput *from, NodeInput *source)
|
||||
{
|
||||
if (block_invalidate_cache_stack_ == 0) {
|
||||
PushLengthChangeSignal(true);
|
||||
TimeRange limited;
|
||||
|
||||
Node::InvalidateCache(TimeRange(qMax(range.in(), rational(0)), qMin(range.out(), track_length())), from, source);
|
||||
if (block_input_->sub_params().contains(from)
|
||||
&& from->get_connected_node()
|
||||
&& from->get_connected_node()->IsBlock()) {
|
||||
// Limit the range signal to the corresponding block
|
||||
Block* b = static_cast<Block*>(from->get_connected_node());
|
||||
|
||||
if (range.out() <= b->in() || range.in() >= b->out()) {
|
||||
return;
|
||||
}
|
||||
|
||||
limited = TimeRange(qMax(range.in(), b->in()), qMin(range.out(), b->out()));
|
||||
} else {
|
||||
limited = TimeRange(qMax(range.in(), rational(0)), qMin(range.out(), track_length()));
|
||||
}
|
||||
|
||||
Node::InvalidateCache(limited, from, source);
|
||||
}
|
||||
|
||||
void TrackOutput::InsertBlockBefore(Block* block, Block* after)
|
||||
@@ -279,12 +290,12 @@ void TrackOutput::InsertBlockAfter(Block *block, Block *before)
|
||||
|
||||
void TrackOutput::PrependBlock(Block *block)
|
||||
{
|
||||
BlockInvalidateCache();
|
||||
BeginOperation();
|
||||
|
||||
block_input_->Prepend();
|
||||
NodeParam::ConnectEdge(block->output(), block_input_->First());
|
||||
|
||||
UnblockInvalidateCache();
|
||||
EndOperation();
|
||||
|
||||
// Everything has shifted at this point
|
||||
InvalidateCache(TimeRange(0, track_length()), block_input_, block_input_);
|
||||
@@ -292,57 +303,47 @@ void TrackOutput::PrependBlock(Block *block)
|
||||
|
||||
void TrackOutput::InsertBlockAtIndex(Block *block, int index)
|
||||
{
|
||||
BlockInvalidateCache();
|
||||
BeginOperation();
|
||||
|
||||
int insert_index = GetInputIndexFromCacheIndex(index);
|
||||
block_input_->InsertAt(insert_index);
|
||||
NodeParam::ConnectEdge(block->output(),
|
||||
block_input_->At(insert_index));
|
||||
|
||||
UnblockInvalidateCache();
|
||||
EndOperation();
|
||||
|
||||
InvalidateCache(TimeRange(block->in(), track_length()), block_input_, block_input_);
|
||||
}
|
||||
|
||||
void TrackOutput::AppendBlock(Block *block)
|
||||
{
|
||||
BlockInvalidateCache();
|
||||
BeginOperation();
|
||||
|
||||
block_input_->Append();
|
||||
NodeParam::ConnectEdge(block->output(), block_input_->Last());
|
||||
|
||||
UnblockInvalidateCache();
|
||||
EndOperation();
|
||||
|
||||
// Invalidate area that block was added to
|
||||
InvalidateCache(TimeRange(block->in(), track_length()), block_input_, block_input_);
|
||||
}
|
||||
|
||||
void TrackOutput::BlockInvalidateCache()
|
||||
{
|
||||
block_invalidate_cache_stack_++;
|
||||
}
|
||||
|
||||
void TrackOutput::UnblockInvalidateCache()
|
||||
{
|
||||
block_invalidate_cache_stack_--;
|
||||
}
|
||||
|
||||
void TrackOutput::RippleRemoveBlock(Block *block)
|
||||
{
|
||||
BlockInvalidateCache();
|
||||
BeginOperation();
|
||||
|
||||
rational remove_in = block->in();
|
||||
|
||||
block_input_->RemoveAt(GetInputIndexFromCacheIndex(block));
|
||||
|
||||
UnblockInvalidateCache();
|
||||
EndOperation();
|
||||
|
||||
InvalidateCache(TimeRange(remove_in, track_length()), block_input_, block_input_);
|
||||
}
|
||||
|
||||
void TrackOutput::ReplaceBlock(Block *old, Block *replace)
|
||||
{
|
||||
BlockInvalidateCache();
|
||||
BeginOperation();
|
||||
|
||||
int index_of_old_block = GetInputIndexFromCacheIndex(old);
|
||||
|
||||
@@ -352,7 +353,7 @@ void TrackOutput::ReplaceBlock(Block *old, Block *replace)
|
||||
NodeParam::ConnectEdge(replace->output(),
|
||||
block_input_->At(index_of_old_block));
|
||||
|
||||
UnblockInvalidateCache();
|
||||
EndOperation();
|
||||
|
||||
if (old->length() == replace->length()) {
|
||||
InvalidateCache(TimeRange(replace->in(), replace->out()), block_input_, block_input_);
|
||||
@@ -443,14 +444,6 @@ void TrackOutput::Hash(QCryptographicHash &hash, const rational &time) const
|
||||
}
|
||||
}
|
||||
|
||||
void TrackOutput::PushLengthChangeSignal(bool invalidate)
|
||||
{
|
||||
if (queued_length_change_) {
|
||||
queued_length_change_ = false;
|
||||
SetLengthInternal(queued_length_, invalidate);
|
||||
}
|
||||
}
|
||||
|
||||
void TrackOutput::SetTrackName(const QString &name)
|
||||
{
|
||||
track_name_ = name;
|
||||
@@ -507,15 +500,6 @@ int TrackOutput::GetInputIndexFromCacheIndex(Block *block)
|
||||
|
||||
void TrackOutput::SetLengthInternal(const rational &r, bool invalidate)
|
||||
{
|
||||
if (block_invalidate_cache_stack_ > 0) {
|
||||
queued_length_change_ = true;
|
||||
}
|
||||
|
||||
if (queued_length_change_) {
|
||||
queued_length_ = r;
|
||||
return;
|
||||
}
|
||||
|
||||
if (r != track_length_) {
|
||||
TimeRange invalidate_range(track_length_, r);
|
||||
|
||||
|
||||
@@ -166,10 +166,6 @@ public:
|
||||
*/
|
||||
void ReplaceBlock(Block* old, Block* replace);
|
||||
|
||||
void BlockInvalidateCache();
|
||||
|
||||
void UnblockInvalidateCache();
|
||||
|
||||
static TrackOutput* TrackFromBlock(const Block *block);
|
||||
|
||||
const rational& track_length() const;
|
||||
@@ -192,8 +188,6 @@ public:
|
||||
|
||||
virtual void Hash(QCryptographicHash& hash, const rational &time) const override;
|
||||
|
||||
void PushLengthChangeSignal(bool invalidate = false);
|
||||
|
||||
AudioVisualWaveform& waveform()
|
||||
{
|
||||
return waveform_;
|
||||
@@ -274,15 +268,10 @@ private:
|
||||
|
||||
QString track_name_;
|
||||
|
||||
int block_invalidate_cache_stack_;
|
||||
|
||||
int index_;
|
||||
|
||||
bool locked_;
|
||||
|
||||
bool queued_length_change_;
|
||||
rational queued_length_;
|
||||
|
||||
AudioVisualWaveform waveform_;
|
||||
QMutex waveform_lock_;
|
||||
|
||||
|
||||
@@ -151,9 +151,9 @@ void TrackList::RemoveTrack()
|
||||
|
||||
void TrackList::TrackConnected(NodeEdgePtr edge)
|
||||
{
|
||||
int track_index = track_input_->IndexOfSubParameter(edge->input());
|
||||
int input_index = track_input_->IndexOfSubParameter(edge->input());
|
||||
|
||||
Q_ASSERT(track_index >= 0);
|
||||
Q_ASSERT(input_index >= 0);
|
||||
|
||||
Node* connected_node = edge->output()->parentNode();
|
||||
|
||||
@@ -163,7 +163,7 @@ void TrackList::TrackConnected(NodeEdgePtr edge)
|
||||
{
|
||||
// Find "real" index
|
||||
TrackOutput* next = nullptr;
|
||||
for (int i=track_index+1; i<track_input_->GetSize(); i++) {
|
||||
for (int i=input_index+1; i<track_input_->GetSize(); i++) {
|
||||
Node* that_track = track_input_->At(i)->get_connected_node();
|
||||
|
||||
if (that_track && that_track->IsTrack()) {
|
||||
|
||||
@@ -24,7 +24,10 @@
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
ViewerOutput::ViewerOutput()
|
||||
ViewerOutput::ViewerOutput() :
|
||||
video_frame_cache_(this),
|
||||
audio_playback_cache_(this),
|
||||
operation_stack_(0)
|
||||
{
|
||||
texture_input_ = new NodeInput("tex_in", NodeInput::kTexture);
|
||||
AddInput(texture_input_);
|
||||
@@ -109,40 +112,60 @@ void ViewerOutput::InvalidateCache(const TimeRange &range, NodeInput *from, Node
|
||||
{
|
||||
emit GraphChangedFrom(source);
|
||||
|
||||
if (from == texture_input_ || from == samples_input_) {
|
||||
TimeRange invalidated_range(qMax(rational(), range.in()),
|
||||
qMin(GetLength(), range.out()));
|
||||
if (operation_stack_ == 0) {
|
||||
if (from == texture_input_ || from == samples_input_) {
|
||||
TimeRange invalidated_range(qMax(rational(), range.in()),
|
||||
qMin(GetLength(), range.out()));
|
||||
|
||||
if (invalidated_range.in() != invalidated_range.out()) {
|
||||
if (from == texture_input_) {
|
||||
video_frame_cache_.Invalidate(invalidated_range);
|
||||
} else {
|
||||
audio_playback_cache_.Invalidate(invalidated_range);
|
||||
if (invalidated_range.in() != invalidated_range.out()) {
|
||||
if (from == texture_input_) {
|
||||
video_frame_cache_.Invalidate(invalidated_range);
|
||||
} else {
|
||||
audio_playback_cache_.Invalidate(invalidated_range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VerifyLength();
|
||||
VerifyLength();
|
||||
}
|
||||
|
||||
Node::InvalidateCache(range, from, source);
|
||||
}
|
||||
|
||||
void ViewerOutput::set_video_params(const VideoParams &video)
|
||||
{
|
||||
bool size_changed = video_params_.width() != video.width() || video_params_.height() != video.height();
|
||||
bool timebase_changed = video_params_.time_base() != video.time_base();
|
||||
bool pixel_aspect_changed = video_params_.pixel_aspect_ratio() != video.pixel_aspect_ratio();
|
||||
bool interlacing_changed = video_params_.interlacing() != video.interlacing();
|
||||
|
||||
video_params_ = video;
|
||||
|
||||
video_frame_cache_.SetTimebase(video_params_.time_base());
|
||||
if (size_changed) {
|
||||
emit SizeChanged(video_params_.width(), video_params_.height());
|
||||
}
|
||||
|
||||
emit SizeChanged(video_params_.width(), video_params_.height());
|
||||
emit TimebaseChanged(video_params_.time_base());
|
||||
emit ParamsChanged();
|
||||
if (pixel_aspect_changed) {
|
||||
emit PixelAspectChanged(video_params_.pixel_aspect_ratio());
|
||||
}
|
||||
|
||||
if (interlacing_changed) {
|
||||
emit InterlacingChanged(video_params_.interlacing());
|
||||
}
|
||||
|
||||
if (timebase_changed) {
|
||||
video_frame_cache_.SetTimebase(video_params_.time_base());
|
||||
emit TimebaseChanged(video_params_.time_base());
|
||||
}
|
||||
|
||||
emit VideoParamsChanged();
|
||||
}
|
||||
|
||||
void ViewerOutput::set_audio_params(const AudioParams &audio)
|
||||
{
|
||||
audio_params_ = audio;
|
||||
|
||||
emit ParamsChanged();
|
||||
emit AudioParamsChanged();
|
||||
}
|
||||
|
||||
rational ViewerOutput::GetLength()
|
||||
@@ -247,6 +270,20 @@ void ViewerOutput::set_media_name(const QString &name)
|
||||
emit MediaNameChanged(media_name_);
|
||||
}
|
||||
|
||||
void ViewerOutput::BeginOperation()
|
||||
{
|
||||
operation_stack_++;
|
||||
|
||||
Node::BeginOperation();
|
||||
}
|
||||
|
||||
void ViewerOutput::EndOperation()
|
||||
{
|
||||
operation_stack_--;
|
||||
|
||||
Node::EndOperation();
|
||||
}
|
||||
|
||||
void ViewerOutput::TrackListAddedBlock(Block *block, int index)
|
||||
{
|
||||
Timeline::TrackType type = static_cast<TrackList*>(sender())->type();
|
||||
|
||||
@@ -118,6 +118,10 @@ public:
|
||||
return &audio_playback_cache_;
|
||||
}
|
||||
|
||||
virtual void BeginOperation() override;
|
||||
|
||||
virtual void EndOperation() override;
|
||||
|
||||
signals:
|
||||
void TimebaseChanged(const rational&);
|
||||
|
||||
@@ -127,7 +131,12 @@ signals:
|
||||
|
||||
void SizeChanged(int width, int height);
|
||||
|
||||
void ParamsChanged();
|
||||
void PixelAspectChanged(const rational& pixel_aspect);
|
||||
|
||||
void InterlacingChanged(VideoParams::Interlacing mode);
|
||||
|
||||
void VideoParamsChanged();
|
||||
void AudioParamsChanged();
|
||||
|
||||
void BlockAdded(Block* block, TrackReference track);
|
||||
void BlockRemoved(Block* block);
|
||||
@@ -164,6 +173,8 @@ private:
|
||||
|
||||
AudioPlaybackCache audio_playback_cache_;
|
||||
|
||||
int operation_stack_;
|
||||
|
||||
private slots:
|
||||
void UpdateTrackCache();
|
||||
|
||||
|
||||
+57
-38
@@ -185,16 +185,68 @@ NodeEdgePtr NodeParam::DisconnectForNewOutput(NodeInput *input)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString NodeParam::GetPrettyDataTypeName(const NodeParam::DataType &type)
|
||||
{
|
||||
switch (type) {
|
||||
case kNone:
|
||||
return tr("None");
|
||||
case kInt:
|
||||
case kCombo:
|
||||
return tr("Integer");
|
||||
case kFloat:
|
||||
return tr("Float");
|
||||
case kRational:
|
||||
return tr("Rational");
|
||||
case kBoolean:
|
||||
return tr("Boolean");
|
||||
case kColor:
|
||||
return tr("Color");
|
||||
case kMatrix:
|
||||
return tr("Matrix");
|
||||
case kText:
|
||||
return tr("Text");
|
||||
case kFont:
|
||||
return tr("Font");
|
||||
case kFile:
|
||||
return tr("File");
|
||||
case kTexture:
|
||||
return tr("Texture");
|
||||
case kSamples:
|
||||
return tr("Samples");
|
||||
case kFootage:
|
||||
return tr("Footage");
|
||||
case kVec2:
|
||||
return tr("Vector 2D");
|
||||
case kVec3:
|
||||
return tr("Vector 3D");
|
||||
case kVec4:
|
||||
return tr("Vector 4D");
|
||||
|
||||
case kDecimal:
|
||||
case kNumber:
|
||||
case kString:
|
||||
case kBuffer:
|
||||
case kVector:
|
||||
case kShaderJob:
|
||||
case kSampleJob:
|
||||
case kGenerateJob:
|
||||
case kAny:
|
||||
break;
|
||||
}
|
||||
|
||||
return tr("Unknown");
|
||||
}
|
||||
|
||||
QByteArray NodeParam::ValueToBytes(const NodeParam::DataType &type, const QVariant &value)
|
||||
{
|
||||
switch (type) {
|
||||
case kInt: return ValueToBytesInternal<int>(value);
|
||||
case kFloat: return ValueToBytesInternal<float>(value);
|
||||
case kInt: return ValueToBytesInternal<int64_t>(value);
|
||||
case kFloat: return ValueToBytesInternal<double>(value);
|
||||
case kColor: return ValueToBytesInternal<Color>(value);
|
||||
case kText: return ValueToBytesInternal<QString>(value);
|
||||
case kText: return value.toString().toUtf8();
|
||||
case kBoolean: return ValueToBytesInternal<bool>(value);
|
||||
case kFont: return ValueToBytesInternal<QString>(value); // FIXME: This should probably be a QFont?
|
||||
case kFile: return ValueToBytesInternal<QString>(value);
|
||||
case kFont: return value.toString().toUtf8();
|
||||
case kFile: return value.toString().toUtf8();
|
||||
case kMatrix: return ValueToBytesInternal<QMatrix4x4>(value);
|
||||
case kRational: return ValueToBytesInternal<rational>(value);
|
||||
case kVec2: return ValueToBytesInternal<QVector2D>(value);
|
||||
@@ -222,39 +274,6 @@ QByteArray NodeParam::ValueToBytes(const NodeParam::DataType &type, const QVaria
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
NodeParam::DataType NodeParam::StringToDataType(const QString &s)
|
||||
{
|
||||
QString type_id = s.toLower();
|
||||
|
||||
if (type_id == QStringLiteral("float")) {
|
||||
return kFloat;
|
||||
} else if (type_id == QStringLiteral("int")) {
|
||||
return kInt;
|
||||
} else if (type_id == QStringLiteral("rational")) {
|
||||
return kRational;
|
||||
} else if (type_id == QStringLiteral("bool")) {
|
||||
return kBoolean;
|
||||
} else if (type_id == QStringLiteral("color")) {
|
||||
return kColor;
|
||||
} else if (type_id == QStringLiteral("matrix")) {
|
||||
return kMatrix;
|
||||
} else if (type_id == QStringLiteral("text")) {
|
||||
return kText;
|
||||
} else if (type_id == QStringLiteral("texture")) {
|
||||
return kTexture;
|
||||
} else if (type_id == QStringLiteral("vec2")) {
|
||||
return kVec2;
|
||||
} else if (type_id == QStringLiteral("vec3")) {
|
||||
return kVec3;
|
||||
} else if (type_id == QStringLiteral("vec4")) {
|
||||
return kVec4;
|
||||
} else if (type_id == QStringLiteral("combo")) {
|
||||
return kCombo;
|
||||
}
|
||||
|
||||
return kAny;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
QByteArray NodeParam::ValueToBytesInternal(const QVariant &v)
|
||||
{
|
||||
|
||||
+2
-7
@@ -67,7 +67,7 @@ public:
|
||||
/**
|
||||
* Integer type
|
||||
*
|
||||
* Resolves to `int` (may resolve to `long` in the future).
|
||||
* Resolves to int64_t.
|
||||
*/
|
||||
kInt = 0x1,
|
||||
|
||||
@@ -378,18 +378,13 @@ public:
|
||||
/**
|
||||
* @brief Get a human-readable translated name for a certain data type
|
||||
*/
|
||||
static QString GetDefaultDataTypeName(const DataType &type);
|
||||
static QString GetPrettyDataTypeName(const DataType &type);
|
||||
|
||||
/**
|
||||
* @brief Convert a value from a NodeParam into bytes
|
||||
*/
|
||||
static QByteArray ValueToBytes(const DataType &type, const QVariant& value);
|
||||
|
||||
/**
|
||||
* @brief Convert a string to a data type
|
||||
*/
|
||||
static DataType StringToDataType(const QString& s);
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Signal emitted when an edge is added to this parameter
|
||||
|
||||
+10
-5
@@ -41,11 +41,7 @@ NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRa
|
||||
database.Insert(input, ProcessInput(input, input_time));
|
||||
}
|
||||
|
||||
// Insert global variables
|
||||
NodeValueTable global;
|
||||
global.Push(NodeParam::kFloat, range.in().toDouble(), nullptr, QStringLiteral("time_in"));
|
||||
global.Push(NodeParam::kFloat, range.out().toDouble(), nullptr, QStringLiteral("time_out"));
|
||||
database.Insert(QStringLiteral("global"), global);
|
||||
AddGlobalsToDatabase(database, range);
|
||||
|
||||
return database;
|
||||
}
|
||||
@@ -156,6 +152,15 @@ QVariant NodeTraverser::GetCachedFrame(const Node *node, const rational &time)
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void NodeTraverser::AddGlobalsToDatabase(NodeValueDatabase &db, const TimeRange& range)
|
||||
{
|
||||
// Insert global variables
|
||||
NodeValueTable global;
|
||||
global.Push(NodeParam::kFloat, range.in().toDouble(), nullptr, QStringLiteral("time_in"));
|
||||
global.Push(NodeParam::kFloat, range.out().toDouble(), nullptr, QStringLiteral("time_out"));
|
||||
db.Insert(QStringLiteral("global"), global);
|
||||
}
|
||||
|
||||
void NodeTraverser::PostProcessTable(const Node *node, const TimeRange &range, NodeValueTable &output_params)
|
||||
{
|
||||
bool got_cached_frame = false;
|
||||
|
||||
@@ -56,6 +56,8 @@ protected:
|
||||
|
||||
virtual QVariant GetCachedFrame(const Node *node, const rational &time);
|
||||
|
||||
static void AddGlobalsToDatabase(NodeValueDatabase& db, const TimeRange &range);
|
||||
|
||||
private:
|
||||
void PostProcessTable(const Node *node, const TimeRange &range, NodeValueTable &output_params);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user