Merge master into branch

This commit is contained in:
Thomas Wilshaw
2020-08-17 18:42:58 +01:00
316 changed files with 13821 additions and 4934 deletions
+27 -28
View File
@@ -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
View File
@@ -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)
+6
View File
@@ -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
View File
@@ -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
)
+29 -9
View File
@@ -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();
+2 -2
View File
@@ -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_;
+6 -3
View File
@@ -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)));
}
}
}
+30
View File
@@ -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
+36
View File
@@ -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
+5 -4
View File
@@ -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;
+2 -2
View File
@@ -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_;
+8 -2
View File
@@ -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
+6
View File
@@ -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
+7
View File
@@ -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 &params)
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
View File
@@ -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 &params);
QString GetIndexFilename();
bool open_;
QMutex mutex_;
+63
View File
@@ -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)
+6
View File
@@ -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_;
+48
View File
@@ -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
+2
View File
@@ -51,6 +51,8 @@ public:
static bool IsCodecAStillImage(Codec c);
static QStringList GetPixelFormatsForCodec(Codec c);
};
OLIVE_NAMESPACE_EXIT
+262 -268
View File
@@ -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 &timestamp, 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_) {
+11 -9
View File
@@ -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 &divider) 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_;
+34 -3
View File
@@ -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
View File
@@ -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_;
-5
View File
@@ -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_;
};
+31 -17
View File
@@ -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;
}
+4 -2
View File
@@ -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_;
+4
View File
@@ -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
+82
View File
@@ -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
+34
View File
@@ -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
+2
View File
@@ -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
+47 -29
View File
@@ -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
+4 -6
View File
@@ -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);
};
+91
View File
@@ -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
+36
View File
@@ -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
+3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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
)
+107
View File
@@ -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
+58
View File
@@ -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
+16 -27
View File
@@ -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(&params);
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()),
-2
View File
@@ -81,8 +81,6 @@ private slots:
void ResolutionChanged();
void VideoCodecChanged();
void UpdateViewerDimensions();
void StartExport();
+39 -20
View File
@@ -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
+24 -3
View File
@@ -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
+2 -35
View File
@@ -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
+16 -9
View File
@@ -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
+40 -72
View File
@@ -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
+83 -16
View File
@@ -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();
+217 -26
View File
@@ -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
+21 -3
View File
@@ -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();
};
+1
View File
@@ -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
+239
View File
@@ -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
+7 -14
View File
@@ -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();
+124 -212
View File
@@ -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;
+28 -13
View File
@@ -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);
+147 -9
View File
@@ -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
View File
@@ -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);
}
-10
View File
@@ -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
-2
View File
@@ -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:
+3
View File
@@ -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
+160 -18
View File
@@ -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
+27 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
+47 -14
View File
@@ -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);
+4
View File
@@ -50,6 +50,10 @@ private:
NodeInput* valign_input_;
NodeInput* font_input_;
NodeInput* font_size_input_;
};
OLIVE_NAMESPACE_EXIT
+72 -26
View File
@@ -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
View File
@@ -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;
+1 -1
View File
@@ -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);
}
}
+17 -3
View File
@@ -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
View File
@@ -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;
+19
View File
@@ -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.
*
+27 -43
View File
@@ -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);
-11
View File
@@ -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_;
+3 -3
View File
@@ -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()) {
+53 -16
View File
@@ -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();
+12 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+2
View File
@@ -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