diff --git a/app/audio/CMakeLists.txt b/app/audio/CMakeLists.txt index 5d190af1f..08273c9af 100644 --- a/app/audio/CMakeLists.txt +++ b/app/audio/CMakeLists.txt @@ -18,13 +18,9 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} audio/audiomanager.cpp audio/audiomanager.h + audio/audioprocessor.cpp + audio/audioprocessor.h audio/audiovisualwaveform.cpp audio/audiovisualwaveform.h - audio/packedprocessor.cpp - audio/packedprocessor.h - audio/planarprocessor.cpp - audio/planarprocessor.h - audio/tempoprocessor.cpp - audio/tempoprocessor.h PARENT_SCOPE ) diff --git a/app/audio/audiomanager.cpp b/app/audio/audiomanager.cpp index 878944505..3817f285a 100644 --- a/app/audio/audiomanager.cpp +++ b/app/audio/audiomanager.cpp @@ -26,7 +26,6 @@ #include -#include "audio/packedprocessor.h" #include "config/config.h" namespace olive { @@ -81,10 +80,11 @@ int InputCallback(const void *input, void *output, unsigned long frameCount, con return paContinue; } -void AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &samples) +bool AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &samples, QString *error) { if (output_device_ == paNoDevice) { - return; + if (error) *error = tr("No output device is set"); + return false; } if (output_params_ != params || output_stream_ == nullptr) { @@ -94,7 +94,13 @@ void AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &sam PaStreamParameters p = GetPortAudioParams(params, output_device_); - Pa_OpenStream(&output_stream_, nullptr, &p, output_params_.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, OutputCallback, output_buffer_); + PaError r = Pa_OpenStream(&output_stream_, nullptr, &p, output_params_.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, OutputCallback, output_buffer_); + if (r != paNoError) { + // Unhandled error + //qCritical() << "Failed to open output stream:" << Pa_GetErrorText(r); + if (error) *error = Pa_GetErrorText(r); + return false; + } output_buffer_->set_bytes_per_frame(output_params_.samples_to_bytes(1)); } @@ -104,6 +110,8 @@ void AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &sam if (!Pa_IsStreamActive(output_stream_)) { Pa_StartStream(output_stream_); } + + return true; } void AudioManager::ClearBufferedOutput() @@ -189,7 +197,7 @@ void AudioManager::HardReset() Pa_Initialize(); } -bool AudioManager::StartRecording(const EncodingParams ¶ms) +bool AudioManager::StartRecording(const EncodingParams ¶ms, QString *error_str) { if (input_device_ == paNoDevice) { return false; @@ -203,12 +211,19 @@ bool AudioManager::StartRecording(const EncodingParams ¶ms) PaStreamParameters p = GetPortAudioParams(params.audio_params(), input_device_); - if (Pa_OpenStream(&input_stream_, &p, nullptr, params.audio_params().sample_rate(), paFramesPerBufferUnspecified, paNoFlag, InputCallback, input_encoder_) == paNoError) { - if (Pa_StartStream(input_stream_) == paNoError) { + PaError r = Pa_OpenStream(&input_stream_, &p, nullptr, params.audio_params().sample_rate(), paFramesPerBufferUnspecified, paNoFlag, InputCallback, input_encoder_); + if (r == paNoError) { + //const PaStreamInfo* info = Pa_GetStreamInfo(input_stream_); + r = Pa_StartStream(input_stream_); + if (r == paNoError) { return true; } } + if (error_str) { + *error_str = Pa_GetErrorText(r); + } + StopRecording(); return false; } @@ -235,7 +250,7 @@ PaDeviceIndex AudioManager::FindConfigDeviceByName(bool is_output_device) { QString entry = is_output_device ? QStringLiteral("AudioOutput") : QStringLiteral("AudioInput"); - return FindDeviceByName(Config::Current()[entry].toString(), is_output_device); + return FindDeviceByName(OLIVE_CONFIG_STR(entry).toString(), is_output_device); } PaDeviceIndex AudioManager::FindDeviceByName(const QString &s, bool is_output_device) diff --git a/app/audio/audiomanager.h b/app/audio/audiomanager.h index bf71e2223..2916d29d2 100644 --- a/app/audio/audiomanager.h +++ b/app/audio/audiomanager.h @@ -27,6 +27,7 @@ #include #include "audiovisualwaveform.h" +#include "audio/audioprocessor.h" #include "common/define.h" #include "codec/ffmpeg/ffmpegencoder.h" #include "render/audioparams.h" @@ -52,7 +53,7 @@ public: void SetOutputNotifyInterval(int n); - void PushToOutput(const AudioParams ¶ms, const QByteArray& samples); + bool PushToOutput(const AudioParams ¶ms, const QByteArray& samples, QString *error = nullptr); void ClearBufferedOutput(); @@ -74,7 +75,7 @@ public: void HardReset(); - bool StartRecording(const EncodingParams ¶ms); + bool StartRecording(const EncodingParams ¶ms, QString *error_str = nullptr); void StopRecording(); @@ -86,6 +87,8 @@ public: signals: void OutputNotify(); + void OutputParamsChanged(); + private: AudioManager(); @@ -104,6 +107,7 @@ private: PaDeviceIndex input_device_; PaStream *input_stream_; + FFmpegEncoder *input_encoder_; }; diff --git a/app/audio/audioprocessor.cpp b/app/audio/audioprocessor.cpp new file mode 100644 index 000000000..b9c95bb6d --- /dev/null +++ b/app/audio/audioprocessor.cpp @@ -0,0 +1,302 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "audioprocessor.h" + +extern "C" { +#include +#include +} + +#include "common/ffmpegutils.h" + +namespace olive { + +AudioProcessor::AudioProcessor() +{ + filter_graph_ = nullptr; + in_frame_ = nullptr; + out_frame_ = nullptr; +} + +AudioProcessor::~AudioProcessor() +{ + Close(); +} + +bool AudioProcessor::Open(const AudioParams &from, const AudioParams &to, double tempo) +{ + if (filter_graph_) { + qWarning() << "Tried to open a processor that was already open"; + return false; + } + + filter_graph_ = avfilter_graph_alloc(); + if (!filter_graph_) { + qCritical() << "Failed to allocate filter graph"; + return false; + } + + from_fmt_ = FFmpegUtils::GetFFmpegSampleFormat(from.format()); + to_fmt_ = FFmpegUtils::GetFFmpegSampleFormat(to.format()); + + // Set up audio buffer args + char filter_args[200]; + snprintf(filter_args, 200, "time_base=%d/%d:sample_rate=%d:sample_fmt=%d:channel_layout=0x%" PRIx64, + 1, + from.sample_rate(), + from.sample_rate(), + from_fmt_, + from.channel_layout()); + + int r; + + // Create buffersrc (input) + r = avfilter_graph_create_filter(&buffersrc_ctx_, avfilter_get_by_name("abuffer"), "in", filter_args, nullptr, filter_graph_); + if (r < 0) { + qCritical() << "Failed to create buffersrc:" << r; + Close(); + return false; + } + + // Store "previous" filter for linking + AVFilterContext *previous_filter = buffersrc_ctx_; + + // Create tempo + bool create_tempo; + if ((create_tempo = !qFuzzyCompare(tempo, 1.0))) { + // Create audio tempo filters: FFmpeg's atempo can only be set between 0.5 and 2.0. If the requested speed is outside + // those boundaries, we need to daisychain more than one together. + double base = (tempo > 1.0) ? 2.0 : 0.5; + double speed_log = log(tempo) / log(base); + + // This is the number of how many 0.5 or 2.0 tempos we need to daisychain + int whole = qFloor(speed_log); + + // Set speed_log to the remainder + speed_log -= whole; + + for (int i=0;i<=whole;i++) { + double filter_tempo = (i == whole) ? qPow(base, speed_log) : base; + + if (qFuzzyCompare(filter_tempo, 1.0)) { + // This filter would do nothing + continue; + } + + previous_filter = CreateTempoFilter(filter_graph_, + previous_filter, + filter_tempo); + + if (!previous_filter) { + qCritical() << "Failed to create audio tempo filter"; + Close(); + return false; + } + } + } + + // Create conversion filter + if (from.sample_rate() != to.sample_rate() || from.channel_layout() != to.channel_layout() || from.format() != to.format() + || (to.FormatIsPlanar() && create_tempo)) { // Tempo processor automatically converts to packed, + // so if the desired output is planar, it'll need + // to be converted + snprintf(filter_args, 200, "sample_fmts=%s:sample_rates=%d:channel_layouts=0x%" PRIx64, + av_get_sample_fmt_name(to_fmt_), + to.sample_rate(), + to.channel_layout()); + + AVFilterContext *c; + r = avfilter_graph_create_filter(&c, avfilter_get_by_name("aformat"), "fmt", filter_args, nullptr, filter_graph_); + if (r < 0) { + qCritical() << "Failed to create format conversion filter:" << r << filter_args; + Close(); + return false; + } + + r = avfilter_link(previous_filter, 0, c, 0); + if (r < 0) { + qCritical() << "Failed to link filters:" << r; + Close(); + return false; + } + + previous_filter = c; + } + + // Create buffersink (output) + r = avfilter_graph_create_filter(&buffersink_ctx_, avfilter_get_by_name("abuffersink"), "out", nullptr, nullptr, filter_graph_); + if (r < 0) { + qCritical() << "Failed to create buffersink:" << r; + Close(); + return false; + } + + r = avfilter_link(previous_filter, 0, buffersink_ctx_, 0); + if (r < 0) { + qCritical() << "Failed to link filters:" << r; + Close(); + return false; + } + + r = avfilter_graph_config(filter_graph_, nullptr); + if (r < 0) { + qCritical() << "Failed to configure graph:" << r; + Close(); + return false; + } + + in_frame_ = av_frame_alloc(); + if (in_frame_) { + in_frame_->sample_rate = from.sample_rate(); + in_frame_->format = from_fmt_; + in_frame_->channel_layout = from.channel_layout(); + in_frame_->channels = from.channel_count(); + in_frame_->pts = 0; + } else { + qCritical() << "Failed to allocate input frame"; + Close(); + return false; + } + + out_frame_ = av_frame_alloc(); + if (!out_frame_) { + qCritical() << "Failed to allocate output frame"; + Close(); + return false; + } + + from_ = from; + to_ = to; + + return true; +} + +void AudioProcessor::Close() +{ + if (filter_graph_) { + avfilter_graph_free(&filter_graph_); + filter_graph_ = nullptr; + buffersrc_ctx_ = nullptr; + buffersink_ctx_ = nullptr; + } + + if (in_frame_) { + av_frame_free(&in_frame_); + in_frame_ = nullptr; + } + + if (out_frame_) { + av_frame_free(&out_frame_); + out_frame_ = nullptr; + } +} + +int AudioProcessor::Convert(float **in, int nb_in_samples, AudioProcessor::Buffer *output) +{ + if (!IsOpen()) { + qCritical() << "Tried to convert on closed processor"; + return -1; + } + + int r = 0; + + if (in && nb_in_samples) { + // Set frame parameters + in_frame_->nb_samples = nb_in_samples; + for (int i=0; idata[i] = reinterpret_cast(in[i]); + in_frame_->linesize[i] = from_.samples_to_bytes(nb_in_samples); + } + + r = av_buffersrc_add_frame_flags(buffersrc_ctx_, in_frame_, AV_BUFFERSRC_FLAG_KEEP_REF); + if (r < 0) { + qCritical() << "Failed to add frame to buffersrc:" << r; + return r; + } + } + + if (output) { + int nb_channels = to_.channel_count(); + + if (to_.FormatIsPacked()) { + nb_channels = 1; + } + + AudioProcessor::Buffer &result = *output; + result.resize(nb_channels); + + int byte_offset = 0; + + while (true) { + av_frame_unref(out_frame_); + r = av_buffersink_get_frame(buffersink_ctx_, out_frame_); + if (r < 0) { + if (r == AVERROR(EAGAIN)) { + r = 0; + } else { + // Handle unexpected error + qCritical() << "Failed to pull from buffersink:" << r; + } + break; + } + + int nb_bytes = out_frame_->nb_samples * to_.bytes_per_sample_per_channel(); + if (to_.FormatIsPacked()) { + nb_bytes *= to_.channel_count(); + } + + for (int i=0; idata[i], nb_bytes); + } + byte_offset += nb_bytes; + } + av_frame_unref(out_frame_); + } + + return r; +} + +void AudioProcessor::Flush() +{ + int r = av_buffersrc_add_frame_flags(buffersrc_ctx_, nullptr, AV_BUFFERSRC_FLAG_KEEP_REF); + if (r < 0) { + qCritical() << "Failed to flush:" << r; + } +} + +AVFilterContext *AudioProcessor::CreateTempoFilter(AVFilterGraph* graph, AVFilterContext* link, const double &tempo) +{ + // Set up tempo param, which is taken as a C string + char speed_param[20]; + snprintf(speed_param, 20, "%f", tempo); + + AVFilterContext* tempo_ctx = nullptr; + + if (avfilter_graph_create_filter(&tempo_ctx, avfilter_get_by_name("atempo"), "atempo", speed_param, nullptr, graph) >= 0 + && avfilter_link(link, 0, tempo_ctx, 0) == 0) { + return tempo_ctx; + } + + return nullptr; +} + +} diff --git a/app/audio/tempoprocessor.h b/app/audio/audioprocessor.h similarity index 63% rename from app/audio/tempoprocessor.h rename to app/audio/audioprocessor.h index c56eafc5d..38826c707 100644 --- a/app/audio/tempoprocessor.h +++ b/app/audio/audioprocessor.h @@ -18,14 +18,8 @@ ***/ -#ifndef TEMPOPROCESSOR_H -#define TEMPOPROCESSOR_H - -#ifdef __MINGW32__ -#ifndef __USE_MINGW_ANSI_STDIO -#define __USE_MINGW_ANSI_STDIO -#endif -#endif +#ifndef AUDIOPROCESSOR_H +#define AUDIOPROCESSOR_H #include @@ -37,28 +31,28 @@ extern "C" { namespace olive { -class TempoProcessor +class AudioProcessor { public: - TempoProcessor(); + AudioProcessor(); - ~TempoProcessor(); + ~AudioProcessor(); - DISABLE_COPY_MOVE(TempoProcessor) + DISABLE_COPY_MOVE(AudioProcessor) - bool IsOpen() const; + bool Open(const AudioParams &from, const AudioParams &to, double tempo = 1.0); - const double& GetSpeed() const; + void Close(); - bool Open(const AudioParams& params, const double &speed); + bool IsOpen() const { return filter_graph_; } - void Push(const QByteArray &packed); + using Buffer = QVector; + int Convert(float **in, int nb_in_samples, AudioProcessor::Buffer *output); void Flush(); - QByteArray Pull(); - - void Close(); + const AudioParams &from() const { return from_; } + const AudioParams &to() const { return to_; } private: static AVFilterContext* CreateTempoFilter(AVFilterGraph *graph, AVFilterContext *link, const double& tempo); @@ -69,17 +63,18 @@ private: AVFilterContext* buffersink_ctx_; - AudioParams params_; + AudioParams from_; + AVSampleFormat from_fmt_; - int64_t timestamp_; + AudioParams to_; + AVSampleFormat to_fmt_; - double speed_; + AVFrame *in_frame_; - bool open_; + AVFrame *out_frame_; - bool flushed_; }; } -#endif // TEMPOPROCESSOR_H +#endif // AUDIOPROCESSOR_H diff --git a/app/audio/audiovisualwaveform.cpp b/app/audio/audiovisualwaveform.cpp index af439bca0..7719d7090 100644 --- a/app/audio/audiovisualwaveform.cpp +++ b/app/audio/audiovisualwaveform.cpp @@ -40,10 +40,10 @@ AudioVisualWaveform::AudioVisualWaveform() : } } -void AudioVisualWaveform::OverwriteSamplesFromBuffer(SampleBufferPtr samples, int sample_rate, const rational &start, double target_rate, Sample& data, int &start_index, int &samples_length) +void AudioVisualWaveform::OverwriteSamplesFromBuffer(const SampleBuffer &samples, int sample_rate, const rational &start, double target_rate, Sample& data, int &start_index, int &samples_length) { start_index = time_to_samples(start, target_rate); - samples_length = time_to_samples(static_cast(samples->sample_count()) / static_cast(sample_rate), target_rate); + samples_length = time_to_samples(static_cast(samples.sample_count()) / static_cast(sample_rate), target_rate); int end_index = start_index + samples_length; if (data.size() < end_index) { @@ -54,7 +54,7 @@ void AudioVisualWaveform::OverwriteSamplesFromBuffer(SampleBufferPtr samples, in for (int i=0; isample_count()); + int src_end = qMin(qRound((double(i + channels_) * chunk_size)) / channels_, samples.sample_count()); Sample summary = SumSamples(samples, src_start, @@ -91,7 +91,7 @@ void AudioVisualWaveform::OverwriteSamplesFromMipmap(const AudioVisualWaveform:: input_length = samples_length; } -void AudioVisualWaveform::OverwriteSamples(SampleBufferPtr samples, int sample_rate, const rational &start) +void AudioVisualWaveform::OverwriteSamples(const SampleBuffer &samples, int sample_rate, const rational &start) { if (!channels_) { qWarning() << "Failed to write samples - channel count is zero"; @@ -125,7 +125,7 @@ void AudioVisualWaveform::OverwriteSamples(SampleBufferPtr samples, int sample_r current_mipmap->second); } - rational sample_length(samples->sample_count(), sample_rate); + rational sample_length(samples.sample_count(), sample_rate); length_ = qMax(length_, start + sample_length); } @@ -277,7 +277,7 @@ AudioVisualWaveform::Sample AudioVisualWaveform::GetSummaryFromTime(const ration return AudioVisualWaveform::Sample(channel_count(), {0, 0}); } -void ExpandMinMaxChannel(float *a, int start, int length, float &min_val, float &max_val) +void ExpandMinMaxChannel(const float *a, int start, int length, float &min_val, float &max_val) { #if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM) // SSE optimized @@ -321,13 +321,13 @@ void ExpandMinMaxChannel(float *a, int start, int length, float &min_val, float #endif } -AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(SampleBufferPtr samples, int start_index, int length) +AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(const SampleBuffer &samples, int start_index, int length) { - int channels = samples->audio_params().channel_count(); + int channels = samples.audio_params().channel_count(); AudioVisualWaveform::Sample summed_samples(channels); - for (int channel=0; channelaudio_params().channel_count(); channel++) { - ExpandMinMaxChannel(samples->data(channel), start_index, length, summed_samples[channel].min, summed_samples[channel].max); + for (int channel=0; channel. - -***/ - -#include "packedprocessor.h" - -#include "common/ffmpegutils.h" - -namespace olive { - -PackedProcessor::PackedProcessor() : - swr_ctx_(nullptr) -{ -} - -PackedProcessor::~PackedProcessor() -{ - Close(); -} - -bool PackedProcessor::Open(const AudioParams ¶ms) -{ - if (IsOpen()) { - return true; - } - - swr_ctx_ = swr_alloc_set_opts(nullptr, - params.channel_layout(), - FFmpegUtils::GetFFmpegSampleFormat(AudioParams::GetPackedEquivalent(params.format())), - params.sample_rate(), - params.channel_layout(), - FFmpegUtils::GetFFmpegSampleFormat(params.format()), - params.sample_rate(), - 0, - nullptr); - - if (!swr_ctx_) { - qCritical() << "Failed to allocate resample context"; - return false; - } - - if (swr_init(swr_ctx_) < 0) { - qCritical() << "Failed to init resample context"; - swr_free(&swr_ctx_); - return false; - } - - return true; -} - -QByteArray PackedProcessor::Convert(SampleBufferPtr planar) -{ - if (!IsOpen()) { - qCritical() << "Tried to convert while closed"; - return QByteArray(); - } - - int nb_samples = planar->sample_count(); - if (nb_samples == 0) { - return QByteArray(); - } - - QByteArray output(planar->audio_params().samples_to_bytes(nb_samples), Qt::Uninitialized); - uint8_t *output_data = reinterpret_cast(output.data()); - - int ret = swr_convert(swr_ctx_, &output_data, nb_samples, - const_cast(reinterpret_cast(planar->to_raw_ptrs())), - nb_samples); - if (ret < 0) { - char buf[200]; - av_strerror(ret, buf, 200); - qDebug() << "Packed processor failed with error:" << buf << ret; - } - - return output; -} - -void PackedProcessor::Close() -{ - if (swr_ctx_) { - swr_free(&swr_ctx_); - } -} - -} diff --git a/app/audio/planarprocessor.cpp b/app/audio/planarprocessor.cpp deleted file mode 100644 index 0d5222b76..000000000 --- a/app/audio/planarprocessor.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "planarprocessor.h" - -#include "common/ffmpegutils.h" - -namespace olive { - -PlanarProcessor::PlanarProcessor() : - swr_ctx_(nullptr) -{ -} - -PlanarProcessor::~PlanarProcessor() -{ - Close(); -} - -bool PlanarProcessor::Open(const AudioParams ¶ms) -{ - if (IsOpen()) { - return true; - } - - swr_ctx_ = swr_alloc_set_opts(nullptr, - params.channel_layout(), - FFmpegUtils::GetFFmpegSampleFormat(AudioParams::GetPlanarEquivalent(params.format())), - params.sample_rate(), - params.channel_layout(), - FFmpegUtils::GetFFmpegSampleFormat(params.format()), - params.sample_rate(), - 0, - nullptr); - - if (!swr_ctx_) { - qCritical() << "Failed to allocate resample context"; - return false; - } - - if (swr_init(swr_ctx_) < 0) { - qCritical() << "Failed to init resample context"; - swr_free(&swr_ctx_); - return false; - } - - params_ = params; - - return true; -} - -SampleBufferPtr PlanarProcessor::Convert(const QByteArray &packed) -{ - if (!IsOpen()) { - qCritical() << "Tried to convert while closed"; - return nullptr; - } - - if (packed.isEmpty()) { - return nullptr; - } - - int nb_samples_per_channel = params_.bytes_to_samples(packed.size()); - - SampleBufferPtr output = SampleBuffer::CreateAllocated(params_, nb_samples_per_channel); - - const uint8_t *input = reinterpret_cast(packed.constData()); - int ret = swr_convert(swr_ctx_, - reinterpret_cast(output->to_raw_ptrs()), nb_samples_per_channel, - &input, nb_samples_per_channel); - if (ret < 0) { - char buf[200]; - av_strerror(ret, buf, 200); - qDebug() << "Planar processor failed with error:" << buf << ret; - } - - return output; -} - -void PlanarProcessor::Close() -{ - if (swr_ctx_) { - swr_free(&swr_ctx_); - } -} - -} diff --git a/app/audio/tempoprocessor.cpp b/app/audio/tempoprocessor.cpp deleted file mode 100644 index f22946227..000000000 --- a/app/audio/tempoprocessor.cpp +++ /dev/null @@ -1,269 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "tempoprocessor.h" - -extern "C" { -#include -#include -#include -} - -#include - -#include "common/ffmpegutils.h" - -namespace olive { - -TempoProcessor::TempoProcessor() : - filter_graph_(nullptr), - buffersrc_ctx_(nullptr), - buffersink_ctx_(nullptr), - open_(false) -{ -} - -TempoProcessor::~TempoProcessor() -{ - Close(); -} - -bool TempoProcessor::IsOpen() const -{ - return open_; -} - -const double &TempoProcessor::GetSpeed() const -{ - return speed_; -} - -bool TempoProcessor::Open(const AudioParams ¶ms, const double& speed) -{ - if (open_) { - return true; - } - - params_ = params; - speed_ = speed; - - // Create AVFilterGraph instance - filter_graph_ = avfilter_graph_alloc(); - if (!filter_graph_) { - qCritical() << "Failed to create AVFilterGraph"; - Close(); - return false; - } - - // Set up audio buffer args - char filter_args[200]; - snprintf(filter_args, 200, "time_base=%d/%d:sample_rate=%d:sample_fmt=%d:channel_layout=0x%" PRIx64, - 1, - params_.sample_rate(), - params_.sample_rate(), - FFmpegUtils::GetFFmpegSampleFormat(params_.format()), - params.channel_layout()); - - // Create buffer and buffersink - if (avfilter_graph_create_filter(&buffersrc_ctx_, avfilter_get_by_name("abuffer"), "in", filter_args, nullptr, filter_graph_) < 0) { - qCritical() << "Failed to create audio buffer source"; - Close(); - return false; - } - - if (avfilter_graph_create_filter(&buffersink_ctx_, avfilter_get_by_name("abuffersink"), "out", nullptr, nullptr, filter_graph_) < 0) { - qCritical() << "Failed to create audio buffer sink"; - Close(); - return false; - } - - // Create audio tempo filters: FFmpeg's atempo can only be set between 0.5 and 2.0. If the requested speed is outside - // those boundaries, we need to daisychain more than one together. - double base = (speed_ > 1.0) ? 2.0 : 0.5; - double speed_log = log(speed_) / log(base); - - // This is the number of how many 0.5 or 2.0 tempos we need to daisychain - int whole = qFloor(speed_log); - - // Set speed_log to the remainder - speed_log -= whole; - - AVFilterContext* previous_filter = buffersrc_ctx_; - - for (int i=0;i<=whole;i++) { - double filter_tempo = (i == whole) ? qPow(base, speed_log) : base; - - if (qFuzzyCompare(filter_tempo, 1.0)) { - // This filter would do nothing - continue; - } - - previous_filter = CreateTempoFilter(filter_graph_, - previous_filter, - filter_tempo); - - if (!previous_filter) { - qCritical() << "Failed to create audio tempo filter"; - Close(); - return false; - } - } - - // Link the last filter to the buffersink - if (avfilter_link(previous_filter, 0, buffersink_ctx_, 0) != 0) { - qCritical() << "Failed to link final filter and buffer sink"; - Close(); - return false; - } - - // Config graph - if (avfilter_graph_config(filter_graph_, nullptr) < 0) { - qCritical() << "Failed to configure filter graph"; - Close(); - return false; - } - - timestamp_ = 0; - - open_ = true; - - flushed_ = false; - - return true; -} - -void TempoProcessor::Push(const QByteArray &packed) -{ - if (!IsOpen()) { - qWarning() << "Tried to push to closed TempoProcessor"; - return; - } - - if (flushed_) { - qWarning() << "Tried to push to flushed TempoProcessor"; - return; - } - - AVFrame* src_frame = av_frame_alloc(); - - if (!src_frame) { - qCritical() << "Failed to allocate source frame"; - return; - } - - // Allocate a buffer for the number of samples we got - src_frame->sample_rate = params_.sample_rate(); - src_frame->format = FFmpegUtils::GetFFmpegSampleFormat(params_.format()); - src_frame->channel_layout = params_.channel_layout(); - src_frame->nb_samples = params_.bytes_to_samples(packed.size()); - src_frame->pts = timestamp_; - timestamp_ += src_frame->nb_samples; - - if (av_frame_get_buffer(src_frame, 0) < 0) { - qCritical() << "Failed to allocate buffer for source frame"; - av_frame_free(&src_frame); - return; - } - - // Copy buffer from data array to frame - memcpy(src_frame->data[0], packed, packed.size()); - - int ret = av_buffersrc_add_frame_flags(buffersrc_ctx_, src_frame, AV_BUFFERSRC_FLAG_KEEP_REF); - - if (ret < 0) { - qCritical() << "Failed to feed buffer source" << ret; - } - - if (src_frame) { - av_frame_free(&src_frame); - } -} - -void TempoProcessor::Flush() -{ - if (!flushed_) { - int ret = av_buffersrc_add_frame_flags(buffersrc_ctx_, nullptr, AV_BUFFERSRC_FLAG_KEEP_REF); - if (ret < 0) { - qCritical() << "Failed to feed buffer source" << ret; - } - flushed_ = true; - } -} - -QByteArray TempoProcessor::Pull() -{ - QByteArray b; - AVFrame *processed_frame = av_frame_alloc(); - - // Try to pull samples from the buffersink - int ret = av_buffersink_get_frame(buffersink_ctx_, processed_frame); - - if (ret < 0) { - // We couldn't pull for some reason, if the error was EAGAIN, we just need to send more samples. Otherwise the - // error might be fatal... - if (ret != AVERROR(EAGAIN)) { - qCritical() << "Failed to pull from buffersink" << ret; - } - - av_frame_free(&processed_frame); - return b; - } - - b.resize(params_.samples_to_bytes(processed_frame->nb_samples)); - - // Copy the bytes - memcpy(b.data(), processed_frame->data[0], b.size()); - - // If the index has reached the limit of this processed frame, we can dispose of the frame now - av_frame_free(&processed_frame); - - return b; -} - -void TempoProcessor::Close() -{ - open_ = false; - - if (filter_graph_) { - avfilter_graph_free(&filter_graph_); - filter_graph_ = nullptr; - } - - buffersrc_ctx_ = nullptr; - buffersink_ctx_ = nullptr; -} - -AVFilterContext *TempoProcessor::CreateTempoFilter(AVFilterGraph* graph, AVFilterContext* link, const double &tempo) -{ - // Set up tempo param, which is taken as a C string - char speed_param[20]; - snprintf(speed_param, 20, "%f", tempo); - - AVFilterContext* tempo_ctx = nullptr; - - if (avfilter_graph_create_filter(&tempo_ctx, avfilter_get_by_name("atempo"), "atempo", speed_param, nullptr, graph) >= 0 - && avfilter_link(link, 0, tempo_ctx, 0) == 0) { - return tempo_ctx; - } - - return nullptr; -} - -} diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 1145a6967..c487e2425 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -109,7 +109,7 @@ FramePtr Decoder::RetrieveVideo(const rational &timecode, const RetrieveVideoPar return RetrieveVideoInternal(timecode, divider, cancelled); } -Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBufferPtr dest, const TimeRange &range, const AudioParams ¶ms, const QString& cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode) +Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBuffer &dest, const TimeRange &range, const AudioParams ¶ms, const QString& cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode) { QMutexLocker locker(&mutex_); @@ -272,14 +272,14 @@ bool Decoder::ConformAudioInternal(const QVector &filenames, const Audi return false; } -bool Decoder::RetrieveAudioFromConform(SampleBufferPtr sample_buffer, const QVector &conform_filenames, const TimeRange& range, Footage::LoopMode loop_mode, const AudioParams &input_params) +bool Decoder::RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, const TimeRange& range, Footage::LoopMode loop_mode, const AudioParams &input_params) { PlanarFileDevice input; if (input.open(conform_filenames, QFile::ReadOnly)) { qint64 read_index = input_params.time_to_bytes(range.in()) / input_params.channel_count(); qint64 write_index = 0; - const qint64 buffer_length_in_bytes = sample_buffer->sample_count() * input_params.bytes_per_sample_per_channel(); + const qint64 buffer_length_in_bytes = sample_buffer.sample_count() * input_params.bytes_per_sample_per_channel(); while (write_index < buffer_length_in_bytes) { if (loop_mode == Footage::kLoopModeLoop) { @@ -297,15 +297,15 @@ bool Decoder::RetrieveAudioFromConform(SampleBufferPtr sample_buffer, const QVec if (read_index < 0) { // Reading before 0, write silence here until audio data would actually start write_count = qMin(-read_index, buffer_length_in_bytes); - sample_buffer->silence_bytes(write_index, write_index + write_count); + sample_buffer.silence_bytes(write_index, write_index + write_count); } else if (read_index >= input.size()) { // Reading after data length, write silence until the end of the buffer write_count = buffer_length_in_bytes - write_index; - sample_buffer->silence_bytes(write_index, write_index + write_count); + sample_buffer.silence_bytes(write_index, write_index + write_count); } else { write_count = qMin(input.size() - read_index, buffer_length_in_bytes - write_index); input.seek(read_index); - input.read(reinterpret_cast(sample_buffer->to_raw_ptrs()), write_count, write_index); + input.read(reinterpret_cast(sample_buffer.to_raw_ptrs().data()), write_count, write_index); } read_index += write_count; diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 2234753d9..3191ade23 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -201,7 +201,7 @@ public: * * This function is thread safe and can only run while the decoder is open. \see Open() */ - RetrieveAudioStatus RetrieveAudio(SampleBufferPtr dest, const TimeRange& range, const AudioParams& params, const QString &cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode); + RetrieveAudioStatus RetrieveAudio(SampleBuffer &dest, const TimeRange& range, const AudioParams& params, const QString &cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode); /** * @brief Determine the last time this decoder instance was used in any way @@ -307,7 +307,7 @@ signals: private: void UpdateLastAccessed(); - bool RetrieveAudioFromConform(SampleBufferPtr sample_buffer, const QVector &conform_filenames, const TimeRange &range, Footage::LoopMode loop_mode, const AudioParams ¶ms); + bool RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector &conform_filenames, const TimeRange &range, Footage::LoopMode loop_mode, const AudioParams ¶ms); CodecStream stream_; diff --git a/app/codec/encoder.h b/app/codec/encoder.h index 102b0430e..f94473dbb 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -183,7 +183,7 @@ public slots: virtual bool Open() = 0; virtual bool WriteFrame(olive::FramePtr frame, olive::rational time) = 0; - virtual bool WriteAudio(olive::SampleBufferPtr audio) = 0; + virtual bool WriteAudio(const olive::SampleBuffer &audio) = 0; virtual bool WriteSubtitle(const SubtitleBlock *sub_block) = 0; virtual void Close() = 0; diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 04f889a34..df6c874da 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -515,7 +515,7 @@ bool FFmpegDecoder::ConformAudioInternal(const QVector &filenames, cons // Resample audio to our destination parameters nb_samples = swr_convert(resampler, - reinterpret_cast(data.to_raw_ptrs()), + reinterpret_cast(data.to_raw_ptrs().data()), nb_samples, const_cast(frame->data), frame->nb_samples); @@ -526,7 +526,7 @@ bool FFmpegDecoder::ConformAudioInternal(const QVector &filenames, cons nb_bytes_per_channel = params.samples_to_bytes(nb_samples) / nb_channels; // Write to files - wave_out.write(const_cast(reinterpret_cast(data.to_raw_ptrs())), nb_bytes_per_channel); + wave_out.write(const_cast(reinterpret_cast(data.to_raw_ptrs().data())), nb_bytes_per_channel); } // Free buffer diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 19db89732..6c42df4da 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -266,26 +266,26 @@ fail: return success; } -bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio) +bool FFmpegEncoder::WriteAudio(const SampleBuffer &audio) { bool result = true; // Create input buffer int input_sample_count = 0; uint8_t** input_data = nullptr; - if (audio) { - input_sample_count = audio->sample_count(); + if (audio.is_allocated()) { + input_sample_count = audio.sample_count(); int input_linesize; - av_samples_alloc_array_and_samples(&input_data, &input_linesize, audio->audio_params().channel_count(), - input_sample_count, FFmpegUtils::GetFFmpegSampleFormat(audio->audio_params().format()), 0); + av_samples_alloc_array_and_samples(&input_data, &input_linesize, audio.audio_params().channel_count(), + input_sample_count, FFmpegUtils::GetFFmpegSampleFormat(audio.audio_params().format()), 0); - for (int i=0; iaudio_params().channel_count(); i++) { - memcpy(input_data[i], audio->data(i), input_sample_count * audio->audio_params().bytes_per_sample_per_channel()); + for (int i=0; iaudio_params(), const_cast(input_data), input_sample_count); + result = WriteAudioData(audio.audio_params().is_valid() ? audio.audio_params() : params().audio_params(), const_cast(input_data), input_sample_count); if (input_data) { av_freep(&input_data[0]); @@ -774,7 +774,7 @@ void FFmpegEncoder::FlushEncoders() } if (audio_codec_ctx_) { - WriteAudio(nullptr); + WriteAudio(SampleBuffer()); FlushCodecCtx(audio_codec_ctx_, audio_stream_); } diff --git a/app/codec/ffmpeg/ffmpegencoder.h b/app/codec/ffmpeg/ffmpegencoder.h index a38955f3f..465f7c40a 100644 --- a/app/codec/ffmpeg/ffmpegencoder.h +++ b/app/codec/ffmpeg/ffmpegencoder.h @@ -47,7 +47,7 @@ public: virtual bool WriteFrame(olive::FramePtr frame, olive::rational time) override; - virtual bool WriteAudio(olive::SampleBufferPtr audio) override; + virtual bool WriteAudio(const olive::SampleBuffer &audio) override; bool WriteAudioData(const AudioParams &audio_params, const uint8_t **data, int input_sample_count); diff --git a/app/codec/oiio/oiioencoder.cpp b/app/codec/oiio/oiioencoder.cpp index f872a758b..679dbd550 100644 --- a/app/codec/oiio/oiioencoder.cpp +++ b/app/codec/oiio/oiioencoder.cpp @@ -62,7 +62,7 @@ bool OIIOEncoder::WriteFrame(FramePtr frame, rational time) return true; } -bool OIIOEncoder::WriteAudio(SampleBufferPtr audio) +bool OIIOEncoder::WriteAudio(const SampleBuffer &audio) { // Do nothing return false; diff --git a/app/codec/oiio/oiioencoder.h b/app/codec/oiio/oiioencoder.h index 7325cdf49..1099e68ca 100644 --- a/app/codec/oiio/oiioencoder.h +++ b/app/codec/oiio/oiioencoder.h @@ -35,7 +35,7 @@ public slots: virtual bool Open() override; virtual bool WriteFrame(olive::FramePtr frame, olive::rational time) override; - virtual bool WriteAudio(SampleBufferPtr audio) override; + virtual bool WriteAudio(const SampleBuffer &audio) override; virtual bool WriteSubtitle(const SubtitleBlock *sub_block) override; virtual void Close() override; diff --git a/app/codec/samplebuffer.cpp b/app/codec/samplebuffer.cpp index 1dede4ae5..b7d93ec83 100644 --- a/app/codec/samplebuffer.cpp +++ b/app/codec/samplebuffer.cpp @@ -20,6 +20,8 @@ #include "samplebuffer.h" +#include "common/cpuoptimize.h" + namespace olive { SampleBuffer::SampleBuffer() : @@ -27,25 +29,18 @@ SampleBuffer::SampleBuffer() : { } -SampleBufferPtr SampleBuffer::Create() +SampleBuffer::SampleBuffer(const AudioParams &audio_params, const rational &length) : + audio_params_(audio_params) { - return std::make_shared(); + sample_count_per_channel_ = audio_params_.time_to_samples(length); + allocate(); } -SampleBufferPtr SampleBuffer::CreateAllocated(const AudioParams &audio_params, const rational &length) +SampleBuffer::SampleBuffer(const AudioParams &audio_params, int samples_per_channel) : + audio_params_(audio_params), + sample_count_per_channel_(samples_per_channel) { - return CreateAllocated(audio_params, audio_params.time_to_samples(length)); -} - -SampleBufferPtr SampleBuffer::CreateAllocated(const AudioParams &audio_params, int samples_per_channel) -{ - SampleBufferPtr buffer = Create(); - - buffer->set_audio_params(audio_params); - buffer->set_sample_count(samples_per_channel); - buffer->allocate(); - - return buffer; + allocate(); } const AudioParams &SampleBuffer::audio_params() const @@ -104,14 +99,11 @@ void SampleBuffer::allocate() for (int i=0; i; - /** * @brief A buffer of audio samples * @@ -42,12 +39,8 @@ class SampleBuffer { public: SampleBuffer(); - - static SampleBufferPtr Create(); - static SampleBufferPtr CreateAllocated(const AudioParams& audio_params, const rational& length); - static SampleBufferPtr CreateAllocated(const AudioParams& audio_params, int samples_per_channel); - - DISABLE_COPY_MOVE(SampleBuffer) + SampleBuffer(const AudioParams& audio_params, const rational& length); + SampleBuffer(const AudioParams& audio_params, int samples_per_channel); const AudioParams& audio_params() const; void set_audio_params(const AudioParams& params); @@ -69,9 +62,13 @@ public: return data_.at(channel).constData(); } - float **to_raw_ptrs() + QVector to_raw_ptrs() { - return raw_ptrs_.data(); + QVector r(data_.size()); + for (int i=0; i > data_; - QVector raw_ptrs_; }; } -Q_DECLARE_METATYPE(olive::SampleBufferPtr) +Q_DECLARE_METATYPE(olive::SampleBuffer) #endif // SAMPLEBUFFER_H diff --git a/app/common/qtutils.cpp b/app/common/qtutils.cpp index f4f363880..560566029 100644 --- a/app/common/qtutils.cpp +++ b/app/common/qtutils.cpp @@ -79,4 +79,40 @@ QString QtUtils::GetFormattedDateTime(const QDateTime &dt) return dt.toString(Qt::TextDate); } +QStringList QtUtils::WordWrapString(const QString &s, const QFontMetrics &fm, int bounding_width) +{ + QStringList list; + + QStringList lines = s.split('\n'); + + // Iterate every line + for (int i=0; i 1 && QFontMetricsWidth(fm, this_line) >= bounding_width) { + for (int j=this_line.size()-1; j>=0; j--) { + if (this_line.at(j).isSpace()) { + QString chopped = this_line.left(j); + if (QFontMetricsWidth(fm, chopped) < bounding_width) { + list.append(chopped); + + int k = j+1; + while (k < this_line.size() && this_line.at(k).isSpace()) { + k++; + } + this_line.remove(0, k); + break; + } + } + } + } + + if (!this_line.isEmpty()) { + list.append(this_line); + } + } + + return list; +} + } diff --git a/app/common/qtutils.h b/app/common/qtutils.h index 45321c76b..2c5f6b652 100644 --- a/app/common/qtutils.h +++ b/app/common/qtutils.h @@ -62,6 +62,8 @@ public: static QString GetFormattedDateTime(const QDateTime &dt); + static QStringList WordWrapString(const QString &s, const QFontMetrics &fm, int bounding_width); + }; } diff --git a/app/config/config.cpp b/app/config/config.cpp index d114b0a10..0aaaa6253 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -121,6 +121,10 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("AudioOutput"), NodeValue::kText, QString()); SetEntryInternal(QStringLiteral("AudioInput"), NodeValue::kText, QString()); + SetEntryInternal(QStringLiteral("AudioOutputSampleRate"), NodeValue::kInt, 48000); + SetEntryInternal(QStringLiteral("AudioOutputChannelLayout"), NodeValue::kInt, AV_CH_LAYOUT_STEREO); + SetEntryInternal(QStringLiteral("AudioOutputSampleFormat"), NodeValue::kInt, AudioParams::kFormatSigned16Packed); + SetEntryInternal(QStringLiteral("AudioRecordingFormat"), NodeValue::kInt, ExportFormat::kFormatWAV); SetEntryInternal(QStringLiteral("AudioRecordingCodec"), NodeValue::kInt, ExportCodec::kCodecPCM); SetEntryInternal(QStringLiteral("AudioRecordingSampleRate"), NodeValue::kInt, 48000); diff --git a/app/config/config.h b/app/config/config.h index b5c18e063..b15362802 100644 --- a/app/config/config.h +++ b/app/config/config.h @@ -31,6 +31,7 @@ namespace olive { #define OLIVE_CONFIG(x) Config::Current()[QStringLiteral(x)] +#define OLIVE_CONFIG_STR(x) Config::Current()[x] class Config { public: diff --git a/app/core.cpp b/app/core.cpp index f13960cf1..a465d8add 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -108,7 +108,7 @@ void Core::DeclareTypesForQt() qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); - qRegisterMetaType(); + qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); @@ -794,7 +794,7 @@ void Core::StartGUI(bool full_screen) connect(this, &Core::ProjectClosed, main_window_, &MainWindow::ProjectClose); // Start autorecovery timer using the config value as its interval - SetAutorecoveryInterval(Config::Current()["AutorecoveryInterval"].toInt()); + SetAutorecoveryInterval(OLIVE_CONFIG("AutorecoveryInterval").toInt()); connect(&autorecovery_timer_, &QTimer::timeout, this, &Core::SaveAutorecovery); autorecovery_timer_.start(); @@ -960,7 +960,7 @@ bool Core::RevertProjectInternal(Project *p, bool by_opening_existing) void Core::SaveAutorecovery() { - if (Config::Current()[QStringLiteral("AutorecoveryEnabled")].toBool()) { + if (OLIVE_CONFIG("AutorecoveryEnabled").toBool()) { foreach (Project* p, open_projects_) { if (!p->has_autorecovery_been_saved()) { QDir project_autorecovery_dir(QDir(FileFunctions::GetAutoRecoveryRoot()).filePath(p->GetUuid().toString())); @@ -986,7 +986,7 @@ void Core::SaveAutorecovery() realname_file.close(); } - int64_t max_recoveries_per_file = Config::Current()[QStringLiteral("AutorecoveryMaximum")].toLongLong(); + int64_t max_recoveries_per_file = OLIVE_CONFIG("AutorecoveryMaximum").toLongLong(); // Since we write an extra file, increment total allowed files by 1 max_recoveries_per_file++; @@ -1075,12 +1075,12 @@ Folder *Core::GetSelectedFolderInActiveProject() const Timecode::Display Core::GetTimecodeDisplay() const { - return static_cast(Config::Current()["TimecodeDisplay"].toInt()); + return static_cast(OLIVE_CONFIG("TimecodeDisplay").toInt()); } void Core::SetTimecodeDisplay(Timecode::Display d) { - Config::Current()["TimecodeDisplay"] = d; + OLIVE_CONFIG("TimecodeDisplay") = d; emit TimecodeDisplayChanged(d); } @@ -1202,7 +1202,7 @@ void Core::SetStartupLocale() } } - QString use_locale = Config::Current()[QStringLiteral("Language")].toString(); + QString use_locale = OLIVE_CONFIG("Language").toString(); if (use_locale.isEmpty()) { // No configured locale, auto-detect the system's locale @@ -1412,25 +1412,6 @@ int Core::CountFilesInFileList(const QFileInfoList &filenames) return file_count; } -QString GetRenderModePreferencePrefix(RenderMode::Mode mode, const QString &preference) { - QString key; - - key.append((mode == RenderMode::kOffline) ? QStringLiteral("Offline") : QStringLiteral("Online")); - key.append(preference); - - return key; -} - -QVariant Core::GetPreferenceForRenderMode(RenderMode::Mode mode, const QString &preference) -{ - return Config::Current()[GetRenderModePreferencePrefix(mode, preference)]; -} - -void Core::SetPreferenceForRenderMode(RenderMode::Mode mode, const QString &preference, const QVariant &value) -{ - Config::Current()[GetRenderModePreferencePrefix(mode, preference)] = value; -} - bool Core::LabelNodes(const QVector &nodes, MultiUndoCommand *parent) { if (nodes.isEmpty()) { diff --git a/app/core.h b/app/core.h index 759cdd522..8466fa435 100644 --- a/app/core.h +++ b/app/core.h @@ -247,9 +247,6 @@ public: */ static int CountFilesInFileList(const QFileInfoList &filenames); - static QVariant GetPreferenceForRenderMode(RenderMode::Mode mode, const QString& preference); - static void SetPreferenceForRenderMode(RenderMode::Mode mode, const QString& preference, const QVariant& value); - /** * @brief Show a dialog to the user to rename a set of nodes */ diff --git a/app/dialog/about/about.cpp b/app/dialog/about/about.cpp index e3500e217..066f8b537 100644 --- a/app/dialog/about/about.cpp +++ b/app/dialog/about/about.cpp @@ -132,7 +132,7 @@ AboutDialog::AboutDialog(bool welcome_dialog, QWidget *parent) : void AboutDialog::accept() { if (dont_show_again_checkbox_ && dont_show_again_checkbox_->isChecked()) { - Config::Current()[QStringLiteral("ShowWelcomeDialog")] = false; + OLIVE_CONFIG("ShowWelcomeDialog") = false; } QDialog::accept(); diff --git a/app/dialog/export/codec/h264section.h b/app/dialog/export/codec/h264section.h index 78fbef19a..8e095d938 100644 --- a/app/dialog/export/codec/h264section.h +++ b/app/dialog/export/codec/h264section.h @@ -38,8 +38,8 @@ public: int GetValue() const; - static const int kDefaultH264CRF = 23; - static const int kDefaultH265CRF = 28; + static const int kDefaultH264CRF = 18; + static const int kDefaultH265CRF = 23; private: static const int kMinimumCRF = 0; diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 8723634e2..58813f7d5 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -208,7 +208,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : video_tab_->height_slider()->SetDefaultValue(vp.height()); video_tab_->SetSelectedFrameRate(vp.frame_rate()); video_tab_->pixel_aspect_combobox()->SetPixelAspectRatio(vp.pixel_aspect_ratio()); - video_tab_->pixel_format_field()->SetPixelFormat(static_cast(Config::Current()[QStringLiteral("OnlinePixelFormat")].toInt())); + video_tab_->pixel_format_field()->SetPixelFormat(static_cast(OLIVE_CONFIG("OnlinePixelFormat").toInt())); video_tab_->interlaced_combobox()->SetInterlaceMode(vp.interlacing()); audio_tab_->sample_rate_combobox()->SetSampleRate(ap.sample_rate()); audio_tab_->sample_format_combobox()->SetAttemptToRestoreFormat(false); diff --git a/app/dialog/preferences/tabs/preferencesappearancetab.cpp b/app/dialog/preferences/tabs/preferencesappearancetab.cpp index 761ea18dd..63c5da053 100644 --- a/app/dialog/preferences/tabs/preferencesappearancetab.cpp +++ b/app/dialog/preferences/tabs/preferencesappearancetab.cpp @@ -74,7 +74,7 @@ PreferencesAppearanceTab::PreferencesAppearanceTab() color_layout->addWidget(new QLabel(cat_name), i, 0); ColorCodingComboBox* ccc = new ColorCodingComboBox(); - ccc->SetColor(Config::Current()[QStringLiteral("CatColor%1").arg(i)].toInt()); + ccc->SetColor(OLIVE_CONFIG_STR(QStringLiteral("CatColor%1").arg(i)).toInt()); color_layout->addWidget(ccc, i, 1); color_btns_.append(ccc); } @@ -92,7 +92,7 @@ PreferencesAppearanceTab::PreferencesAppearanceTab() marker_layout->addWidget(new QLabel("Default Marker Color"), 0, 0); marker_btn_ = new ColorCodingComboBox(); - marker_btn_->SetColor(Config::Current()[QStringLiteral("MarkerColor")].toInt()); + marker_btn_->SetColor(OLIVE_CONFIG("MarkerColor").toInt()); marker_layout->addWidget(marker_btn_, 0, 1); appearance_layout->addWidget(marker_group, row, 0, 1, 2); @@ -109,14 +109,14 @@ void PreferencesAppearanceTab::Accept(MultiUndoCommand *command) if (style_path != StyleManager::GetStyle()) { StyleManager::SetStyle(style_path); - Config::Current()[QStringLiteral("Style")] = style_path; + OLIVE_CONFIG("Style") = style_path; } for (int i=0; iGetSelectedColor(); + OLIVE_CONFIG_STR(QStringLiteral("CatColor%1").arg(i)) = color_btns_.at(i)->GetSelectedColor(); } - Config::Current()[QStringLiteral("MarkerColor")] = marker_btn_->GetSelectedColor(); + OLIVE_CONFIG("MarkerColor") = marker_btn_->GetSelectedColor(); } } diff --git a/app/dialog/preferences/tabs/preferencesaudiotab.cpp b/app/dialog/preferences/tabs/preferencesaudiotab.cpp index c8760ae8a..7d2edc892 100644 --- a/app/dialog/preferences/tabs/preferencesaudiotab.cpp +++ b/app/dialog/preferences/tabs/preferencesaudiotab.cpp @@ -69,6 +69,40 @@ PreferencesAudioTab::PreferencesAudioTab() audio_output_devices_ = new QComboBox(); output_layout->addWidget(audio_output_devices_, row, 1); + + row++; + + { + int output_row = 0; + + QGroupBox *output_param_group = new QGroupBox(tr("Advanced")); + output_layout->addWidget(output_param_group, row, 0, 1, 2); + + QGridLayout *output_param_layout = new QGridLayout(output_param_group); + + output_param_layout->addWidget(new QLabel(tr("Sample Rate:")), output_row, 0); + + output_rate_combo_ = new SampleRateComboBox(); + output_rate_combo_->SetSampleRate(OLIVE_CONFIG("AudioOutputSampleRate").toInt()); + output_param_layout->addWidget(output_rate_combo_, output_row, 1); + + output_row++; + + output_param_layout->addWidget(new QLabel(tr("Channel Layout:")), output_row, 0); + + output_ch_layout_combo_ = new ChannelLayoutComboBox(); + output_ch_layout_combo_->SetChannelLayout(OLIVE_CONFIG("AudioOutputChannelLayout").toULongLong()); + output_param_layout->addWidget(output_ch_layout_combo_, output_row, 1); + + output_row++; + + output_param_layout->addWidget(new QLabel(tr("Sample Format:")), output_row, 0); + + output_fmt_combo_ = new SampleFormatComboBox(); + output_fmt_combo_->SetPackedFormats(); + output_fmt_combo_->SetSampleFormat(static_cast(OLIVE_CONFIG("AudioOutputSampleFormat").toInt())); + output_param_layout->addWidget(output_fmt_combo_, output_row, 1); + } } row = 0; @@ -146,12 +180,18 @@ void PreferencesAudioTab::Accept(MultiUndoCommand *command) AudioManager::instance()->SetOutputDevice(output_device); AudioManager::instance()->SetInputDevice(input_device); + OLIVE_CONFIG("AudioOutputSampleRate") = output_rate_combo_->GetSampleRate(); + OLIVE_CONFIG("AudioOutputChannelLayout") = QVariant::fromValue(output_ch_layout_combo_->GetChannelLayout()); + OLIVE_CONFIG("AudioOutputSampleFormat") = output_fmt_combo_->GetSampleFormat(); + OLIVE_CONFIG("AudioRecordingFormat") = record_format_combo_->GetFormat(); OLIVE_CONFIG("AudioRecordingCodec") = record_options_->GetCodec(); OLIVE_CONFIG("AudioRecordingSampleRate") = record_options_->sample_rate_combobox()->GetSampleRate(); OLIVE_CONFIG("AudioRecordingChannelLayout") = QVariant::fromValue(record_options_->channel_layout_combobox()->GetChannelLayout()); OLIVE_CONFIG("AudioRecordingBitRate") = QVariant::fromValue(record_options_->bit_rate_slider()->GetValue()); OLIVE_CONFIG("AudioRecordingSampleFormat") = record_options_->sample_format_combobox()->GetSampleFormat(); + + emit AudioManager::instance()->OutputParamsChanged(); } void PreferencesAudioTab::RefreshBackends() diff --git a/app/dialog/preferences/tabs/preferencesaudiotab.h b/app/dialog/preferences/tabs/preferencesaudiotab.h index 8fccd4276..550b6fa9e 100644 --- a/app/dialog/preferences/tabs/preferencesaudiotab.h +++ b/app/dialog/preferences/tabs/preferencesaudiotab.h @@ -61,6 +61,10 @@ private: */ QPushButton* refresh_devices_btn_; + SampleRateComboBox *output_rate_combo_; + ChannelLayoutComboBox *output_ch_layout_combo_; + SampleFormatComboBox *output_fmt_combo_; + ExportFormatComboBox *record_format_combo_; ExportAudioTab *record_options_; diff --git a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp index 8d71b3df5..d16c7dce5 100644 --- a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp +++ b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp @@ -108,10 +108,8 @@ void PreferencesBehaviorTab::Accept(MultiUndoCommand *command) { Q_UNUSED(command) - QMap::const_iterator iterator; - - for (iterator=config_map_.begin();iterator!=config_map_.end();iterator++) { - Config::Current()[iterator.value()] = (iterator.key()->checkState(0) == Qt::Checked); + for (auto iterator=config_map_.begin();iterator!=config_map_.end();iterator++) { + OLIVE_CONFIG_STR(iterator.value()) = (iterator.key()->checkState(0) == Qt::Checked); } } @@ -119,7 +117,7 @@ QTreeWidgetItem* PreferencesBehaviorTab::AddItem(const QString &text, const QStr { QTreeWidgetItem* item = new QTreeWidgetItem({text}); item->setToolTip(0, tooltip); - item->setCheckState(0, Config::Current()[config_key].toBool() ? Qt::Checked : Qt::Unchecked); + item->setCheckState(0, OLIVE_CONFIG_STR(config_key).toBool() ? Qt::Checked : Qt::Unchecked); config_map_.insert(item, config_key); diff --git a/app/dialog/preferences/tabs/preferencesdisktab.cpp b/app/dialog/preferences/tabs/preferencesdisktab.cpp index f5258f34e..3d3ee3fa1 100644 --- a/app/dialog/preferences/tabs/preferencesdisktab.cpp +++ b/app/dialog/preferences/tabs/preferencesdisktab.cpp @@ -71,7 +71,7 @@ PreferencesDiskTab::PreferencesDiskTab() cache_ahead_slider_ = new FloatSlider(); cache_ahead_slider_->SetFormat(tr("%1 seconds")); cache_ahead_slider_->SetMinimum(0); - cache_ahead_slider_->SetValue(Config::Current()["DiskCacheAhead"].value().toDouble()); + cache_ahead_slider_->SetValue(OLIVE_CONFIG("DiskCacheAhead").value().toDouble()); cache_behavior_layout->addWidget(cache_ahead_slider_, row, 1); cache_behavior_layout->addWidget(new QLabel(tr("Cache Behind:")), row, 2); @@ -79,7 +79,7 @@ PreferencesDiskTab::PreferencesDiskTab() cache_behind_slider_ = new FloatSlider(); cache_behind_slider_->SetMinimum(0); cache_behind_slider_->SetFormat(tr("%1 seconds")); - cache_behind_slider_->SetValue(Config::Current()["DiskCacheBehind"].value().toDouble()); + cache_behind_slider_->SetValue(OLIVE_CONFIG("DiskCacheBehind").value().toDouble()); cache_behavior_layout->addWidget(cache_behind_slider_, row, 3); outer_layout->addStretch(); @@ -115,8 +115,8 @@ void PreferencesDiskTab::Accept(MultiUndoCommand *command) 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())); + OLIVE_CONFIG("DiskCacheBehind") = QVariant::fromValue(rational::fromDouble(cache_behind_slider_->GetValue())); + OLIVE_CONFIG("DiskCacheAhead") = QVariant::fromValue(rational::fromDouble(cache_ahead_slider_->GetValue())); } } diff --git a/app/dialog/preferences/tabs/preferencesgeneraltab.cpp b/app/dialog/preferences/tabs/preferencesgeneraltab.cpp index 3c869bb73..2e6425731 100644 --- a/app/dialog/preferences/tabs/preferencesgeneraltab.cpp +++ b/app/dialog/preferences/tabs/preferencesgeneraltab.cpp @@ -55,7 +55,7 @@ PreferencesGeneralTab::PreferencesGeneralTab() AddLanguage(l); } - QString current_language = Config::Current()[QStringLiteral("Language")].toString(); + QString current_language = OLIVE_CONFIG("Language").toString(); if (current_language.isEmpty()) { // No configured language, use system language current_language = QLocale::system().name(); @@ -86,7 +86,7 @@ PreferencesGeneralTab::PreferencesGeneralTab() autoscroll_method_->addItem(tr("None"), AutoScroll::kNone); autoscroll_method_->addItem(tr("Page Scrolling"), AutoScroll::kPage); autoscroll_method_->addItem(tr("Smooth Scrolling"), AutoScroll::kSmooth); - autoscroll_method_->setCurrentIndex(Config::Current()["Autoscroll"].toInt()); + autoscroll_method_->setCurrentIndex(OLIVE_CONFIG("Autoscroll").toInt()); timeline_layout->addWidget(autoscroll_method_, row, 1); row++; @@ -94,7 +94,7 @@ PreferencesGeneralTab::PreferencesGeneralTab() timeline_layout->addWidget(new QLabel(tr("Rectified Waveforms:")), row, 0); rectified_waveforms_ = new QCheckBox(); - rectified_waveforms_->setChecked(Config::Current()["RectifiedWaveforms"].toBool()); + rectified_waveforms_->setChecked(OLIVE_CONFIG("RectifiedWaveforms").toBool()); timeline_layout->addWidget(rectified_waveforms_, row, 1); row++; @@ -105,7 +105,7 @@ PreferencesGeneralTab::PreferencesGeneralTab() default_still_length_->SetMinimum(rational(100, 1000)); default_still_length_->SetTimebase(rational(100, 1000)); default_still_length_->SetFormat(tr("%1 seconds")); - default_still_length_->SetValue(Config::Current()["DefaultStillLength"].value()); + default_still_length_->SetValue(OLIVE_CONFIG("DefaultStillLength").value()); timeline_layout->addWidget(default_still_length_); } @@ -119,7 +119,7 @@ PreferencesGeneralTab::PreferencesGeneralTab() autorecovery_layout->addWidget(new QLabel(tr("Enable Auto-Recovery:")), row, 0); autorecovery_enabled_ = new QCheckBox(); - autorecovery_enabled_->setChecked(Config::Current()[QStringLiteral("AutorecoveryEnabled")].toBool()); + autorecovery_enabled_->setChecked(OLIVE_CONFIG("AutorecoveryEnabled").toBool()); autorecovery_layout->addWidget(autorecovery_enabled_, row, 1); row++; @@ -130,7 +130,7 @@ PreferencesGeneralTab::PreferencesGeneralTab() autorecovery_interval_->SetMinimum(1); autorecovery_interval_->SetMaximum(60); autorecovery_interval_->SetFormat(QT_TRANSLATE_N_NOOP("olive::SliderBase", "%n minute(s)"), true); - autorecovery_interval_->SetValue(Config::Current()[QStringLiteral("AutorecoveryInterval")].toLongLong()); + autorecovery_interval_->SetValue(OLIVE_CONFIG("AutorecoveryInterval").toLongLong()); autorecovery_layout->addWidget(autorecovery_interval_, row, 1); row++; @@ -140,7 +140,7 @@ PreferencesGeneralTab::PreferencesGeneralTab() autorecovery_maximum_ = new IntegerSlider(); autorecovery_maximum_->SetMinimum(1); autorecovery_maximum_->SetMaximum(1000); - autorecovery_maximum_->SetValue(Config::Current()[QStringLiteral("AutorecoveryMaximum")].toLongLong()); + autorecovery_maximum_->SetValue(OLIVE_CONFIG("AutorecoveryMaximum").toLongLong()); autorecovery_layout->addWidget(autorecovery_maximum_, row, 1); row++; @@ -157,11 +157,11 @@ void PreferencesGeneralTab::Accept(MultiUndoCommand *command) { Q_UNUSED(command) - Config::Current()[QStringLiteral("RectifiedWaveforms")] = rectified_waveforms_->isChecked(); + OLIVE_CONFIG("RectifiedWaveforms") = rectified_waveforms_->isChecked(); - Config::Current()[QStringLiteral("Autoscroll")] = autoscroll_method_->currentData(); + OLIVE_CONFIG("Autoscroll") = autoscroll_method_->currentData(); - Config::Current()[QStringLiteral("DefaultStillLength")] = QVariant::fromValue(default_still_length_->GetValue()); + OLIVE_CONFIG("DefaultStillLength") = QVariant::fromValue(default_still_length_->GetValue()); QString set_language = language_combobox_->currentData().toString(); if (QLocale::system().name() == set_language) { @@ -170,14 +170,14 @@ void PreferencesGeneralTab::Accept(MultiUndoCommand *command) } // If the language has changed, set it now - if (Config::Current()[QStringLiteral("Language")].toString() != set_language) { - Config::Current()[QStringLiteral("Language")] = set_language; + if (OLIVE_CONFIG("Language").toString() != set_language) { + OLIVE_CONFIG("Language") = set_language; Core::instance()->SetLanguage(set_language.isEmpty() ? QLocale::system().name() : set_language); } - Config::Current()[QStringLiteral("AutorecoveryEnabled")] = autorecovery_enabled_->isChecked(); - Config::Current()[QStringLiteral("AutorecoveryInterval")] = QVariant::fromValue(autorecovery_interval_->GetValue()); - Config::Current()[QStringLiteral("AutorecoveryMaximum")] = QVariant::fromValue(autorecovery_maximum_->GetValue()); + OLIVE_CONFIG("AutorecoveryEnabled") = autorecovery_enabled_->isChecked(); + OLIVE_CONFIG("AutorecoveryInterval") = QVariant::fromValue(autorecovery_interval_->GetValue()); + OLIVE_CONFIG("AutorecoveryMaximum") = QVariant::fromValue(autorecovery_maximum_->GetValue()); Core::instance()->SetAutorecoveryInterval(autorecovery_interval_->GetValue()); } diff --git a/app/dialog/sequence/sequence.cpp b/app/dialog/sequence/sequence.cpp index e7daa93e5..4305e1894 100644 --- a/app/dialog/sequence/sequence.cpp +++ b/app/dialog/sequence/sequence.cpp @@ -148,14 +148,14 @@ void SequenceDialog::SetAsDefaultClicked() tr("Are you sure you want to set the current parameters as defaults?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { // Maybe replace with Preset system - Config::Current()[QStringLiteral("DefaultSequenceWidth")] = parameter_tab_->GetSelectedVideoWidth(); - Config::Current()[QStringLiteral("DefaultSequenceHeight")] = parameter_tab_->GetSelectedVideoHeight(); - Config::Current()[QStringLiteral("DefaultSequencePixelAspect")] = QVariant::fromValue(parameter_tab_->GetSelectedVideoPixelAspect()); - Config::Current()[QStringLiteral("DefaultSequenceFrameRate")] = QVariant::fromValue(parameter_tab_->GetSelectedVideoFrameRate().flipped()); - Config::Current()[QStringLiteral("DefaultSequenceInterlacing")] = parameter_tab_->GetSelectedVideoInterlacingMode(); - Config::Current()[QStringLiteral("DefaultSequenceAudioFrequency")] = parameter_tab_->GetSelectedAudioSampleRate(); - Config::Current()[QStringLiteral("DefaultSequenceAudioLayout")] = QVariant::fromValue(parameter_tab_->GetSelectedAudioChannelLayout()); - Config::Current()[QStringLiteral("DefaultSequenceAutoCache")] = QVariant::fromValue(parameter_tab_->GetSelectedPreviewAutoCache()); + OLIVE_CONFIG("DefaultSequenceWidth") = parameter_tab_->GetSelectedVideoWidth(); + OLIVE_CONFIG("DefaultSequenceHeight") = parameter_tab_->GetSelectedVideoHeight(); + OLIVE_CONFIG("DefaultSequencePixelAspect") = QVariant::fromValue(parameter_tab_->GetSelectedVideoPixelAspect()); + OLIVE_CONFIG("DefaultSequenceFrameRate") = QVariant::fromValue(parameter_tab_->GetSelectedVideoFrameRate().flipped()); + OLIVE_CONFIG("DefaultSequenceInterlacing") = parameter_tab_->GetSelectedVideoInterlacingMode(); + OLIVE_CONFIG("DefaultSequenceAudioFrequency") = parameter_tab_->GetSelectedAudioSampleRate(); + OLIVE_CONFIG("DefaultSequenceAudioLayout") = QVariant::fromValue(parameter_tab_->GetSelectedAudioChannelLayout()); + OLIVE_CONFIG("DefaultSequenceAutoCache") = QVariant::fromValue(parameter_tab_->GetSelectedPreviewAutoCache()); } } diff --git a/app/dialog/sequence/sequencedialogpresettab.cpp b/app/dialog/sequence/sequencedialogpresettab.cpp index c3e471b9c..74c5a0595 100644 --- a/app/dialog/sequence/sequencedialogpresettab.cpp +++ b/app/dialog/sequence/sequencedialogpresettab.cpp @@ -100,8 +100,8 @@ QTreeWidgetItem* SequenceDialogPresetTab::CreateFolder(const QString &name) QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &name, int width, int height, int divider) { - const VideoParams::Format default_format = static_cast(Config::Current()["OfflinePixelFormat"].toInt()); - const bool default_autocache = Config::Current()[QStringLiteral("DefaultSequenceAutoCache")].toBool(); + const VideoParams::Format default_format = static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()); + const bool default_autocache = OLIVE_CONFIG("DefaultSequenceAutoCache").toBool(); QTreeWidgetItem* parent = CreateFolder(name); AddStandardItem(parent, std::make_shared(tr("%1 23.976 FPS").arg(name), width, @@ -163,8 +163,8 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder(const QString &name, int width, int height, const rational& frame_rate, const rational &standard_par, const rational &wide_par, int divider) { - const VideoParams::Format default_format = static_cast(Config::Current()["OfflinePixelFormat"].toInt()); - const bool default_autocache = Config::Current()[QStringLiteral("DefaultSequenceAutoCache")].toBool(); + const VideoParams::Format default_format = static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()); + const bool default_autocache = OLIVE_CONFIG("DefaultSequenceAutoCache").toBool(); QTreeWidgetItem* parent = CreateFolder(name); preset_tree_->addTopLevelItem(parent); AddStandardItem(parent, std::make_shared(tr("%1 Standard").arg(name), diff --git a/app/node/audio/pan/pan.cpp b/app/node/audio/pan/pan.cpp index 482bf075f..5951ef921 100644 --- a/app/node/audio/pan/pan.cpp +++ b/app/node/audio/pan/pan.cpp @@ -42,11 +42,6 @@ PanNode::PanNode() SetEffectInput(kSamplesInput); } -Node *PanNode::copy() const -{ - return new PanNode(); -} - QString PanNode::Name() const { return tr("Pan"); @@ -72,45 +67,48 @@ void PanNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV Q_UNUSED(globals) // Create a sample job - SampleJob job(kSamplesInput, value); - - if (job.HasSamples()) { - bool push_job = false; + SampleBuffer samples = value[kSamplesInput].toSamples(); + if (samples.is_allocated()) { + bool pushed_job = false; // This node is only compatible with stereo audio - if (job.samples()->audio_params().channel_count() == 2) { + if (samples.audio_params().channel_count() == 2) { // If the input is static, we can just do it now which will be faster if (IsInputStatic(kPanningInput)) { - float pan_volume = job.GetValue(kPanningInput).data().toFloat(); + float pan_volume = value[kPanningInput].toDouble(); if (!qIsNull(pan_volume)) { if (pan_volume > 0) { - job.samples()->transform_volume_for_channel(0, 1.0f - pan_volume); + samples.transform_volume_for_channel(0, 1.0f - pan_volume); } else { - job.samples()->transform_volume_for_channel(1, 1.0f + pan_volume); + samples.transform_volume_for_channel(1, 1.0f + pan_volume); } } } else { // Requires job - push_job = true; + + pushed_job = true; + table->Push(NodeValue::kSamples, SampleJob(kSamplesInput, value), this); } } - table->Push(NodeValue::kSamples, push_job ? QVariant::fromValue(job) : QVariant::fromValue(job.samples()), this); + if (!pushed_job) { + table->Push(value[kSamplesInput]); + } } } -void PanNode::ProcessSamples(const NodeValueRow &values, const SampleBufferPtr input, SampleBufferPtr output, int index) const +void PanNode::ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const { - float pan_val = values[kPanningInput].data().toFloat(); + float pan_val = values[kPanningInput].toDouble(); - for (int i=0;iaudio_params().channel_count();i++) { - output->data(i)[index] = input->data(i)[index]; + for (int i=0;i 0) { - output->data(0)[index] *= (1.0F - pan_val); + output.data(0)[index] *= (1.0F - pan_val); } else if (pan_val < 0) { - output->data(1)[index] *= (1.0F - qAbs(pan_val)); + output.data(1)[index] *= (1.0F - qAbs(pan_val)); } } diff --git a/app/node/audio/pan/pan.h b/app/node/audio/pan/pan.h index 6f8fd9a89..8d915a6fa 100644 --- a/app/node/audio/pan/pan.h +++ b/app/node/audio/pan/pan.h @@ -31,9 +31,7 @@ class PanNode : public Node public: PanNode(); - NODE_DEFAULT_DESTRUCTOR(PanNode) - - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(PanNode) virtual QString Name() const override; virtual QString id() const override; @@ -42,7 +40,7 @@ public: virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - virtual void ProcessSamples(const NodeValueRow &values, const SampleBufferPtr input, SampleBufferPtr output, int index) const override; + virtual void ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const override; virtual void Retranslate() override; diff --git a/app/node/audio/volume/volume.cpp b/app/node/audio/volume/volume.cpp index 79feef607..a12a939e1 100644 --- a/app/node/audio/volume/volume.cpp +++ b/app/node/audio/volume/volume.cpp @@ -41,11 +41,6 @@ VolumeNode::VolumeNode() SetEffectInput(kSamplesInput); } -Node *VolumeNode::copy() const -{ - return new VolumeNode(); -} - QString VolumeNode::Name() const { return tr("Volume"); @@ -68,17 +63,30 @@ QString VolumeNode::Description() const void VolumeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - return ValueInternal(kOpMultiply, - kPairSampleNumber, - kSamplesInput, - value[kSamplesInput], - kVolumeInput, - value[kVolumeInput], - globals, - table); + Q_UNUSED(globals) + + // Create a sample job + SampleBuffer buffer = value[kSamplesInput].toSamples(); + + if (buffer.is_allocated()) { + // If the input is static, we can just do it now which will be faster + if (IsInputStatic(kVolumeInput)) { + auto volume = value[kVolumeInput].toDouble(); + + if (!qFuzzyCompare(volume, 1.0)) { + buffer.transform_volume(volume); + } + + table->Push(NodeValue::kSamples, QVariant::fromValue(buffer), this); + } else { + // Requires job + SampleJob job(kSamplesInput, value); + table->Push(NodeValue::kSamples, QVariant::fromValue(job), this); + } + } } -void VolumeNode::ProcessSamples(const NodeValueRow &values, const SampleBufferPtr input, SampleBufferPtr output, int index) const +void VolumeNode::ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const { return ProcessSamplesInternal(values, kOpMultiply, kSamplesInput, kVolumeInput, input, output, index); } diff --git a/app/node/audio/volume/volume.h b/app/node/audio/volume/volume.h index a9fcc10d9..73f0c43df 100644 --- a/app/node/audio/volume/volume.h +++ b/app/node/audio/volume/volume.h @@ -31,9 +31,7 @@ class VolumeNode : public MathNodeBase public: VolumeNode(); - NODE_DEFAULT_DESTRUCTOR(VolumeNode) - - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(VolumeNode) virtual QString Name() const override; virtual QString id() const override; @@ -42,7 +40,7 @@ public: virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - virtual void ProcessSamples(const NodeValueRow &values, const SampleBufferPtr input, SampleBufferPtr output, int index) const override; + virtual void ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const override; virtual void Retranslate() override; diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index 68ee09863..c505a80a2 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -45,6 +45,8 @@ Block::Block() : SetInputProperty(kLengthInput, QStringLiteral("viewlock"), true); IgnoreHashingFrom(kLengthInput); + SetInputFlags(kEnabledInput, InputFlags(GetInputFlags(kEnabledInput) | kInputFlagNotConnectable | kInputFlagNotKeyframable)); + SetFlags(kDontShowInParamView); } diff --git a/app/node/block/block.h b/app/node/block/block.h index a555cb065..33ddc00dd 100644 --- a/app/node/block/block.h +++ b/app/node/block/block.h @@ -37,8 +37,6 @@ class Block : public Node public: Block(); - NODE_DEFAULT_DESTRUCTOR(Block) - virtual QVector Category() const override; const rational& in() const diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index a7ce383b2..c48dee2a4 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -61,11 +61,6 @@ ClipBlock::ClipBlock() : SetEffectInput(kBufferIn); } -Node *ClipBlock::copy() const -{ - return new ClipBlock(); -} - QString ClipBlock::Name() const { if (track()) { @@ -289,6 +284,7 @@ void ClipBlock::Retranslate() SetInputName(kMediaInInput, tr("Media In")); SetInputName(kSpeedInput, tr("Speed")); SetInputName(kReverseInput, tr("Reverse")); + SetInputName(kMaintainAudioPitchInput, tr("Maintain Audio Pitch")); } void ClipBlock::Hash(QCryptographicHash &hash, const NodeGlobals &globals, const VideoParams &video_params) const diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index 300f3408f..943c6ed15 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -37,9 +37,7 @@ class ClipBlock : public Block public: ClipBlock(); - NODE_DEFAULT_DESTRUCTOR(ClipBlock) - - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(ClipBlock) virtual QString Name() const override; virtual QString id() const override; diff --git a/app/node/block/gap/gap.cpp b/app/node/block/gap/gap.cpp index d5ea6915c..60a066153 100644 --- a/app/node/block/gap/gap.cpp +++ b/app/node/block/gap/gap.cpp @@ -26,11 +26,6 @@ GapBlock::GapBlock() { } -Node *GapBlock::copy() const -{ - return new GapBlock(); -} - QString GapBlock::Name() const { return tr("Gap"); diff --git a/app/node/block/gap/gap.h b/app/node/block/gap/gap.h index c048e229e..5dfc01eb1 100644 --- a/app/node/block/gap/gap.h +++ b/app/node/block/gap/gap.h @@ -34,9 +34,7 @@ class GapBlock : public Block public: GapBlock(); - NODE_DEFAULT_DESTRUCTOR(GapBlock) - - virtual Node * copy() const override; + NODE_DEFAULT_FUNCTIONS(GapBlock) virtual QString Name() const override; virtual QString id() const override; diff --git a/app/node/block/subtitle/subtitle.cpp b/app/node/block/subtitle/subtitle.cpp index ef42559cd..6231fc7d2 100644 --- a/app/node/block/subtitle/subtitle.cpp +++ b/app/node/block/subtitle/subtitle.cpp @@ -29,11 +29,16 @@ const QString SubtitleBlock::kTextIn = QStringLiteral("text_in"); SubtitleBlock::SubtitleBlock() { AddInput(kTextIn, NodeValue::kText, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); -} -Node *SubtitleBlock::copy() const -{ - return new SubtitleBlock(); + SetInputFlags(kBufferIn, InputFlags(GetInputFlags(kBufferIn) | kInputFlagHidden)); + SetInputFlags(kLengthInput, InputFlags(GetInputFlags(kLengthInput) | kInputFlagHidden)); + SetInputFlags(kMediaInInput, InputFlags(GetInputFlags(kMediaInInput) | kInputFlagHidden)); + SetInputFlags(kSpeedInput, InputFlags(GetInputFlags(kSpeedInput) | kInputFlagHidden)); + SetInputFlags(kReverseInput, InputFlags(GetInputFlags(kReverseInput) | kInputFlagHidden)); + SetInputFlags(kMaintainAudioPitchInput, InputFlags(GetInputFlags(kMaintainAudioPitchInput) | kInputFlagHidden)); + + // Undo block flag that hides in param view + SetFlags(GetFlags() & ~kDontShowInParamView); } QString SubtitleBlock::Name() const diff --git a/app/node/block/subtitle/subtitle.h b/app/node/block/subtitle/subtitle.h index c88a2bfc1..ed51c37d3 100644 --- a/app/node/block/subtitle/subtitle.h +++ b/app/node/block/subtitle/subtitle.h @@ -31,9 +31,7 @@ class SubtitleBlock : public ClipBlock public: SubtitleBlock(); - NODE_DEFAULT_DESTRUCTOR(SubtitleBlock) - - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(SubtitleBlock) virtual QString Name() const override; virtual QString id() const override; diff --git a/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp b/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp index 8f9d33e62..bba754160 100644 --- a/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp +++ b/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp @@ -24,12 +24,6 @@ namespace olive { CrossDissolveTransition::CrossDissolveTransition() { - -} - -Node *CrossDissolveTransition::copy() const -{ - return new CrossDissolveTransition(); } QString CrossDissolveTransition::Name() const @@ -52,9 +46,9 @@ QString CrossDissolveTransition::Description() const return tr("Smoothly transition between two clips."); } -ShaderCode CrossDissolveTransition::GetShaderCode(const QString &shader_id) const +ShaderCode CrossDissolveTransition::GetShaderCode(const ShaderRequest &request) const { - Q_UNUSED(shader_id) + Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/crossdissolve.frag"), QString()); } @@ -66,27 +60,27 @@ void CrossDissolveTransition::ShaderJobEvent(const NodeValueRow &value, ShaderJo job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); } -void CrossDissolveTransition::SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const +void CrossDissolveTransition::SampleJobEvent(const SampleBuffer &from_samples, const SampleBuffer &to_samples, SampleBuffer &out_samples, double time_in) const { - for (int i=0; isample_count(); i++) { - double this_sample_time = out_samples->audio_params().samples_to_time(i).toDouble() + time_in; + for (int i=0; iaudio_params().channel_count(); j++) { - out_samples->data(j)[i] = 0; + for (int j=0; jsample_count()) { - out_samples->data(j)[i] += from_samples->data(j)[i] * TransformCurve(1.0 - progress); + if (from_samples.is_allocated()) { + if (i < from_samples.sample_count()) { + out_samples.data(j)[i] += from_samples.data(j)[i] * TransformCurve(1.0 - progress); } } - if (to_samples) { + if (to_samples.is_allocated()) { // Offset input samples from the end - int in_index = i - (out_samples->sample_count() - to_samples->sample_count()); + 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); + out_samples.data(j)[i] += to_samples.data(j)[in_index] * TransformCurve(progress); } } } diff --git a/app/node/block/transition/crossdissolve/crossdissolvetransition.h b/app/node/block/transition/crossdissolve/crossdissolvetransition.h index aca4fd039..6cd7c8960 100644 --- a/app/node/block/transition/crossdissolve/crossdissolvetransition.h +++ b/app/node/block/transition/crossdissolve/crossdissolvetransition.h @@ -31,9 +31,7 @@ class CrossDissolveTransition : public TransitionBlock public: CrossDissolveTransition(); - NODE_DEFAULT_DESTRUCTOR(CrossDissolveTransition) - - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(CrossDissolveTransition) virtual QString Name() const override; virtual QString id() const override; @@ -42,12 +40,12 @@ public: //virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const QString& shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; protected: virtual void ShaderJobEvent(const NodeValueRow &value, ShaderJob& job) const override; - virtual void SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const override; + virtual void SampleJobEvent(const SampleBuffer &from_samples, const SampleBuffer &to_samples, SampleBuffer &out_samples, double time_in) const override; }; diff --git a/app/node/block/transition/diptocolor/diptocolortransition.cpp b/app/node/block/transition/diptocolor/diptocolortransition.cpp index 1c9691e13..8700c3628 100644 --- a/app/node/block/transition/diptocolor/diptocolortransition.cpp +++ b/app/node/block/transition/diptocolor/diptocolortransition.cpp @@ -29,11 +29,6 @@ DipToColorTransition::DipToColorTransition() AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(0, 0, 0))); } -Node *DipToColorTransition::copy() const -{ - return new DipToColorTransition(); -} - QString DipToColorTransition::Name() const { return tr("Dip To Color"); @@ -54,16 +49,16 @@ QString DipToColorTransition::Description() const return tr("Transition between clips by dipping to a color."); } -ShaderCode DipToColorTransition::GetShaderCode(const QString &shader_id) const +ShaderCode DipToColorTransition::GetShaderCode(const ShaderRequest &request) const { - Q_UNUSED(shader_id) + Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/diptoblack.frag"), QString()); } void DipToColorTransition::ShaderJobEvent(const NodeValueRow &value, ShaderJob &job) const { - job.InsertValue(kColorInput, value); + job.Insert(kColorInput, value); } } diff --git a/app/node/block/transition/diptocolor/diptocolortransition.h b/app/node/block/transition/diptocolor/diptocolortransition.h index 00ff56550..b32918555 100644 --- a/app/node/block/transition/diptocolor/diptocolortransition.h +++ b/app/node/block/transition/diptocolor/diptocolortransition.h @@ -31,16 +31,14 @@ class DipToColorTransition : public TransitionBlock public: DipToColorTransition(); - NODE_DEFAULT_DESTRUCTOR(DipToColorTransition) - - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(DipToColorTransition) virtual QString Name() const override; virtual QString id() const override; virtual QVector Category() const override; virtual QString Description() const override; - virtual ShaderCode GetShaderCode(const QString& shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; static const QString kColorInput; diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index 97f97a31b..38b956ad4 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -162,15 +162,15 @@ double TransitionBlock::GetInternalTransitionTime(const double &time) const 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"), + job->Insert(QStringLiteral("ove_tprog_all"), NodeValue(NodeValue::kFloat, GetTotalProgress(time), this)); // Provides progress of out section from 1.0 (start) - 0.0 (end) - job->InsertValue(QStringLiteral("ove_tprog_out"), + job->Insert(QStringLiteral("ove_tprog_out"), NodeValue(NodeValue::kFloat, GetOutProgress(time), this)); // Provides progress of in section from 0.0 (start) - 1.0 (end) - job->InsertValue(QStringLiteral("ove_tprog_in"), + job->Insert(QStringLiteral("ove_tprog_in"), NodeValue(NodeValue::kFloat, GetInProgress(time), this)); } @@ -188,14 +188,14 @@ void TransitionBlock::Value(const NodeValueRow &value, const NodeGlobals &global ShaderJob job; if (out_buffer.type() != NodeValue::kNone) { - job.InsertValue(kOutBlockInput, out_buffer); + job.Insert(kOutBlockInput, out_buffer); } if (in_buffer.type() != NodeValue::kNone) { - job.InsertValue(kInBlockInput, in_buffer); + job.Insert(kInBlockInput, in_buffer); } - job.InsertValue(kCurveInput, value); + job.Insert(kCurveInput, value); double time = globals.time().in().toDouble(); InsertTransitionTimes(&job, time); @@ -206,25 +206,22 @@ void TransitionBlock::Value(const NodeValueRow &value, const NodeGlobals &global push_job = QVariant::fromValue(job); } else if (data_type == NodeValue::kSamples) { // This must be an audio transition - SampleBufferPtr from_samples = out_buffer.data().value(); - SampleBufferPtr to_samples = in_buffer.data().value(); + SampleBuffer from_samples = out_buffer.toSamples(); + SampleBuffer to_samples = in_buffer.toSamples(); - if (from_samples || to_samples) { + if (from_samples.is_allocated() || to_samples.is_allocated()) { double time_in = globals.time().in().toDouble(); double time_out = globals.time().out().toDouble(); - const AudioParams& params = (from_samples) ? from_samples->audio_params() : to_samples->audio_params(); + const AudioParams& params = (from_samples.is_allocated()) ? from_samples.audio_params() : to_samples.audio_params(); - SampleBufferPtr out_samples; + SampleBuffer out_samples; if (params.is_valid()) { int nb_samples = params.time_to_samples(time_out - time_in); - out_samples = SampleBuffer::CreateAllocated(params, nb_samples); + out_samples = SampleBuffer(params, nb_samples); SampleJobEvent(from_samples, to_samples, out_samples, time_in); - } else { - // Create dummy sample buffer - out_samples = SampleBuffer::Create(); } job_type = NodeValue::kSamples; @@ -251,20 +248,6 @@ void TransitionBlock::InvalidateCache(const TimeRange &range, const QString &fro super::InvalidateCache(r, from, element, options); } -void TransitionBlock::ShaderJobEvent(const NodeValueRow &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(GetStandardValue(kCurveInput).toInt())) { diff --git a/app/node/block/transition/transition.h b/app/node/block/transition/transition.h index 93577fcbc..0e3f740a6 100644 --- a/app/node/block/transition/transition.h +++ b/app/node/block/transition/transition.h @@ -33,8 +33,6 @@ class TransitionBlock : public Block public: TransitionBlock(); - NODE_DEFAULT_DESTRUCTOR(TransitionBlock) - virtual void Retranslate() override; rational in_offset() const; @@ -75,9 +73,9 @@ public: static const QString kCenterInput; protected: - virtual void ShaderJobEvent(const NodeValueRow &value, ShaderJob& job) const; + virtual void ShaderJobEvent(const NodeValueRow &value, ShaderJob& job) const {} - virtual void SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const; + virtual void SampleJobEvent(const SampleBuffer &from_samples, const SampleBuffer &to_samples, SampleBuffer &out_samples, double time_in) const {} double TransformCurve(double linear) const; diff --git a/app/node/color/CMakeLists.txt b/app/node/color/CMakeLists.txt index afaa401ff..046c499da 100644 --- a/app/node/color/CMakeLists.txt +++ b/app/node/color/CMakeLists.txt @@ -15,6 +15,9 @@ # along with this program. If not, see . add_subdirectory(colormanager) +add_subdirectory(displaytransform) +add_subdirectory(ociobase) +add_subdirectory(ociogradingtransformlinear) set(OLIVE_SOURCES ${OLIVE_SOURCES} diff --git a/app/node/color/colormanager/colormanager.cpp b/app/node/color/colormanager/colormanager.cpp index 895a522b4..6164592b6 100644 --- a/app/node/color/colormanager/colormanager.cpp +++ b/app/node/color/colormanager/colormanager.cpp @@ -273,6 +273,7 @@ void ColorManager::InputValueChangedEvent(const QString &input, int element) try { SetConfig(OCIO::Config::CreateFromFile(GetConfigFilename().toUtf8())); + emit ConfigChanged(); } catch (OCIO::Exception&) {} } diff --git a/app/node/color/colormanager/colormanager.h b/app/node/color/colormanager/colormanager.h index 2dccc7270..a26d3b424 100644 --- a/app/node/color/colormanager/colormanager.h +++ b/app/node/color/colormanager/colormanager.h @@ -38,6 +38,8 @@ class ColorManager : public Node public: ColorManager(); + NODE_DEFAULT_FUNCTIONS(ColorManager) + virtual QString Name() const override { return tr("Color Manager"); @@ -58,11 +60,6 @@ public: return tr("Color management configuration for project."); } - virtual Node* copy() const override - { - return new ColorManager(); - } - OCIO::ConstConfigRcPtr GetConfig() const; static OCIO::ConstConfigRcPtr CreateConfigFromFile(const QString& filename); @@ -124,6 +121,9 @@ public: virtual void Retranslate() override; +signals: + void ConfigChanged(); + protected: virtual void InputValueChangedEvent(const QString &input, int element) override; diff --git a/app/node/color/displaytransform/CMakeLists.txt b/app/node/color/displaytransform/CMakeLists.txt new file mode 100644 index 000000000..4bede1600 --- /dev/null +++ b/app/node/color/displaytransform/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2021 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/color/displaytransform/displaytransform.cpp + node/color/displaytransform/displaytransform.h + PARENT_SCOPE +) diff --git a/app/node/color/displaytransform/displaytransform.cpp b/app/node/color/displaytransform/displaytransform.cpp new file mode 100644 index 000000000..5152d6614 --- /dev/null +++ b/app/node/color/displaytransform/displaytransform.cpp @@ -0,0 +1,144 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "displaytransform.h" + +#include "node/color/colormanager/colormanager.h" + +namespace olive { + +const QString DisplayTransformNode::kDisplayInput = QStringLiteral("display_in"); +const QString DisplayTransformNode::kViewInput = QStringLiteral("view_in"); +const QString DisplayTransformNode::kDirectionInput = QStringLiteral("dir_in"); + +#define super OCIOBaseNode + +DisplayTransformNode::DisplayTransformNode() +{ + AddInput(kDisplayInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable)); + + AddInput(kViewInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable)); + + AddInput(kDirectionInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable)); +} + +QString DisplayTransformNode::Name() const +{ + return tr("Display Transform"); +} + +QString DisplayTransformNode::id() const +{ + return QStringLiteral("org.olivevideoeditor.Olive.displaytransform"); +} + +QVector DisplayTransformNode::Category() const +{ + return {kCategoryColor}; +} + +QString DisplayTransformNode::Description() const +{ + return tr("Converts an image to or from a display color space."); +} + +void DisplayTransformNode::Retranslate() +{ + super::Retranslate(); + + SetInputName(kTextureInput, tr("Input")); + SetInputName(kDisplayInput, tr("Display")); + SetInputName(kViewInput, tr("View")); + SetInputName(kDirectionInput, tr("Direction")); + SetComboBoxStrings(kDirectionInput, {tr("Forward"), tr("Inverse")}); +} + +void DisplayTransformNode::InputValueChangedEvent(const QString &input, int element) +{ + Q_UNUSED(element); + if (input == kDisplayInput || input == kDirectionInput || input == kViewInput) { + if (input == kDisplayInput) { + UpdateViews(); + } + GenerateProcessor(); + } +} + +QString DisplayTransformNode::GetDisplay() const +{ + if (manager()) { + int index = GetStandardValue(kDisplayInput).toInt(); + if (index < manager()->ListAvailableDisplays().size()) { + return manager()->ListAvailableDisplays().at(index); + } + } + return QString(); +} + +QString DisplayTransformNode::GetView() const +{ + if (manager()) { + QString display = GetDisplay(); + if (!display.isEmpty()) { + int index = GetStandardValue(kViewInput).toInt(); + QStringList views = manager()->ListAvailableViews(display); + if (index < views.size()) { + return views.at(index); + } + } + } + return QString(); +} + +ColorProcessor::Direction DisplayTransformNode::GetDirection() const +{ + return static_cast(GetStandardValue(kDirectionInput).toInt());; +} + +void DisplayTransformNode::UpdateDisplays() +{ + if (manager()) { + SetComboBoxStrings(kDisplayInput, manager()->ListAvailableDisplays()); + } +} + +void DisplayTransformNode::UpdateViews() +{ + if (manager()) { + SetComboBoxStrings(kViewInput, manager()->ListAvailableViews(GetDisplay())); + } +} + +void DisplayTransformNode::ConfigChanged() +{ + UpdateDisplays(); + UpdateViews(); + GenerateProcessor(); +} + +void DisplayTransformNode::GenerateProcessor() +{ + if (manager()) { + ColorTransform transform(GetDisplay(), GetView(), QString()); + set_processor(ColorProcessor::Create(manager(), manager()->GetReferenceColorSpace(), transform, GetDirection())); + } +} + +} diff --git a/app/node/color/displaytransform/displaytransform.h b/app/node/color/displaytransform/displaytransform.h new file mode 100644 index 000000000..ac6be6bef --- /dev/null +++ b/app/node/color/displaytransform/displaytransform.h @@ -0,0 +1,67 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef DISPLAYTRANSFORMNODE_H +#define DISPLAYTRANSFORMNODE_H + +#include "node/color/ociobase/ociobase.h" +#include "render/colorprocessor.h" + +namespace olive { + +class DisplayTransformNode : public OCIOBaseNode +{ + Q_OBJECT + public: + DisplayTransformNode(); + + NODE_DEFAULT_FUNCTIONS(DisplayTransformNode) + + virtual QString Name() const override; + virtual QString id() const override; + virtual QVector Category() const override; + virtual QString Description() const override; + + virtual void Retranslate() override; + virtual void InputValueChangedEvent(const QString &input, int element) override; + + QString GetDisplay() const; + QString GetView() const; + ColorProcessor::Direction GetDirection() const; + + static const QString kDisplayInput; + static const QString kViewInput; + static const QString kDirectionInput; + +protected slots: + virtual void ConfigChanged() override; + +private: + void GenerateProcessor(); + + void UpdateDisplays(); + + void UpdateViews(); + +}; + +} // olive + +#endif // DISPLAYTRANSFORMNODE_H diff --git a/app/node/color/ociobase/CMakeLists.txt b/app/node/color/ociobase/CMakeLists.txt new file mode 100644 index 000000000..fa00411a4 --- /dev/null +++ b/app/node/color/ociobase/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2021 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/color/ociobase/ociobase.cpp + node/color/ociobase/ociobase.h + PARENT_SCOPE +) diff --git a/app/node/color/ociobase/ociobase.cpp b/app/node/color/ociobase/ociobase.cpp new file mode 100644 index 000000000..7ce711050 --- /dev/null +++ b/app/node/color/ociobase/ociobase.cpp @@ -0,0 +1,69 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "ociobase.h" + +#include "node/color/colormanager/colormanager.h" +#include "node/project/project.h" + +namespace olive { + +const QString OCIOBaseNode::kTextureInput = QStringLiteral("tex_in"); + +OCIOBaseNode::OCIOBaseNode() : + manager_(nullptr), + processor_(nullptr) +{ + AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + + SetEffectInput(kTextureInput); + + connect(this, &Node::AddedToGraph, this, &OCIOBaseNode::ParentChanged); + + SetFlags(kVideoEffect); +} + +void OCIOBaseNode::ParentChanged(NodeGraph *graph) +{ + if (manager_) { + disconnect(manager_, &ColorManager::ConfigChanged, this, &OCIOBaseNode::ConfigChanged); + manager_ = nullptr; + } + + if (Project *p = dynamic_cast(graph)) { + manager_ = p->color_manager(); + connect(manager_, &ColorManager::ConfigChanged, this, &OCIOBaseNode::ConfigChanged); + ConfigChanged(); + } +} + +void OCIOBaseNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + if (value[kTextureInput].toTexture() && processor_) { + ColorTransformJob job; + + job.SetColorProcessor(processor_); + job.SetInputTexture(value[kTextureInput].toTexture()); + + table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + } +} + +} diff --git a/app/audio/planarprocessor.h b/app/node/color/ociobase/ociobase.h similarity index 51% rename from app/audio/planarprocessor.h rename to app/node/color/ociobase/ociobase.h index ad5167a95..a0b6fc0f2 100644 --- a/app/audio/planarprocessor.h +++ b/app/node/color/ociobase/ociobase.h @@ -18,45 +18,43 @@ ***/ -#ifndef PLANARPROCESSOR_H -#define PLANARPROCESSOR_H +#ifndef OCIOBASENODE_H +#define OCIOBASENODE_H -extern "C" { -#include -} - -#include "codec/samplebuffer.h" -#include "render/audioparams.h" +#include "node/node.h" +#include "render/job/colortransformjob.h" namespace olive { -class PlanarProcessor +class OCIOBaseNode : public Node { + Q_OBJECT public: - PlanarProcessor(); + OCIOBaseNode(); - ~PlanarProcessor(); + virtual void Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const override; - DISABLE_COPY_MOVE(PlanarProcessor) + static const QString kTextureInput; - bool Open(const AudioParams ¶ms); +protected slots: + virtual void ConfigChanged() = 0; - SampleBufferPtr Convert(const QByteArray &packed); +protected: + ColorManager *manager() const { return manager_; } - void Close(); - - bool IsOpen() const - { - return swr_ctx_; - } + ColorProcessorPtr processor() const { return processor_; } + void set_processor(ColorProcessorPtr p) { processor_ = p; } private: - SwrContext *swr_ctx_; + ColorManager *manager_; - AudioParams params_; + ColorProcessorPtr processor_; + +private slots: + void ParentChanged(olive::NodeGraph *graph); }; } -#endif // PLANARPROCESSOR_H +#endif // OCIOBASENODE_H diff --git a/app/node/color/ociogradingtransformlinear/CMakeLists.txt b/app/node/color/ociogradingtransformlinear/CMakeLists.txt new file mode 100644 index 000000000..05a3d4ae9 --- /dev/null +++ b/app/node/color/ociogradingtransformlinear/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2021 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp + node/color/ociogradingtransformlinear/ociogradingtransformlinear.h + PARENT_SCOPE +) diff --git a/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp b/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp new file mode 100644 index 000000000..5aa6ee8dc --- /dev/null +++ b/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.cpp @@ -0,0 +1,218 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "ociogradingtransformlinear.h" + +#include + +#include "common/ocioutils.h" +#include "node/project/project.h" +#include "render/colorprocessor.h" +#include "widget/slider/floatslider.h" + +namespace olive { + +const QString OCIOGradingTransformLinearNode::kContrastInput = QStringLiteral("ocio_grading_primary_contrast"); +const QString OCIOGradingTransformLinearNode::kOffsetInput = QStringLiteral("ocio_grading_primary_offset"); +const QString OCIOGradingTransformLinearNode::kExposureInput = QStringLiteral("ocio_grading_primary_exposure"); +const QString OCIOGradingTransformLinearNode::kSaturationInput = QStringLiteral("ocio_grading_primary_saturation"); +const QString OCIOGradingTransformLinearNode::kPivotInput = QStringLiteral("ocio_grading_primary_pivot"); +const QString OCIOGradingTransformLinearNode::kClampBlackEnableInput = QStringLiteral("clamp_black_enable_in"); +const QString OCIOGradingTransformLinearNode::kClampBlackInput = QStringLiteral("ocio_grading_primary_clampBlack"); +const QString OCIOGradingTransformLinearNode::kClampWhiteEnableInput = QStringLiteral("clamp_white_enable_in"); +const QString OCIOGradingTransformLinearNode::kClampWhiteInput = QStringLiteral("ocio_grading_primary_clampWhite"); + +#define super OCIOBaseNode + +OCIOGradingTransformLinearNode::OCIOGradingTransformLinearNode() +{ + AddInput(kContrastInput, NodeValue::kVec4, QVector4D{1.0, 1.0, 1.0, 1.0}); + // Minimum based on OCIO::GradingPrimary::validate + SetInputProperty(kContrastInput, QStringLiteral("min"), QVector4D{0.01f, 0.01f, 0.01f, 0.01f}); + SetInputProperty(kContrastInput, QStringLiteral("base"), 0.01); + SetVec4InputColors(kContrastInput); + + AddInput(kOffsetInput, NodeValue::kVec4, QVector4D{0.0, 0.0, 0.0, 0.0}); + SetInputProperty(kOffsetInput, QStringLiteral("base"), 0.01); + SetVec4InputColors(kOffsetInput); + + AddInput(kExposureInput, NodeValue::kVec4, QVector4D{0.0, 0.0, 0.0, 0.0}); + SetInputProperty(kExposureInput, QStringLiteral("base"), 0.01); + SetVec4InputColors(kExposureInput); + + AddInput(kSaturationInput, NodeValue::kFloat, 1.0); + SetInputProperty(kSaturationInput, QStringLiteral("view"), FloatSlider::kPercentage); + SetInputProperty(kSaturationInput, QStringLiteral("min"), 0.0); + + AddInput(kPivotInput, NodeValue::kFloat, 0.18); // Default listed in OCIO::GradingPrimary + SetInputProperty(kPivotInput, QStringLiteral("base"), 0.01); + + AddInput(kClampBlackEnableInput, NodeValue::kBoolean, false); + + AddInput(kClampBlackInput, NodeValue::kFloat, 0.0); + SetInputProperty(kClampBlackInput, QStringLiteral("enabled"), GetStandardValue(kClampBlackEnableInput).toBool()); + SetInputProperty(kClampBlackInput, QStringLiteral("base"), 0.01); + + AddInput(kClampWhiteEnableInput, NodeValue::kBoolean, false); + + AddInput(kClampWhiteInput, NodeValue::kFloat, 1.0); + SetInputProperty(kClampWhiteInput, QStringLiteral("enabled"), GetStandardValue(kClampWhiteEnableInput).toBool()); + SetInputProperty(kClampWhiteInput, QStringLiteral("base"), 0.01); + + // FIXME: Temporarily disabled. This will break if "clamp black" is keyframed or connected to + // something and there's currently no solution to remedy that. If there is in the future, + // we can look into re-enabling this. + //SetInputProperty(kClampWhiteInput, QStringLiteral("min"), GetStandardValue(kClampBlackInput).toDouble() + 0.000001); +} + +QString OCIOGradingTransformLinearNode::Name() const +{ + return tr("OCIO Color Grading (Linear)"); +} + +QString OCIOGradingTransformLinearNode::id() const +{ + return QStringLiteral("org.olivevideoeditor.Olive.ociogradingtransformlinear"); +} + +QVector OCIOGradingTransformLinearNode::Category() const +{ + return {kCategoryColor}; +} + +QString OCIOGradingTransformLinearNode::Description() const +{ + return tr("Simple linear color grading using OpenColorIO."); +} + +void OCIOGradingTransformLinearNode::Retranslate() +{ + super::Retranslate(); + + SetInputName(kTextureInput, tr("Input")); + SetInputName(kContrastInput, tr("Contrast")); + SetInputName(kOffsetInput, tr("Offset")); + SetInputName(kExposureInput, tr("Exposure")); + SetInputProperty(kExposureInput, QStringLiteral("tooltip"), tr("Exposure increments in stops.")); + SetInputName(kSaturationInput, tr("Saturation")); + SetInputName(kPivotInput, tr("Pivot")); + SetInputName(kClampBlackEnableInput, tr("Enable Black Clamp")); + SetInputName(kClampBlackInput, tr("Black Clamp")); + SetInputName(kClampWhiteEnableInput, tr("Enable White Clamp")); + SetInputName(kClampWhiteInput, tr("White Clamp")); +} + +void OCIOGradingTransformLinearNode::InputValueChangedEvent(const QString &input, int element) +{ + Q_UNUSED(element); + + if (input == kClampWhiteEnableInput) { + SetInputProperty(kClampWhiteInput, QStringLiteral("enabled"), GetStandardValue(kClampWhiteEnableInput).toBool()); + } else if (input == kClampBlackEnableInput) { + SetInputProperty(kClampBlackInput, QStringLiteral("enabled"), GetStandardValue(kClampBlackEnableInput).toBool()); + } else if (input == kClampBlackInput) { + // Ensure the white clamp is always greater than the black clamp as per OCIO::GradingPrimary::validate + // FIXME: Temporarily disabled. This will break if "clamp black" is keyframed or connected to + // something and there's currently no solution to remedy that. If there is in the future, + // we can look into re-enabling this. + //SetInputProperty(kClampWhiteInput, QStringLiteral("min"), GetStandardValue(kClampBlackInput).toDouble() + 0.000001); + } + + GenerateProcessor(); +} + +void OCIOGradingTransformLinearNode::GenerateProcessor() +{ + if (manager()) { + OCIO::GradingPrimaryTransformRcPtr gp = OCIO::GradingPrimaryTransform::Create(OCIO::GRADING_LIN); + gp->makeDynamic(); + gp->setDirection(OCIO::TransformDirection::TRANSFORM_DIR_FORWARD); + + try { + set_processor(ColorProcessor::Create(manager()->GetConfig()->getProcessor(gp))); + } catch (const OCIO::Exception &e) { + std::cerr << std::endl << e.what() << std::endl; + } + } +} + +void OCIOGradingTransformLinearNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + if (value[kTextureInput].toTexture() && processor()) { + ColorTransformJob job; + + job.SetColorProcessor(processor()); + job.SetInputTexture(value[kTextureInput].toTexture()); + + job.Insert(value); + + const int MASTER_CHANNEL = 0; + const int RED_CHANNEL = 1; + const int GREEN_CHANNEL = 2; + const int BLUE_CHANNEL = 3; + + // Oddly, OCIO uses RGBMs when setting the GradingPrimary on the CPU, but uses vec3s on the GPU. + // Even more oddly, the conversion from RGBM to vec3 does not appear to have a public API. + // Therefore, this code has been duplicated from OCIO here: + // https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/3abbe5b20521169580fcfe3692aca81859859953/src/OpenColorIO/ops/gradingprimary/GradingPrimary.cpp#L157 + QVector4D offset = value[kOffsetInput].toVec4(); + offset[RED_CHANNEL] += offset[MASTER_CHANNEL]; + offset[GREEN_CHANNEL] += offset[MASTER_CHANNEL]; + offset[BLUE_CHANNEL] += offset[MASTER_CHANNEL]; + job.Insert(kOffsetInput, NodeValue(NodeValue::kVec3, QVector3D(offset[RED_CHANNEL], offset[GREEN_CHANNEL], offset[BLUE_CHANNEL]))); + + QVector4D exposure = value[kExposureInput].toVec4(); + exposure[RED_CHANNEL] = std::pow(2.0f, exposure[MASTER_CHANNEL] + exposure[RED_CHANNEL]); + exposure[GREEN_CHANNEL] = std::pow(2.0f, exposure[MASTER_CHANNEL] + exposure[GREEN_CHANNEL]); + exposure[BLUE_CHANNEL] = std::pow(2.0f, exposure[MASTER_CHANNEL] + exposure[BLUE_CHANNEL]); + job.Insert(kExposureInput, NodeValue(NodeValue::kVec3, QVector3D(exposure[RED_CHANNEL], exposure[GREEN_CHANNEL], exposure[BLUE_CHANNEL]))); + + QVector4D contrast = value[kContrastInput].toVec4(); + contrast[RED_CHANNEL] *= contrast[MASTER_CHANNEL]; + contrast[GREEN_CHANNEL] *= contrast[MASTER_CHANNEL]; + contrast[BLUE_CHANNEL] *= contrast[MASTER_CHANNEL]; + job.Insert(kContrastInput, NodeValue(NodeValue::kVec3, QVector3D(contrast[RED_CHANNEL], contrast[GREEN_CHANNEL], contrast[BLUE_CHANNEL]))); + + if (!value[kClampBlackEnableInput].toBool()) { + job.Insert(kClampBlackInput, NodeValue(NodeValue::kFloat, OCIO::GradingPrimary::NoClampBlack())); + } + + if (!value[kClampWhiteEnableInput].toBool()) { + job.Insert(kClampWhiteInput, NodeValue(NodeValue::kFloat, OCIO::GradingPrimary::NoClampWhite())); + } + + table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + } +} + +void OCIOGradingTransformLinearNode::ConfigChanged() +{ + GenerateProcessor(); +} + +void OCIOGradingTransformLinearNode::SetVec4InputColors(const QString &input) +{ + SetInputProperty(input, QStringLiteral("color0"), QColor(192, 192, 192).name()); + SetInputProperty(input, QStringLiteral("color1"), QColor(255, 0, 0).name()); + SetInputProperty(input, QStringLiteral("color2"), QColor(0, 255, 0).name()); + SetInputProperty(input, QStringLiteral("color3"), QColor(0, 0, 255).name()); +} + +} diff --git a/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.h b/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.h new file mode 100644 index 000000000..8e5d59cca --- /dev/null +++ b/app/node/color/ociogradingtransformlinear/ociogradingtransformlinear.h @@ -0,0 +1,68 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef OCIOGRADINGTRANSFORMLINEARNODE_H +#define OCIOGRADINGTRANSFORMLINEARNODE_H + +#include "node/color/ociobase/ociobase.h" +#include "render/colorprocessor.h" + +namespace olive { + +class OCIOGradingTransformLinearNode : public OCIOBaseNode +{ + Q_OBJECT + public: + OCIOGradingTransformLinearNode(); + + NODE_DEFAULT_FUNCTIONS(OCIOGradingTransformLinearNode) + + virtual QString Name() const override; + virtual QString id() const override; + virtual QVector Category() const override; + virtual QString Description() const override; + + virtual void Retranslate() override; + virtual void InputValueChangedEvent(const QString &input, int element) override; + void GenerateProcessor(); + + virtual void Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const override; + + static const QString kContrastInput; + static const QString kOffsetInput; + static const QString kExposureInput; + static const QString kSaturationInput; + static const QString kPivotInput; + static const QString kClampBlackEnableInput; + static const QString kClampBlackInput; + static const QString kClampWhiteEnableInput; + static const QString kClampWhiteInput; + +protected slots: + virtual void ConfigChanged() override; + +private: + void SetVec4InputColors(const QString &input); + +}; + +} // olive + +#endif diff --git a/app/node/distort/CMakeLists.txt b/app/node/distort/CMakeLists.txt index 375232ccc..de3480361 100644 --- a/app/node/distort/CMakeLists.txt +++ b/app/node/distort/CMakeLists.txt @@ -17,6 +17,7 @@ add_subdirectory(cornerpin) add_subdirectory(crop) add_subdirectory(flip) +add_subdirectory(mask) add_subdirectory(transform) set(OLIVE_SOURCES diff --git a/app/node/distort/cornerpin/cornerpindistortnode.cpp b/app/node/distort/cornerpin/cornerpindistortnode.cpp index 57129bf3f..62015df1a 100644 --- a/app/node/distort/cornerpin/cornerpindistortnode.cpp +++ b/app/node/distort/cornerpin/cornerpindistortnode.cpp @@ -70,9 +70,9 @@ void CornerPinDistortNode::Retranslate() void CornerPinDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { ShaderJob job; - job.InsertValue(value); + job.Insert(value); job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); - job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); // Convert slider values to their pixel values and then convert to clip space (-1.0 ... 1.0) for overriding the // vertex coordinates. @@ -94,23 +94,23 @@ void CornerPinDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g job.SetVertexCoordinates(adjusted_vertices); // If no texture do nothing - if (!job.GetValue(kTextureInput).data().isNull()) { + if (job.Get(kTextureInput).toTexture()) { // In the special case that all sliders are in their default position just // push the texture. - if (!(job.GetValue(kTopLeftInput).data().value().isNull() - && job.GetValue(kTopRightInput).data().value().isNull() && - job.GetValue(kBottomRightInput).data().value().isNull() && - job.GetValue(kBottomLeftInput).data().value().isNull())) { + if (!(job.Get(kTopLeftInput).toVec2().isNull() + && job.Get(kTopRightInput).toVec2().isNull() && + job.Get(kBottomRightInput).toVec2().isNull() && + job.Get(kBottomLeftInput).toVec2().isNull())) { table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } else { - table->Push(NodeValue::kTexture, job.GetValue(kTextureInput).data(), this); + table->Push(job.Get(kTextureInput)); } } } -ShaderCode CornerPinDistortNode::GetShaderCode(const QString &shader_id) const +ShaderCode CornerPinDistortNode::GetShaderCode(const ShaderRequest &request) const { - Q_UNUSED(shader_id) + Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/cornerpin.frag")), FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/cornerpin.vert"))); @@ -119,25 +119,24 @@ ShaderCode CornerPinDistortNode::GetShaderCode(const QString &shader_id) const QPointF CornerPinDistortNode::ValueToPixel(int value, const NodeValueRow& row, const QVector2D &resolution) const { Q_ASSERT(value >= 0 && value <= 3); + + QVector2D v; + switch (value) { - case 0: // Top left - return QPointF(row[kTopLeftInput].data().value().x(), - row[kTopLeftInput].data().value().y()); - break; - case 1: // Top right - return QPointF(resolution.x() + row[kTopRightInput].data().value().x(), - row[kTopRightInput].data().value().y()); - break; - case 2: // Bottom right - return QPointF(resolution.x() + row[kBottomRightInput].data().value().x(), - resolution.y() + row[kBottomRightInput].data().value().y()); - break; - case 3: //Bottom left - return QPointF(row[kBottomLeftInput].data().value().x(), - row[kBottomLeftInput].data().value().y() + resolution.y()); - break; - default: // We should never get here - return QPointF(); + case 0: // Top left + v = row[kTopLeftInput].toVec2(); + return QPointF(v.x(), v.y()); + case 1: // Top right + v = row[kTopRightInput].toVec2(); + return QPointF(resolution.x() + v.x(), v.y()); + case 2: // Bottom right + v = row[kBottomRightInput].toVec2(); + return QPointF(resolution.x() + v.x(), resolution.y() + v.y()); + case 3: //Bottom left + v = row[kBottomLeftInput].toVec2(); + return QPointF(v.x(), v.y() + resolution.y()); + default: // We should never get here + return QPointF(); } } diff --git a/app/node/distort/cornerpin/cornerpindistortnode.h b/app/node/distort/cornerpin/cornerpindistortnode.h index 86d80952f..41a82ca88 100644 --- a/app/node/distort/cornerpin/cornerpindistortnode.h +++ b/app/node/distort/cornerpin/cornerpindistortnode.h @@ -35,12 +35,7 @@ class CornerPinDistortNode : public Node public: CornerPinDistortNode(); - NODE_DEFAULT_DESTRUCTOR(CornerPinDistortNode) - - virtual Node* copy() const override - { - return new CornerPinDistortNode(); - } + NODE_DEFAULT_FUNCTIONS(CornerPinDistortNode) virtual QString Name() const override { @@ -66,7 +61,7 @@ public: virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - virtual ShaderCode GetShaderCode(const QString &shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; diff --git a/app/node/distort/crop/cropdistortnode.cpp b/app/node/distort/crop/cropdistortnode.cpp index 5fc6dd56a..62532f839 100644 --- a/app/node/distort/crop/cropdistortnode.cpp +++ b/app/node/distort/crop/cropdistortnode.cpp @@ -78,25 +78,25 @@ void CropDistortNode::Retranslate() void CropDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { ShaderJob job; - job.InsertValue(value); - job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + job.Insert(value); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); - if (!job.GetValue(kTextureInput).data().isNull()) { - if (!qIsNull(job.GetValue(kLeftInput).data().toDouble()) - || !qIsNull(job.GetValue(kRightInput).data().toDouble()) - || !qIsNull(job.GetValue(kTopInput).data().toDouble()) - || !qIsNull(job.GetValue(kBottomInput).data().toDouble())) { + if (job.Get(kTextureInput).toTexture()) { + if (!qIsNull(job.Get(kLeftInput).toDouble()) + || !qIsNull(job.Get(kRightInput).toDouble()) + || !qIsNull(job.Get(kTopInput).toDouble()) + || !qIsNull(job.Get(kBottomInput).toDouble())) { table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } else { - table->Push(NodeValue::kTexture, job.GetValue(kTextureInput).data(), this); + table->Push(job.Get(kTextureInput)); } } } -ShaderCode CropDistortNode::GetShaderCode(const QString &shader_id) const +ShaderCode CropDistortNode::GetShaderCode(const ShaderRequest &request) const { - Q_UNUSED(shader_id) + Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/crop.frag"))); } @@ -104,10 +104,10 @@ void CropDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGl { const QVector2D &resolution = globals.resolution(); - double left_pt = resolution.x() * row[kLeftInput].data().toDouble(); - double top_pt = resolution.y() * row[kTopInput].data().toDouble(); - double right_pt = resolution.x() * (1.0 - row[kRightInput].data().toDouble()); - double bottom_pt = resolution.y() * (1.0 - row[kBottomInput].data().toDouble()); + double left_pt = resolution.x() * row[kLeftInput].toDouble(); + double top_pt = resolution.y() * row[kTopInput].toDouble(); + double right_pt = resolution.x() * (1.0 - row[kRightInput].toDouble()); + double bottom_pt = resolution.y() * (1.0 - row[kBottomInput].toDouble()); double center_x_pt = mid(left_pt, right_pt); double center_y_pt = mid(top_pt, bottom_pt); diff --git a/app/node/distort/crop/cropdistortnode.h b/app/node/distort/crop/cropdistortnode.h index d2916a24d..697f5540a 100644 --- a/app/node/distort/crop/cropdistortnode.h +++ b/app/node/distort/crop/cropdistortnode.h @@ -36,12 +36,7 @@ class CropDistortNode : public Node public: CropDistortNode(); - NODE_DEFAULT_DESTRUCTOR(CropDistortNode) - - virtual Node* copy() const override - { - return new CropDistortNode(); - } + NODE_DEFAULT_FUNCTIONS(CropDistortNode) virtual QString Name() const override { @@ -67,7 +62,7 @@ public: virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - virtual ShaderCode GetShaderCode(const QString &shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; diff --git a/app/node/distort/flip/flipdistortnode.cpp b/app/node/distort/flip/flipdistortnode.cpp index 4589b4542..050bbcc84 100644 --- a/app/node/distort/flip/flipdistortnode.cpp +++ b/app/node/distort/flip/flipdistortnode.cpp @@ -40,11 +40,6 @@ FlipDistortNode::FlipDistortNode() SetEffectInput(kTextureInput); } -Node* FlipDistortNode::copy() const -{ - return new FlipDistortNode(); -} - QString FlipDistortNode::Name() const { return tr("Flip"); @@ -74,9 +69,9 @@ void FlipDistortNode::Retranslate() SetInputName(kVerticalInput, tr("Vertical")); } -ShaderCode FlipDistortNode::GetShaderCode(const QString& shader_id) const +ShaderCode FlipDistortNode::GetShaderCode(const ShaderRequest &request) const { - Q_UNUSED(shader_id) + Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/flip.frag")); } @@ -84,16 +79,16 @@ void FlipDistortNode::Value(const NodeValueRow &value, const NodeGlobals &global { ShaderJob job; - job.InsertValue(value); + job.Insert(value); // If there's no texture, no need to run an operation - if (!job.GetValue(kTextureInput).data().isNull()) { + if (job.Get(kTextureInput).toTexture()) { // Only run shader if at least one of flip or flop are selected - if (job.GetValue(kHorizontalInput).data().toBool() || job.GetValue(kVerticalInput).data().toBool()) { + if (job.Get(kHorizontalInput).toBool() || job.Get(kVerticalInput).toBool()) { table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } else { // If we're not flipping or flopping just push the texture - table->Push(job.GetValue(kTextureInput)); + table->Push(job.Get(kTextureInput)); } } diff --git a/app/node/distort/flip/flipdistortnode.h b/app/node/distort/flip/flipdistortnode.h index ba78023f1..2d607ffd3 100644 --- a/app/node/distort/flip/flipdistortnode.h +++ b/app/node/distort/flip/flipdistortnode.h @@ -31,9 +31,7 @@ class FlipDistortNode : public Node public: FlipDistortNode(); - NODE_DEFAULT_DESTRUCTOR(FlipDistortNode) - - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(FlipDistortNode) virtual QString Name() const override; virtual QString id() const override; @@ -42,7 +40,7 @@ public: virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const QString &shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; static const QString kTextureInput; diff --git a/app/node/distort/mask/CMakeLists.txt b/app/node/distort/mask/CMakeLists.txt new file mode 100644 index 000000000..67ff95dee --- /dev/null +++ b/app/node/distort/mask/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2021 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/distort/mask/mask.cpp + node/distort/mask/mask.h + PARENT_SCOPE +) diff --git a/app/node/distort/mask/mask.cpp b/app/node/distort/mask/mask.cpp new file mode 100644 index 000000000..b891a262d --- /dev/null +++ b/app/node/distort/mask/mask.cpp @@ -0,0 +1,94 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "mask.h" + +#include "node/filter/blur/blur.h" + +namespace olive { + +#define super PolygonGenerator + +const QString MaskDistortNode::kFeatherInput = QStringLiteral("feather_in"); + +MaskDistortNode::MaskDistortNode() +{ + // Mask should always be (1.0, 1.0, 1.0) for multiply to work correctly + SetInputFlags(kColorInput, InputFlags(GetInputFlags(kColorInput) | kInputFlagHidden)); + + AddInput(kFeatherInput, NodeValue::kFloat, 0.0); + SetInputProperty(kFeatherInput, QStringLiteral("min"), 0.0); +} + +ShaderCode MaskDistortNode::GetShaderCode(const ShaderRequest &request) const +{ + if (request.id == QStringLiteral("mrg")) { + return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/multiply.frag"))); + } else if (request.id == QStringLiteral("feather")) { + return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/blur.frag"))); + } else { + return ShaderCode(); + } +} + +void MaskDistortNode::Retranslate() +{ + super::Retranslate(); + + SetInputName(kBaseInput, tr("Texture")); + SetInputName(kFeatherInput, tr("Feather")); +} + +void MaskDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + GenerateJob job = GetGenerateJob(value); + + if (value[kBaseInput].toTexture()) { + // Push as merge node + ShaderJob merge; + + merge.SetShaderID(QStringLiteral("mrg")); + merge.Insert(QStringLiteral("tex_a"), value[kBaseInput]); + + if (value[kFeatherInput].toDouble() > 0.0) { + // Nest a blur shader in there too + ShaderJob feather; + + feather.SetShaderID(QStringLiteral("feather")); + feather.Insert(BlurFilterNode::kTextureInput, NodeValue(NodeValue::kTexture, job, this)); + feather.Insert(BlurFilterNode::kMethodInput, NodeValue(NodeValue::kInt, int(BlurFilterNode::kGaussian), this)); + feather.Insert(BlurFilterNode::kHorizInput, NodeValue(NodeValue::kBoolean, true, this)); + feather.Insert(BlurFilterNode::kVertInput, NodeValue(NodeValue::kBoolean, true, this)); + feather.Insert(BlurFilterNode::kRepeatEdgePixelsInput, NodeValue(NodeValue::kBoolean, true, this)); + feather.Insert(BlurFilterNode::kRadiusInput, NodeValue(NodeValue::kFloat, value[kFeatherInput].toDouble(), this)); + feather.SetIterations(2, BlurFilterNode::kTextureInput); + feather.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + feather.SetAlphaChannelRequired(ShaderJob::kAlphaForceOn); + + merge.Insert(QStringLiteral("tex_b"), NodeValue(NodeValue::kTexture, feather, this)); + } else { + merge.Insert(QStringLiteral("tex_b"), NodeValue(NodeValue::kTexture, job, this)); + } + + table->Push(NodeValue::kTexture, QVariant::fromValue(merge), this); + } +} + +} diff --git a/app/node/distort/mask/mask.h b/app/node/distort/mask/mask.h new file mode 100644 index 000000000..c782cb15a --- /dev/null +++ b/app/node/distort/mask/mask.h @@ -0,0 +1,68 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef MASKDISTORTNODE_H +#define MASKDISTORTNODE_H + +#include "node/generator/polygon/polygon.h" + +namespace olive { + +class MaskDistortNode : public PolygonGenerator +{ + Q_OBJECT +public: + MaskDistortNode(); + + NODE_DEFAULT_FUNCTIONS(MaskDistortNode) + + virtual QString Name() const override + { + return tr("Mask"); + } + + virtual QString id() const override + { + return QStringLiteral("org.olivevideoeditor.Olive.mask"); + } + + virtual QVector Category() const override + { + return {kCategoryDistort}; + } + + virtual QString Description() const override + { + return tr("Apply a polygonal mask."); + } + + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; + + virtual void Retranslate() override; + + virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + + static const QString kFeatherInput; + +}; + +} + +#endif // MASKDISTORTNODE_H diff --git a/app/node/distort/transform/transformdistortnode.cpp b/app/node/distort/transform/transformdistortnode.cpp index 1b5cad90f..093808738 100644 --- a/app/node/distort/transform/transformdistortnode.cpp +++ b/app/node/distort/transform/transformdistortnode.cpp @@ -92,16 +92,16 @@ void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g bool pushed_job = false; // If we have a texture, generate a matrix and make it happen - if (TexturePtr texture = texture_meta.data().value()) { + if (TexturePtr texture = texture_meta.toTexture()) { // Adjust our matrix by the resolutions involved QMatrix4x4 real_matrix = GenerateAutoScaledMatrix(generated_matrix, value, globals, texture->params()); if (!real_matrix.isIdentity()) { // The matrix will transform things ShaderJob job; - job.InsertValue(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture), this)); - job.InsertValue(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, real_matrix, this)); - job.SetInterpolation(QStringLiteral("ove_maintex"), static_cast(value[kInterpolationInput].data().toInt())); + job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture), this)); + job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, real_matrix, this)); + job.SetInterpolation(QStringLiteral("ove_maintex"), static_cast(value[kInterpolationInput].toInt())); // FIXME: This should be optimized, we can use matrix math to determine if this operation will // end up with gaps in the screen that will require an alpha channel. @@ -119,9 +119,9 @@ void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g } } -ShaderCode TransformDistortNode::GetShaderCode(const QString &shader_id) const +ShaderCode TransformDistortNode::GetShaderCode(const ShaderRequest &request) const { - Q_UNUSED(shader_id); + Q_UNUSED(request); // Returns default frag and vert shader return ShaderCode(); @@ -140,7 +140,7 @@ void TransformDistortNode::Hash(QCryptographicHash &hash, const NodeGlobals &glo traverser.SetCacheVideoParams(video_params); NodeValueRow db = traverser.GenerateRow(this, globals.time()); - TexturePtr tex = db[kTextureInput].data().value(); + TexturePtr tex = db[kTextureInput].toTexture(); if (tex) { VideoParams tex_params = tex->params(); QMatrix4x4 matrix = GenerateMatrix(db, true, false, false, false); @@ -167,13 +167,13 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou } else if (IsAScaleGizmo(gizmo)) { // Dragging scale handle - TexturePtr tex = row[kTextureInput].data().value(); + TexturePtr tex = row[kTextureInput].toTexture(); if (!tex) { return; } - gizmo_scale_uniform_ = row[kUniformScaleInput].data().toBool(); - gizmo_anchor_pt_ = (row[kAnchorInput].data().value() + gizmo->GetGlobals().resolution()/2).toPointF(); + gizmo_scale_uniform_ = row[kUniformScaleInput].toBool(); + gizmo_anchor_pt_ = (row[kAnchorInput].toVec2() + gizmo->GetGlobals().resolution()/2).toPointF(); if (gizmo == point_gizmo_[kGizmoScaleTopLeft] || gizmo == point_gizmo_[kGizmoScaleTopRight] || gizmo == point_gizmo_[kGizmoScaleBottomLeft] || gizmo == point_gizmo_[kGizmoScaleBottomRight]) { @@ -187,7 +187,7 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou // Store texture size VideoParams texture_params = tex->params(); QVector2D texture_sz(texture_params.square_pixel_width(), texture_params.height()); - gizmo_scale_anchor_ = row[kAnchorInput].data().value() + texture_sz/2; + gizmo_scale_anchor_ = row[kAnchorInput].toVec2() + texture_sz/2; if (gizmo == point_gizmo_[kGizmoScaleTopRight] || gizmo == point_gizmo_[kGizmoScaleBottomRight] @@ -208,7 +208,7 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou } else if (gizmo == rotation_gizmo_) { - gizmo_anchor_pt_ = (row[kAnchorInput].data().value() + gizmo->GetGlobals().resolution()/2).toPointF(); + gizmo_anchor_pt_ = (row[kAnchorInput].toVec2() + gizmo->GetGlobals().resolution()/2).toPointF(); gizmo_start_angle_ = qAtan2(y - gizmo_anchor_pt_.y(), x - gizmo_anchor_pt_.x()); gizmo_last_angle_ = gizmo_start_angle_; gizmo_last_alt_angle_ = qAtan2(x - gizmo_anchor_pt_.x(), y - gizmo_anchor_pt_.y()); @@ -368,7 +368,7 @@ QMatrix4x4 TransformDistortNode::AdjustMatrixByResolutions(const QMatrix4x4 &mat void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) { - TexturePtr tex = row[kTextureInput].data().value(); + TexturePtr tex = row[kTextureInput].toTexture(); if (!tex) { return; } @@ -384,7 +384,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N QVector2D tex_offset = tex_params.offset(); // Retrieve autoscale value - AutoScaleType autoscale = static_cast(row[kAutoscaleInput].data().toInt()); + AutoScaleType autoscale = static_cast(row[kAutoscaleInput].toInt()); // Fold values into a matrix for the rectangle QMatrix4x4 rectangle_matrix; @@ -441,7 +441,7 @@ QMatrix4x4 TransformDistortNode::GenerateAutoScaledMatrix(const QMatrix4x4& gene { const QVector2D &sequence_res = globals.resolution(); QVector2D texture_res(texture_params.square_pixel_width(), texture_params.height()); - AutoScaleType autoscale = static_cast(value[kAutoscaleInput].data().toInt()); + AutoScaleType autoscale = static_cast(value[kAutoscaleInput].toInt()); return AdjustMatrixByResolutions(generated_matrix, sequence_res, diff --git a/app/node/distort/transform/transformdistortnode.h b/app/node/distort/transform/transformdistortnode.h index f584ff8e6..e2ae7eb93 100644 --- a/app/node/distort/transform/transformdistortnode.h +++ b/app/node/distort/transform/transformdistortnode.h @@ -34,23 +34,13 @@ class TransformDistortNode : public MatrixGenerator public: TransformDistortNode(); - NODE_DEFAULT_DESTRUCTOR(TransformDistortNode) - - virtual Node* copy() const override - { - return new TransformDistortNode(); - } + NODE_DEFAULT_FUNCTIONS(TransformDistortNode) virtual QString Name() const override { return tr("Transform"); } - virtual QString ShortName() const override - { - return Node::ShortName(); - } - virtual QString id() const override { return QStringLiteral("org.olivevideoeditor.Olive.transform"); @@ -70,7 +60,7 @@ public: virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - virtual ShaderCode GetShaderCode(const QString& shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; enum AutoScaleType { kAutoScaleNone, diff --git a/app/node/effect/opacity/opacityeffect.cpp b/app/node/effect/opacity/opacityeffect.cpp index 6027c2ab3..9ebabbc20 100644 --- a/app/node/effect/opacity/opacityeffect.cpp +++ b/app/node/effect/opacity/opacityeffect.cpp @@ -37,9 +37,9 @@ void OpacityEffect::Retranslate() SetInputName(kValueInput, tr("Opacity")); } -ShaderCode OpacityEffect::GetShaderCode(const QString &shader_id) const +ShaderCode OpacityEffect::GetShaderCode(const ShaderRequest &request) const { - Q_UNUSED(shader_id) + Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/opacity.frag")); } @@ -47,16 +47,16 @@ void OpacityEffect::Value(const NodeValueRow &value, const NodeGlobals &globals, { ShaderJob job; - job.InsertValue(value); + job.Insert(value); // If there's no texture, no need to run an operation - if (!job.GetValue(kTextureInput).data().isNull()) { - if (!qFuzzyCompare(job.GetValue(kValueInput).data().toDouble(), 1.0)) { + if (job.Get(kTextureInput).toTexture()) { + if (!qFuzzyCompare(job.Get(kValueInput).toDouble(), 1.0)) { job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } else { // 1.0 float is a no-op, so just push the texture - table->Push(job.GetValue(kTextureInput)); + table->Push(job.Get(kTextureInput)); } } } diff --git a/app/node/effect/opacity/opacityeffect.h b/app/node/effect/opacity/opacityeffect.h index d18af81cc..36e284274 100644 --- a/app/node/effect/opacity/opacityeffect.h +++ b/app/node/effect/opacity/opacityeffect.h @@ -10,9 +10,7 @@ class OpacityEffect : public Node public: OpacityEffect(); - NODE_DEFAULT_DESTRUCTOR(OpacityEffect) - - NODE_COPY_FUNCTION(OpacityEffect) + NODE_DEFAULT_FUNCTIONS(OpacityEffect) virtual QString Name() const override { @@ -36,7 +34,7 @@ public: virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const QString &shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; static const QString kTextureInput; diff --git a/app/node/factory.cpp b/app/node/factory.cpp index cf5e3b0c4..6e60b1ccb 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -29,9 +29,12 @@ #include "block/subtitle/subtitle.h" #include "block/transition/crossdissolve/crossdissolvetransition.h" #include "block/transition/diptocolor/diptocolortransition.h" +#include "color/displaytransform/displaytransform.h" +#include "color/ociogradingtransformlinear/ociogradingtransformlinear.h" #include "distort/cornerpin/cornerpindistortnode.h" #include "distort/crop/cropdistortnode.h" #include "distort/flip/flipdistortnode.h" +#include "distort/mask/mask.h" #include "distort/transform/transformdistortnode.h" #include "effect/opacity/opacityeffect.h" #include "generator/matrix/matrix.h" @@ -52,6 +55,7 @@ #include "math/trigonometry/trigonometry.h" #include "keying/colordifferencekey/colordifferencekey.h" #include "keying/despill/despill.h" +#include "keying/chromakey/chromakey.h" #include "output/track/track.h" #include "output/viewer/viewer.h" #include "project/folder/folder.h" @@ -61,8 +65,8 @@ #include "time/timeremap/timeremap.h" namespace olive { + QList NodeFactory::library_; -QVector NodeFactory::hidden_; void NodeFactory::Initialize() { @@ -74,10 +78,6 @@ void NodeFactory::Initialize() library_.append(created_node); } - - hidden_.append(kTextGeneratorV1); - hidden_.append(kTextGeneratorV2); - hidden_.append(kGroupNode); } void NodeFactory::Destroy() @@ -103,8 +103,7 @@ Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item, Node::Cate continue; } - if (hidden_.contains(i)) { - // Skip this node + if (n->GetFlags() & Node::kDontShowInCreateMenu) { continue; } @@ -281,6 +280,14 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return new TimeOffsetNode(); case kCornerPinDistort: return new CornerPinDistortNode(); + case kDisplayTransform: + return new DisplayTransformNode(); + case kOCIOGradingTransformLinear: + return new OCIOGradingTransformLinearNode(); + case kChromaKey: + return new ChromaKeyNode(); + case kMaskDistort: + return new MaskDistortNode(); case kInternalNodeCount: break; diff --git a/app/node/factory.h b/app/node/factory.h index 56192cc7b..05bde90f4 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -70,6 +70,10 @@ public: kNoiseGenerator, kTimeOffsetNode, kCornerPinDistort, + kDisplayTransform, + kOCIOGradingTransformLinear, + kChromaKey, + kMaskDistort, // Count value kInternalNodeCount @@ -96,8 +100,6 @@ public: private: static QList library_; - static QVector hidden_; - }; } diff --git a/app/node/filter/blur/blur.cpp b/app/node/filter/blur/blur.cpp index 0452c4fba..6b822cf43 100644 --- a/app/node/filter/blur/blur.cpp +++ b/app/node/filter/blur/blur.cpp @@ -29,30 +29,50 @@ const QString BlurFilterNode::kHorizInput = QStringLiteral("horiz_in"); const QString BlurFilterNode::kVertInput = QStringLiteral("vert_in"); const QString BlurFilterNode::kRepeatEdgePixelsInput = QStringLiteral("repeat_edge_pixels_in"); +const QString BlurFilterNode::kDirectionalDegreesInput = QStringLiteral("directional_degrees_in"); + +const QString BlurFilterNode::kRadialCenterInput = QStringLiteral("radial_center_in"); + #define super Node BlurFilterNode::BlurFilterNode() { AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); - AddInput(kMethodInput, NodeValue::kCombo, 1); // Default to gaussian + Method default_method = kGaussian; + + AddInput(kMethodInput, NodeValue::kCombo, default_method, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable)); AddInput(kRadiusInput, NodeValue::kFloat, 10.0); SetInputProperty(kRadiusInput, QStringLiteral("min"), 0.0); - AddInput(kHorizInput, NodeValue::kBoolean, true); + { + // Box and gaussian only + AddInput(kHorizInput, NodeValue::kBoolean, true); + AddInput(kVertInput, NodeValue::kBoolean, true); + } - AddInput(kVertInput, NodeValue::kBoolean, true); + { + // Directional only + AddInput(kDirectionalDegreesInput, NodeValue::kFloat, 0.0); + } + + { + // Radial only + AddInput(kRadialCenterInput, NodeValue::kVec2, QVector2D(0, 0)); + } + + UpdateInputs(default_method); AddInput(kRepeatEdgePixelsInput, NodeValue::kBoolean, true); SetFlags(kVideoEffect); SetEffectInput(kTextureInput); -} -Node *BlurFilterNode::copy() const -{ - return new BlurFilterNode(); + radial_center_gizmo_ = AddDraggableGizmo(); + radial_center_gizmo_->SetShape(PointGizmo::kAnchorPoint); + radial_center_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kRadialCenterInput), 0)); + radial_center_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kRadialCenterInput), 1)); } QString BlurFilterNode::Name() const @@ -81,16 +101,19 @@ void BlurFilterNode::Retranslate() SetInputName(kTextureInput, tr("Input")); SetInputName(kMethodInput, tr("Method")); - SetComboBoxStrings(kMethodInput, { tr("Box"), tr("Gaussian") }); + SetComboBoxStrings(kMethodInput, { tr("Box"), tr("Gaussian"), tr("Directional"), tr("Radial") }); SetInputName(kRadiusInput, tr("Radius")); SetInputName(kHorizInput, tr("Horizontal")); SetInputName(kVertInput, tr("Vertical")); SetInputName(kRepeatEdgePixelsInput, tr("Repeat Edge Pixels")); + + SetInputName(kDirectionalDegreesInput, tr("Direction")); + SetInputName(kRadialCenterInput, tr("Center")); } -ShaderCode BlurFilterNode::GetShaderCode(const QString &shader_id) const +ShaderCode BlurFilterNode::GetShaderCode(const ShaderRequest &request) const { - Q_UNUSED(shader_id) + Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/blur.frag")); } @@ -98,34 +121,103 @@ void BlurFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals { ShaderJob job; - job.InsertValue(value); - job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + job.Insert(value); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + + Method method = static_cast(job.Get(kMethodInput).toInt()); // If there's no texture, no need to run an operation - if (!job.GetValue(kTextureInput).data().isNull()) { + if (job.Get(kTextureInput).toTexture()) { - // Check if radius > 0, and both "horiz" and/or "vert" are enabled - if ((job.GetValue(kHorizInput).data().toBool() || job.GetValue(kVertInput).data().toBool()) - && job.GetValue(kRadiusInput).data().toDouble() > 0.0) { + bool can_push_job = true; - // Set iteration count to 2 if we're blurring both horizontally and vertically - if (job.GetValue(kHorizInput).data().toBool() && job.GetValue(kVertInput).data().toBool()) { - job.SetIterations(2, kTextureInput); + // Check if radius is > 0 + if (job.Get(kRadiusInput).toDouble() > 0.0) { + // Method-specific considerations + switch (method) { + case kBox: + case kGaussian: + { + bool horiz = job.Get(kHorizInput).toBool(); + bool vert = job.Get(kVertInput).toBool(); + + if (!horiz && !vert) { + // Disable job if horiz and vert are unchecked + can_push_job = false; + } else if (horiz && vert) { + // Set iteration count to 2 if we're blurring both horizontally and vertically + job.SetIterations(2, kTextureInput); + } + break; } + case kDirectional: + case kRadial: + break; + } + } else { + can_push_job = false; + } + if (can_push_job) { // If we're not repeating pixels, expect an alpha channel to appear - if (!job.GetValue(kRepeatEdgePixelsInput).data().toBool()) { + if (!job.Get(kRepeatEdgePixelsInput).toBool()) { job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); } table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); - } else { // If we're not performing the blur job, just push the texture - table->Push(job.GetValue(kTextureInput)); + table->Push(job.Get(kTextureInput)); } } } +void BlurFilterNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) +{ + if (row[kMethodInput].toInt() == kRadial) { + const QVector2D &sequence_res = globals.resolution(); + QVector2D sequence_half_res = sequence_res * 0.5; + + radial_center_gizmo_->SetVisible(true); + radial_center_gizmo_->SetPoint(sequence_half_res.toPointF() + row[kRadialCenterInput].toVec2().toPointF()); + + SetInputProperty(kRadialCenterInput, QStringLiteral("offset"), sequence_half_res); + } else{ + radial_center_gizmo_->SetVisible(false); + } +} + +void BlurFilterNode::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) +{ + DraggableGizmo *gizmo = static_cast(sender()); + + if (gizmo == radial_center_gizmo_) { + + NodeInputDragger &x_drag = gizmo->GetDraggers()[0]; + NodeInputDragger &y_drag = gizmo->GetDraggers()[1]; + + x_drag.Drag(x_drag.GetStartValue().toDouble() + x); + y_drag.Drag(y_drag.GetStartValue().toDouble() + y); + + } +} + +void BlurFilterNode::InputValueChangedEvent(const QString &input, int element) +{ + if (input == kMethodInput) { + UpdateInputs(GetMethod()); + } + + super::InputValueChangedEvent(input, element); +} + +void BlurFilterNode::UpdateInputs(Method method) +{ + SetInputFlags(kHorizInput, (method == kBox || method == kGaussian) ? InputFlags() : InputFlags(kInputFlagHidden)); + SetInputFlags(kVertInput, (method == kBox || method == kGaussian) ? InputFlags() : InputFlags(kInputFlagHidden)); + SetInputFlags(kDirectionalDegreesInput, (method == kDirectional) ? InputFlags() : InputFlags(kInputFlagHidden)); + SetInputFlags(kRadialCenterInput, (method == kRadial) ? InputFlags() : InputFlags(kInputFlagHidden)); +} + } diff --git a/app/node/filter/blur/blur.h b/app/node/filter/blur/blur.h index 4b061040d..9355dd4c6 100644 --- a/app/node/filter/blur/blur.h +++ b/app/node/filter/blur/blur.h @@ -21,6 +21,7 @@ #ifndef BLURFILTERNODE_H #define BLURFILTERNODE_H +#include "node/gizmo/point.h" #include "node/node.h" namespace olive { @@ -31,9 +32,14 @@ class BlurFilterNode : public Node public: BlurFilterNode(); - NODE_DEFAULT_DESTRUCTOR(BlurFilterNode) + enum Method { + kBox, + kGaussian, + kDirectional, + kRadial + }; - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(BlurFilterNode) virtual QString Name() const override; virtual QString id() const override; @@ -42,9 +48,16 @@ public: virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const QString &shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + Method GetMethod() const + { + return static_cast(GetStandardValue(kMethodInput).toInt()); + } + + virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + static const QString kTextureInput; static const QString kMethodInput; static const QString kRadiusInput; @@ -52,6 +65,21 @@ public: static const QString kVertInput; static const QString kRepeatEdgePixelsInput; + static const QString kDirectionalDegreesInput; + + static const QString kRadialCenterInput; + +protected slots: + virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; + +protected: + virtual void InputValueChangedEvent(const QString& input, int element) override; + +private: + void UpdateInputs(Method method); + + PointGizmo *radial_center_gizmo_; + }; } diff --git a/app/node/filter/mosaic/mosaicfilternode.cpp b/app/node/filter/mosaic/mosaicfilternode.cpp index c198868c5..cf80a2139 100644 --- a/app/node/filter/mosaic/mosaicfilternode.cpp +++ b/app/node/filter/mosaic/mosaicfilternode.cpp @@ -55,27 +55,27 @@ void MosaicFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globa { ShaderJob job; - job.InsertValue(value); + job.Insert(value); // Mipmapping makes this look weird, so we just use bilinear for finding the color of each block job.SetInterpolation(kTextureInput, Texture::kLinear); - if (!job.GetValue(kTextureInput).data().isNull()) { - TexturePtr texture = job.GetValue(kTextureInput).data().value(); + if (job.Get(kTextureInput).toTexture()) { + TexturePtr texture = job.Get(kTextureInput).toTexture(); if (texture - && job.GetValue(kHorizInput).data().toInt() != texture->width() - && job.GetValue(kVertInput).data().toInt() != texture->height()) { + && job.Get(kHorizInput).toInt() != texture->width() + && job.Get(kVertInput).toInt() != texture->height()) { table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } else { - table->Push(job.GetValue(kTextureInput)); + table->Push(job.Get(kTextureInput)); } } } -ShaderCode MosaicFilterNode::GetShaderCode(const QString &shader_id) const +ShaderCode MosaicFilterNode::GetShaderCode(const ShaderRequest &request) const { - Q_UNUSED(shader_id) + Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/mosaic.frag")); } diff --git a/app/node/filter/mosaic/mosaicfilternode.h b/app/node/filter/mosaic/mosaicfilternode.h index 726da5744..fed3f3168 100644 --- a/app/node/filter/mosaic/mosaicfilternode.h +++ b/app/node/filter/mosaic/mosaicfilternode.h @@ -31,12 +31,7 @@ class MosaicFilterNode : public Node public: MosaicFilterNode(); - NODE_DEFAULT_DESTRUCTOR(MosaicFilterNode) - - virtual Node* copy() const override - { - return new MosaicFilterNode(); - } + NODE_DEFAULT_FUNCTIONS(MosaicFilterNode) virtual QString Name() const override { @@ -61,7 +56,7 @@ public: virtual void Retranslate() override; virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - virtual ShaderCode GetShaderCode(const QString &shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; static const QString kTextureInput; static const QString kHorizInput; diff --git a/app/node/filter/stroke/stroke.cpp b/app/node/filter/stroke/stroke.cpp index d0360cc10..f3edea14e 100644 --- a/app/node/filter/stroke/stroke.cpp +++ b/app/node/filter/stroke/stroke.cpp @@ -53,11 +53,6 @@ StrokeFilterNode::StrokeFilterNode() SetEffectInput(kTextureInput); } -Node *StrokeFilterNode::copy() const -{ - return new StrokeFilterNode(); -} - QString StrokeFilterNode::Name() const { return tr("Stroke"); @@ -93,22 +88,22 @@ void StrokeFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globa { ShaderJob job; - job.InsertValue(value); - job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + job.Insert(value); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); - if (!job.GetValue(kTextureInput).data().isNull()) { - if (job.GetValue(kRadiusInput).data().toDouble() > 0.0 - && job.GetValue(kOpacityInput).data().toDouble() > 0.0) { + if (job.Get(kTextureInput).toTexture()) { + if (job.Get(kRadiusInput).toDouble() > 0.0 + && job.Get(kOpacityInput).toDouble() > 0.0) { table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } else { - table->Push(job.GetValue(kTextureInput)); + table->Push(job.Get(kTextureInput)); } } } -ShaderCode StrokeFilterNode::GetShaderCode(const QString &shader_id) const +ShaderCode StrokeFilterNode::GetShaderCode(const ShaderRequest &request) const { - Q_UNUSED(shader_id) + Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/stroke.frag")); } diff --git a/app/node/filter/stroke/stroke.h b/app/node/filter/stroke/stroke.h index d70e7e7bc..48a442ace 100644 --- a/app/node/filter/stroke/stroke.h +++ b/app/node/filter/stroke/stroke.h @@ -31,9 +31,7 @@ class StrokeFilterNode : public Node public: StrokeFilterNode(); - NODE_DEFAULT_DESTRUCTOR(StrokeFilterNode) - - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(StrokeFilterNode) virtual QString Name() const override; virtual QString id() const override; @@ -43,7 +41,7 @@ public: virtual void Retranslate() override; virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - virtual ShaderCode GetShaderCode(const QString &shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; static const QString kTextureInput; static const QString kColorInput; diff --git a/app/node/generator/matrix/matrix.cpp b/app/node/generator/matrix/matrix.cpp index bbd5c9ba6..d545ab2d9 100644 --- a/app/node/generator/matrix/matrix.cpp +++ b/app/node/generator/matrix/matrix.cpp @@ -44,18 +44,13 @@ MatrixGenerator::MatrixGenerator() AddInput(kScaleInput, NodeValue::kVec2, QVector2D(1.0f, 1.0f)); SetInputProperty(kScaleInput, QStringLiteral("min"), QVector2D(0, 0)); SetInputProperty(kScaleInput, QStringLiteral("view"), FloatSlider::kPercentage); - SetInputProperty(kScaleInput, QStringLiteral("disabley"), true); + SetInputProperty(kScaleInput, QStringLiteral("disable1"), true); AddInput(kUniformScaleInput, NodeValue::kBoolean, true, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); AddInput(kAnchorInput, NodeValue::kVec2, QVector2D(0.0, 0.0)); } -Node *MatrixGenerator::copy() const -{ - return new MatrixGenerator(); -} - QString MatrixGenerator::Name() const { return tr("Orthographic Matrix"); @@ -108,47 +103,47 @@ QMatrix4x4 MatrixGenerator::GenerateMatrix(const NodeValueRow &value, bool take, if (!ignore_anchor) { if (take) { // Take and store - anchor = value[kAnchorInput].data().value(); + anchor = value[kAnchorInput].toVec2(); } else { // Get and store - anchor = value[kAnchorInput].data().value(); + anchor = value[kAnchorInput].toVec2(); } } else if (take) { // Just take - value[kAnchorInput].data().value(); + value[kAnchorInput].toVec2(); } if (!ignore_scale) { if (take) { - scale = value[kScaleInput].data().value(); + scale = value[kScaleInput].toVec2(); } else { - scale = value[kScaleInput].data().value(); + scale = value[kScaleInput].toVec2(); } } else if (take) { - value[kScaleInput].data().value(); + value[kScaleInput].toVec2(); } if (!ignore_position) { if (take) { - position = value[kPositionInput].data().value(); + position = value[kPositionInput].toVec2(); } else { - position = value[kPositionInput].data().value(); + position = value[kPositionInput].toVec2(); } } else if (take) { - value[kPositionInput].data().value(); + value[kPositionInput].toVec2(); } if (take) { return GenerateMatrix(position, - value[kRotationInput].data().toFloat(), + value[kRotationInput].toDouble(), scale, - value[kUniformScaleInput].data().toBool(), + value[kUniformScaleInput].toBool(), anchor); } else { return GenerateMatrix(position, - value[kRotationInput].data().toFloat(), + value[kRotationInput].toDouble(), scale, - value[kUniformScaleInput].data().toBool(), + value[kUniformScaleInput].toBool(), anchor); } @@ -188,7 +183,7 @@ void MatrixGenerator::InputValueChangedEvent(const QString &input, int element) Q_UNUSED(element) if (input == kUniformScaleInput) { - SetInputProperty(kScaleInput, QStringLiteral("disabley"), GetStandardValue(kUniformScaleInput).toBool()); + SetInputProperty(kScaleInput, QStringLiteral("disable1"), GetStandardValue(kUniformScaleInput).toBool()); } } diff --git a/app/node/generator/matrix/matrix.h b/app/node/generator/matrix/matrix.h index 6ccb318a0..3466c703d 100644 --- a/app/node/generator/matrix/matrix.h +++ b/app/node/generator/matrix/matrix.h @@ -34,9 +34,7 @@ class MatrixGenerator : public Node public: MatrixGenerator(); - NODE_DEFAULT_DESTRUCTOR(MatrixGenerator) - - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(MatrixGenerator) virtual QString Name() const override; virtual QString ShortName() const override; diff --git a/app/node/generator/noise/noise.cpp b/app/node/generator/noise/noise.cpp index 213910ad4..dff259996 100644 --- a/app/node/generator/noise/noise.cpp +++ b/app/node/generator/noise/noise.cpp @@ -44,11 +44,6 @@ NoiseGeneratorNode::NoiseGeneratorNode() SetFlags(kVideoEffect); } -Node* NoiseGeneratorNode::copy() const -{ - return new NoiseGeneratorNode(); -} - QString NoiseGeneratorNode::Name() const { return tr("Noise"); @@ -78,7 +73,7 @@ void NoiseGeneratorNode::Retranslate() SetInputName(kColorInput, tr("Color")); } -ShaderCode NoiseGeneratorNode::GetShaderCode(const QString& shader_id) const +ShaderCode NoiseGeneratorNode::GetShaderCode(const ShaderRequest &request) const { return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/noise.frag")); } @@ -87,8 +82,8 @@ void NoiseGeneratorNode::Value(const NodeValueRow &value, const NodeGlobals &glo { ShaderJob job; - job.InsertValue(value); - job.InsertValue(QStringLiteral("time_in"), NodeValue(NodeValue::kFloat, globals.time().in().toDouble(), this)); + job.Insert(value); + job.Insert(QStringLiteral("time_in"), NodeValue(NodeValue::kFloat, globals.time().in().toDouble(), this)); table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } diff --git a/app/node/generator/noise/noise.h b/app/node/generator/noise/noise.h index 36cbd7fe8..73a3a0c0b 100644 --- a/app/node/generator/noise/noise.h +++ b/app/node/generator/noise/noise.h @@ -30,9 +30,7 @@ class NoiseGeneratorNode : public Node { public: NoiseGeneratorNode(); - NODE_DEFAULT_DESTRUCTOR(NoiseGeneratorNode) - - virtual Node *copy() const override; + NODE_DEFAULT_FUNCTIONS(NoiseGeneratorNode) virtual QString Name() const override; virtual QString id() const override; @@ -41,7 +39,7 @@ class NoiseGeneratorNode : public Node { virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const QString &shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; virtual void Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const override; static const QString kBaseIn; diff --git a/app/node/generator/polygon/polygon.cpp b/app/node/generator/polygon/polygon.cpp index 40283689b..9677799af 100644 --- a/app/node/generator/polygon/polygon.cpp +++ b/app/node/generator/polygon/polygon.cpp @@ -61,11 +61,6 @@ PolygonGenerator::PolygonGenerator() poly_gizmo_ = new PathGizmo(this); } -Node *PolygonGenerator::copy() const -{ - return new PolygonGenerator(); -} - QString PolygonGenerator::Name() const { return tr("Polygon"); @@ -94,14 +89,21 @@ void PolygonGenerator::Retranslate() SetInputName(kColorInput, tr("Color")); } -void PolygonGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +GenerateJob PolygonGenerator::GetGenerateJob(const NodeValueRow &value) const { GenerateJob job; - job.InsertValue(value); + job.Insert(value); job.SetRequestedFormat(VideoParams::kFormatFloat32); job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); + return job; +} + +void PolygonGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + GenerateJob job = GetGenerateJob(value); + PushMergableJob(value, QVariant::fromValue(job), table); } @@ -114,7 +116,7 @@ void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) con QImage img(frame->width(), frame->height(), QImage::Format_Grayscale8); img.fill(Qt::transparent); - QVector points = job.GetValue(kPointsInput).data().value< QVector >(); + QVector points = job.Get(kPointsInput).value< QVector >(); QPainterPath path = GeneratePath(points); @@ -128,7 +130,7 @@ void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) con p.drawPath(path); // Transplant alpha channel to frame - Color rgba = job.GetValue(kColorInput).data().value(); + Color rgba = job.Get(kColorInput).toColor(); #if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM) __m128 sse_color = _mm_loadu_ps(rgba.data()); #endif @@ -194,7 +196,7 @@ void PolygonGenerator::UpdateGizmoPositions(const NodeValueRow &row, const NodeG { QPointF half_res(globals.resolution_by_par().x()/2, globals.resolution_by_par().y()/2); - QVector points = row[kPointsInput].data().value< QVector >(); + QVector points = row[kPointsInput].value< QVector >(); int current_pos_sz = gizmo_position_handles_.size(); @@ -221,7 +223,7 @@ void PolygonGenerator::UpdateGizmoPositions(const NodeValueRow &row, const NodeG if (!points.isEmpty()) { for (int i=0; i(); + const Bezier &pt = points.at(i).toBezier(); QPointF main = pt.ToPointF() + half_res; QPointF cp1 = main + pt.ControlPoint1ToPointF(); @@ -265,14 +267,14 @@ QPainterPath PolygonGenerator::GeneratePath(const QVector &points) QPainterPath path; if (!points.isEmpty()) { - const Bezier &first_pt = points.first().data().value(); + const Bezier &first_pt = points.first().toBezier(); path.moveTo(first_pt.ToPointF()); for (int i=1; i(), points.at(i).data().value()); + AddPointToPath(&path, points.at(i-1).toBezier(), points.at(i).toBezier()); } - AddPointToPath(&path, points.last().data().value(), first_pt); + AddPointToPath(&path, points.last().toBezier(), first_pt); } return path; diff --git a/app/node/generator/polygon/polygon.h b/app/node/generator/polygon/polygon.h index 2f2e4e467..7107818df 100644 --- a/app/node/generator/polygon/polygon.h +++ b/app/node/generator/polygon/polygon.h @@ -39,9 +39,7 @@ class PolygonGenerator : public GeneratorWithMerge public: PolygonGenerator(); - NODE_DEFAULT_DESTRUCTOR(PolygonGenerator) - - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(PolygonGenerator) virtual QString Name() const override; virtual QString id() const override; @@ -59,6 +57,9 @@ public: static const QString kPointsInput; static const QString kColorInput; +protected: + GenerateJob GetGenerateJob(const NodeValueRow &value) const; + protected slots: virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; diff --git a/app/node/generator/shape/generatorwithmerge.cpp b/app/node/generator/shape/generatorwithmerge.cpp index 363ba4d12..3e36c9229 100644 --- a/app/node/generator/shape/generatorwithmerge.cpp +++ b/app/node/generator/shape/generatorwithmerge.cpp @@ -42,9 +42,9 @@ void GeneratorWithMerge::Retranslate() SetInputName(kBaseInput, tr("Base")); } -ShaderCode GeneratorWithMerge::GetShaderCode(const QString &shader_id) const +ShaderCode GeneratorWithMerge::GetShaderCode(const ShaderRequest &request) const { - if (shader_id == QStringLiteral("mrg")) { + if (request.id == QStringLiteral("mrg")) { return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/alphaover.frag")); } @@ -53,13 +53,13 @@ ShaderCode GeneratorWithMerge::GetShaderCode(const QString &shader_id) const void GeneratorWithMerge::PushMergableJob(const NodeValueRow &value, const QVariant &job, NodeValueTable *table) const { - if (!value[kBaseInput].data().isNull()) { + if (value[kBaseInput].toTexture()) { // Push as merge node ShaderJob merge; merge.SetShaderID(QStringLiteral("mrg")); - merge.InsertValue(MergeNode::kBaseIn, value[kBaseInput]); - merge.InsertValue(MergeNode::kBlendIn, NodeValue(NodeValue::kTexture, job, this)); + merge.Insert(MergeNode::kBaseIn, value[kBaseInput]); + merge.Insert(MergeNode::kBlendIn, NodeValue(NodeValue::kTexture, job, this)); table->Push(NodeValue::kTexture, QVariant::fromValue(merge), this); } else { diff --git a/app/node/generator/shape/generatorwithmerge.h b/app/node/generator/shape/generatorwithmerge.h index 9345f2d64..de3e3bc04 100644 --- a/app/node/generator/shape/generatorwithmerge.h +++ b/app/node/generator/shape/generatorwithmerge.h @@ -31,11 +31,9 @@ class GeneratorWithMerge : public Node public: GeneratorWithMerge(); - NODE_DEFAULT_DESTRUCTOR(GeneratorWithMerge) - virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const QString &shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; static const QString kBaseInput; diff --git a/app/node/generator/shape/shapenode.cpp b/app/node/generator/shape/shapenode.cpp index 34db8dfcb..b8af51bf9 100644 --- a/app/node/generator/shape/shapenode.cpp +++ b/app/node/generator/shape/shapenode.cpp @@ -61,12 +61,12 @@ void ShapeNode::Retranslate() SetComboBoxStrings(kTypeInput, {tr("Rectangle"), tr("Ellipse")}); } -ShaderCode ShapeNode::GetShaderCode(const QString &shader_id) const +ShaderCode ShapeNode::GetShaderCode(const ShaderRequest &request) const { - if (shader_id == QStringLiteral("shape")) { + if (request.id == QStringLiteral("shape")) { return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/shape.frag"))); } else { - return super::GetShaderCode(shader_id); + return super::GetShaderCode(request); } } @@ -74,8 +74,8 @@ void ShapeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, Nod { ShaderJob job; - job.InsertValue(value); - job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + job.Insert(value); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); job.SetShaderID(QStringLiteral("shape")); diff --git a/app/node/generator/shape/shapenode.h b/app/node/generator/shape/shapenode.h index 2df95bed4..5b69209a7 100644 --- a/app/node/generator/shape/shapenode.h +++ b/app/node/generator/shape/shapenode.h @@ -36,8 +36,7 @@ public: kEllipse }; - NODE_DEFAULT_DESTRUCTOR(ShapeNode) - NODE_COPY_FUNCTION(ShapeNode) + NODE_DEFAULT_FUNCTIONS(ShapeNode) virtual QString Name() const override; virtual QString id() const override; @@ -46,7 +45,7 @@ public: virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const QString& shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; static QString kTypeInput; diff --git a/app/node/generator/shape/shapenodebase.cpp b/app/node/generator/shape/shapenodebase.cpp index 4eb411079..77a25f5e3 100644 --- a/app/node/generator/shape/shapenodebase.cpp +++ b/app/node/generator/shape/shapenodebase.cpp @@ -79,8 +79,8 @@ void ShapeNodeBase::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlob QVector2D center_pt = globals.resolution() * 0.5; SetInputProperty(kPositionInput, QStringLiteral("offset"), center_pt); - QVector2D pos = row[kPositionInput].data().value(); - QVector2D sz = row[kSizeInput].data().value(); + QVector2D pos = row[kPositionInput].toVec2(); + QVector2D sz = row[kSizeInput].toVec2(); QVector2D half_sz = sz * 0.5; double left_pt = pos.x() + center_pt.x() - half_sz.x(); diff --git a/app/node/generator/shape/shapenodebase.h b/app/node/generator/shape/shapenodebase.h index 451c32dea..4d9b01006 100644 --- a/app/node/generator/shape/shapenodebase.h +++ b/app/node/generator/shape/shapenodebase.h @@ -35,8 +35,6 @@ class ShapeNodeBase : public GeneratorWithMerge public: ShapeNodeBase(bool create_color_input = true); - NODE_DEFAULT_DESTRUCTOR(ShapeNodeBase) - virtual void Retranslate() override; virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; diff --git a/app/node/generator/solid/solid.cpp b/app/node/generator/solid/solid.cpp index 713a096a7..237c98fde 100644 --- a/app/node/generator/solid/solid.cpp +++ b/app/node/generator/solid/solid.cpp @@ -34,11 +34,6 @@ SolidGenerator::SolidGenerator() AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(1.0f, 0.0f, 0.0f, 1.0f))); } -Node *SolidGenerator::copy() const -{ - return new SolidGenerator(); -} - QString SolidGenerator::Name() const { return tr("Solid"); @@ -69,13 +64,13 @@ void SolidGenerator::Retranslate() void SolidGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { ShaderJob job; - job.InsertValue(value); + job.Insert(value); table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } -ShaderCode SolidGenerator::GetShaderCode(const QString &shader_id) const +ShaderCode SolidGenerator::GetShaderCode(const ShaderRequest &request) const { - Q_UNUSED(shader_id) + Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/solid.frag")); } diff --git a/app/node/generator/solid/solid.h b/app/node/generator/solid/solid.h index 9e60090bc..4b40a7fc4 100644 --- a/app/node/generator/solid/solid.h +++ b/app/node/generator/solid/solid.h @@ -31,9 +31,7 @@ class SolidGenerator : public Node public: SolidGenerator(); - NODE_DEFAULT_DESTRUCTOR(SolidGenerator) - - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(SolidGenerator) virtual QString Name() const override; virtual QString id() const override; @@ -43,7 +41,7 @@ public: virtual void Retranslate() override; virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - virtual ShaderCode GetShaderCode(const QString &shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; static const QString kColorInput; diff --git a/app/node/generator/text/textv1.cpp b/app/node/generator/text/textv1.cpp index 9a850d4d4..b70aaa05b 100644 --- a/app/node/generator/text/textv1.cpp +++ b/app/node/generator/text/textv1.cpp @@ -53,6 +53,8 @@ TextGeneratorV1::TextGeneratorV1() AddInput(kFontInput, NodeValue::kFont); AddInput(kFontSizeInput, NodeValue::kFloat, 72.0f); + + SetFlags(kDontShowInCreateMenu); } QString TextGeneratorV1::Name() const @@ -91,10 +93,10 @@ void TextGeneratorV1::Retranslate() void TextGeneratorV1::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { GenerateJob job; - job.InsertValue(value); + job.Insert(value); job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); - if (!job.GetValue(kTextInput).data().toString().isEmpty()) { + if (!job.Get(kTextInput).toString().isEmpty()) { table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } } @@ -112,15 +114,15 @@ void TextGeneratorV1::GenerateFrame(FramePtr frame, const GenerateJob& job) cons // Set default font QFont default_font; - default_font.setFamily(job.GetValue(kFontInput).data().toString()); - default_font.setPointSizeF(job.GetValue(kFontSizeInput).data().toFloat()); + default_font.setFamily(job.Get(kFontInput).toString()); + default_font.setPointSizeF(job.Get(kFontSizeInput).toDouble()); text_doc.setDefaultFont(default_font); // Center by default text_doc.setDefaultTextOption(QTextOption(Qt::AlignCenter)); - QString html = job.GetValue(kTextInput).data().toString(); - if (job.GetValue(kHtmlInput).data().toBool()) { + QString html = job.Get(kTextInput).toString(); + if (job.Get(kHtmlInput).toBool()) { html.replace('\n', QStringLiteral("
")); text_doc.setHtml(html); } else { @@ -138,7 +140,7 @@ void TextGeneratorV1::GenerateFrame(FramePtr frame, const GenerateJob& job) cons // Push 10% inwards to compensate for title safe area p.translate(tenth_of_width, 0); - TextVerticalAlign valign = static_cast(job.GetValue(kVAlignInput).data().toInt()); + TextVerticalAlign valign = static_cast(job.Get(kVAlignInput).toInt()); int doc_height = text_doc.size().height(); switch (valign) { @@ -161,7 +163,7 @@ void TextGeneratorV1::GenerateFrame(FramePtr frame, const GenerateJob& job) cons text_doc.documentLayout()->draw(&p, ctx); // Transplant alpha channel to frame - Color rgb = job.GetValue(kColorInput).data().value(); + Color rgb = job.Get(kColorInput).toColor(); for (int x=0; xwidth(); x++) { for (int y=0; yheight(); y++) { uchar src_alpha = img.bits()[img.bytesPerLine() * y + x]; diff --git a/app/node/generator/text/textv1.h b/app/node/generator/text/textv1.h index 868e22449..85e4f24cf 100644 --- a/app/node/generator/text/textv1.h +++ b/app/node/generator/text/textv1.h @@ -31,8 +31,7 @@ class TextGeneratorV1 : public Node public: TextGeneratorV1(); - NODE_DEFAULT_DESTRUCTOR(TextGeneratorV1) - NODE_COPY_FUNCTION(TextGeneratorV1) + NODE_DEFAULT_FUNCTIONS(TextGeneratorV1) virtual QString Name() const override; virtual QString id() const override; diff --git a/app/node/generator/text/textv2.cpp b/app/node/generator/text/textv2.cpp index 12eb94b06..2d091c4c9 100644 --- a/app/node/generator/text/textv2.cpp +++ b/app/node/generator/text/textv2.cpp @@ -57,6 +57,8 @@ TextGeneratorV2::TextGeneratorV2() SetStandardValue(kColorInput, QVariant::fromValue(Color(1.0f, 1.0f, 1.0))); SetStandardValue(kSizeInput, QVector2D(400, 300)); + + SetFlags(kDontShowInCreateMenu); } QString TextGeneratorV2::Name() const @@ -94,11 +96,11 @@ void TextGeneratorV2::Retranslate() void TextGeneratorV2::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { GenerateJob job; - job.InsertValue(value); + job.Insert(value); job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); job.SetRequestedFormat(VideoParams::kFormatFloat32); - if (!job.GetValue(kTextInput).data().toString().isEmpty()) { + if (!job.Get(kTextInput).toString().isEmpty()) { table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } } @@ -122,19 +124,19 @@ void TextGeneratorV2::GenerateFrame(FramePtr frame, const GenerateJob& job) cons // Set default font QFont default_font; - default_font.setFamily(job.GetValue(kFontInput).data().toString()); - default_font.setPointSizeF(job.GetValue(kFontSizeInput).data().toFloat()); + default_font.setFamily(job.Get(kFontInput).toString()); + default_font.setPointSizeF(job.Get(kFontSizeInput).toDouble()); text_doc.setDefaultFont(default_font); - QString html = job.GetValue(kTextInput).data().toString(); - if (job.GetValue(kHtmlInput).data().toBool()) { + QString html = job.Get(kTextInput).toString(); + if (job.Get(kHtmlInput).toBool()) { html.replace('\n', QStringLiteral("
")); text_doc.setHtml(html); } else { text_doc.setPlainText(html); } - QVector2D size = job.GetValue(kSizeInput).data().value(); + QVector2D size = job.Get(kSizeInput).toVec2(); text_doc.setTextWidth(size.x()); // Draw rich text onto image @@ -142,12 +144,12 @@ void TextGeneratorV2::GenerateFrame(FramePtr frame, const GenerateJob& job) cons p.scale(1.0 / frame->video_params().divider(), 1.0 / frame->video_params().divider()); - QVector2D pos = job.GetValue(kPositionInput).data().value(); + QVector2D pos = job.Get(kPositionInput).toVec2(); p.translate(pos.x() - size.x()/2, pos.y() - size.y()/2); p.translate(frame->video_params().width()/2, frame->video_params().height()/2); p.setClipRect(0, 0, size.x(), size.y()); - TextVerticalAlign valign = static_cast(job.GetValue(kVAlignInput).data().toInt()); + TextVerticalAlign valign = static_cast(job.Get(kVAlignInput).toInt()); int doc_height = text_doc.size().height(); switch (valign) { @@ -169,7 +171,7 @@ void TextGeneratorV2::GenerateFrame(FramePtr frame, const GenerateJob& job) cons text_doc.documentLayout()->draw(&p, ctx); // Transplant alpha channel to frame - Color rgba = job.GetValue(kColorInput).data().value(); + Color rgba = job.Get(kColorInput).toColor(); #if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM) __m128 sse_color = _mm_loadu_ps(rgba.data()); #endif diff --git a/app/node/generator/text/textv2.h b/app/node/generator/text/textv2.h index d9fb17386..d98443dfc 100644 --- a/app/node/generator/text/textv2.h +++ b/app/node/generator/text/textv2.h @@ -31,8 +31,7 @@ class TextGeneratorV2 : public ShapeNodeBase public: TextGeneratorV2(); - NODE_DEFAULT_DESTRUCTOR(TextGeneratorV2) - NODE_COPY_FUNCTION(TextGeneratorV2) + NODE_DEFAULT_FUNCTIONS(TextGeneratorV2) virtual QString Name() const override; virtual QString id() const override; diff --git a/app/node/generator/text/textv3.cpp b/app/node/generator/text/textv3.cpp index 4413d6f65..40628ec46 100644 --- a/app/node/generator/text/textv3.cpp +++ b/app/node/generator/text/textv3.cpp @@ -81,16 +81,16 @@ void TextGeneratorV3::Retranslate() void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { GenerateJob job; - job.InsertValue(value); + job.Insert(value); job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); job.SetRequestedFormat(VideoParams::kFormatUnsigned8); // FIXME: Provide user override for this job.SetColorspace(project()->color_manager()->GetDefaultInputColorSpace()); - if (!job.GetValue(kTextInput).data().toString().isEmpty()) { + if (!job.Get(kTextInput).toString().isEmpty()) { PushMergableJob(value, QVariant::fromValue(job), table); - } else if (!value[kBaseInput].data().isNull()) { + } else if (value[kBaseInput].toTexture()) { table->Push(value[kBaseInput]); } } @@ -108,17 +108,17 @@ void TextGeneratorV3::GenerateFrame(FramePtr frame, const GenerateJob& job) cons QTextDocument text_doc; text_doc.documentLayout()->setPaintDevice(&img); - QString html = job.GetValue(kTextInput).data().toString(); + QString html = job.Get(kTextInput).toString(); Html::HtmlToDoc(&text_doc, html); - QVector2D size = job.GetValue(kSizeInput).data().value(); + QVector2D size = job.Get(kSizeInput).toVec2(); text_doc.setTextWidth(size.x()); // Draw rich text onto image QPainter p(&img); p.scale(1.0 / frame->video_params().divider(), 1.0 / frame->video_params().divider()); - QVector2D pos = job.GetValue(kPositionInput).data().value(); + QVector2D pos = job.Get(kPositionInput).toVec2(); p.translate(pos.x() - size.x()/2, pos.y() - size.y()/2); p.translate(frame->video_params().width()/2, frame->video_params().height()/2); p.setClipRect(0, 0, size.x(), size.y()); @@ -136,7 +136,7 @@ void TextGeneratorV3::UpdateGizmoPositions(const NodeValueRow &row, const NodeGl QRectF rect = poly_gizmo()->GetPolygon().boundingRect(); text_gizmo_->SetRect(rect); - text_gizmo_->SetHtml(row[kTextInput].data().toString()); + text_gizmo_->SetHtml(row[kTextInput].toString()); } } diff --git a/app/node/generator/text/textv3.h b/app/node/generator/text/textv3.h index 1fd9237c4..f2cd2d47c 100644 --- a/app/node/generator/text/textv3.h +++ b/app/node/generator/text/textv3.h @@ -32,8 +32,7 @@ class TextGeneratorV3 : public ShapeNodeBase public: TextGeneratorV3(); - NODE_DEFAULT_DESTRUCTOR(TextGeneratorV3) - NODE_COPY_FUNCTION(TextGeneratorV3) + NODE_DEFAULT_FUNCTIONS(TextGeneratorV3) virtual QString Name() const override; virtual QString id() const override; diff --git a/app/node/gizmo/gizmo.cpp b/app/node/gizmo/gizmo.cpp index f885a62e9..f69451869 100644 --- a/app/node/gizmo/gizmo.cpp +++ b/app/node/gizmo/gizmo.cpp @@ -22,7 +22,8 @@ namespace olive { -NodeGizmo::NodeGizmo(QObject *parent) +NodeGizmo::NodeGizmo(QObject *parent) : + visible_(true) { setParent(parent); } diff --git a/app/node/gizmo/gizmo.h b/app/node/gizmo/gizmo.h index 5a3e585d2..07c519886 100644 --- a/app/node/gizmo/gizmo.h +++ b/app/node/gizmo/gizmo.h @@ -39,11 +39,16 @@ public: const NodeGlobals &GetGlobals() const { return globals_; } void SetGlobals(const NodeGlobals &globals) { globals_ = globals; } + bool IsVisible() const { return visible_; } + void SetVisible(bool e) { visible_ = e; } + signals: private: NodeGlobals globals_; + bool visible_; + }; } diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp index a3089478d..9c7fe1c6b 100644 --- a/app/node/group/group.cpp +++ b/app/node/group/group.cpp @@ -29,6 +29,7 @@ namespace olive { NodeGroup::NodeGroup() : output_passthrough_(nullptr) { + SetFlags(kDontShowInCreateMenu); } QString NodeGroup::Name() const diff --git a/app/node/group/group.h b/app/node/group/group.h index fe2151b26..d906c987e 100644 --- a/app/node/group/group.h +++ b/app/node/group/group.h @@ -31,8 +31,7 @@ class NodeGroup : public Node public: NodeGroup(); - NODE_DEFAULT_DESTRUCTOR(NodeGroup) - NODE_COPY_FUNCTION(NodeGroup) + NODE_DEFAULT_FUNCTIONS(NodeGroup) virtual QString Name() const override; virtual QString id() const override; diff --git a/app/node/hashtraverser.cpp b/app/node/hashtraverser.cpp index 78edf681b..28948cef8 100644 --- a/app/node/hashtraverser.cpp +++ b/app/node/hashtraverser.cpp @@ -72,14 +72,8 @@ void HashTraverser::ProcessVideoFootage(TexturePtr destination, const FootageJob texture_ids_.insert(destination.get(), hash_.result()); } -void HashTraverser::ProcessAudioFootage(SampleBufferPtr destination, const FootageJob &stream, const TimeRange &input_time) +void HashTraverser::ProcessAudioFootage(SampleBuffer &destination, const FootageJob &stream, const TimeRange &input_time) { - Hash(FileFunctions::GetUniqueFileIdentifier(stream.filename())); - Hash(stream.loop_mode()); - Hash(stream.audio_params().stream_index()); - Hash(input_time); - - texture_ids_.insert(destination.get(), hash_.result()); } void HashTraverser::ProcessShader(TexturePtr destination, const Node *node, const TimeRange &range, const ShaderJob &job) @@ -98,11 +92,16 @@ void HashTraverser::ProcessShader(TexturePtr destination, const Node *node, cons texture_ids_.insert(destination.get(), hash_.result()); } -void HashTraverser::ProcessSamples(SampleBufferPtr destination, const Node *node, const TimeRange &range, const SampleJob &job) +void HashTraverser::ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob &job) { + Hash(job.GetColorProcessor()->id()); texture_ids_.insert(destination.get(), hash_.result()); } +void HashTraverser::ProcessSamples(SampleBuffer &destination, const Node *node, const TimeRange &range, const SampleJob &job) +{ +} + void HashTraverser::ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob &job) { HashGenerateJob(node, &job); @@ -138,11 +137,8 @@ void HashTraverser::HashNodeValue(const NodeValue &value) if (value_type == NodeValue::kSamples || value_type == NodeValue::kTexture) { QByteArray id_for_buffer; if (value_type == NodeValue::kTexture) { - TexturePtr texture = value.data().value(); + TexturePtr texture = value.toTexture(); id_for_buffer = texture_ids_.value(texture.get()); - } else { - SampleBufferPtr samples = value.data().value(); - id_for_buffer = texture_ids_.value(samples.get()); } if (!id_for_buffer.isEmpty()) { diff --git a/app/node/hashtraverser.h b/app/node/hashtraverser.h index 707d89b9f..bdf0a70f4 100644 --- a/app/node/hashtraverser.h +++ b/app/node/hashtraverser.h @@ -35,11 +35,13 @@ public: protected: virtual void ProcessVideoFootage(TexturePtr destination, const FootageJob &stream, const rational &input_time) override; - virtual void ProcessAudioFootage(SampleBufferPtr destination, const FootageJob &stream, const TimeRange &input_time) override; + virtual void ProcessAudioFootage(SampleBuffer &destination, const FootageJob &stream, const TimeRange &input_time) override; virtual void ProcessShader(TexturePtr destination, const Node *node, const TimeRange &range, const ShaderJob& job) override; - virtual void ProcessSamples(SampleBufferPtr destination, const Node *node, const TimeRange &range, const SampleJob &job) override; + virtual void ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob& job) override; + + virtual void ProcessSamples(SampleBuffer &destination, const Node *node, const TimeRange &range, const SampleJob &job) override; virtual void ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob& job) override; diff --git a/app/node/input/time/timeinput.cpp b/app/node/input/time/timeinput.cpp index 96f80f385..b9e46c27a 100644 --- a/app/node/input/time/timeinput.cpp +++ b/app/node/input/time/timeinput.cpp @@ -28,11 +28,6 @@ TimeInput::TimeInput() { } -Node *TimeInput::copy() const -{ - return new TimeInput(); -} - QString TimeInput::Name() const { return tr("Time"); diff --git a/app/node/input/time/timeinput.h b/app/node/input/time/timeinput.h index c5afc75f4..c892e351c 100644 --- a/app/node/input/time/timeinput.h +++ b/app/node/input/time/timeinput.h @@ -27,13 +27,11 @@ namespace olive { class TimeInput : public Node { - Q_OBJECT + Q_OBJECT public: TimeInput(); - NODE_DEFAULT_DESTRUCTOR(TimeInput) - - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(TimeInput) virtual QString Name() const override; virtual QString id() const override; diff --git a/app/node/input/value/valuenode.h b/app/node/input/value/valuenode.h index 2d6d2ada8..8d2569786 100644 --- a/app/node/input/value/valuenode.h +++ b/app/node/input/value/valuenode.h @@ -31,12 +31,7 @@ class ValueNode : public Node public: ValueNode(); - NODE_DEFAULT_DESTRUCTOR(ValueNode) - - virtual Node* copy() const override - { - return new ValueNode(); - } + NODE_DEFAULT_FUNCTIONS(ValueNode) virtual QString Name() const override { diff --git a/app/node/keying/CMakeLists.txt b/app/node/keying/CMakeLists.txt index 2177a550a..4dbd437cd 100644 --- a/app/node/keying/CMakeLists.txt +++ b/app/node/keying/CMakeLists.txt @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +add_subdirectory(chromakey) add_subdirectory(colordifferencekey) add_subdirectory(despill) diff --git a/app/node/keying/chromakey/CMakeLists.txt b/app/node/keying/chromakey/CMakeLists.txt new file mode 100644 index 000000000..c2af2bba4 --- /dev/null +++ b/app/node/keying/chromakey/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2021 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/keying/chromakey/chromakey.h + node/keying/chromakey/chromakey.cpp + PARENT_SCOPE +) \ No newline at end of file diff --git a/app/node/keying/chromakey/chromakey.cpp b/app/node/keying/chromakey/chromakey.cpp new file mode 100644 index 000000000..4d7701476 --- /dev/null +++ b/app/node/keying/chromakey/chromakey.cpp @@ -0,0 +1,150 @@ +/*** + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +***/ + +#include "chromakey.h" + +#include "node/color/colormanager/colormanager.h" +#include "render/colorprocessor.h" + +namespace olive { + +#define super OCIOBaseNode + +const QString ChromaKeyNode::kColorInput = QStringLiteral("color_key"); +const QString ChromaKeyNode::kMaskOnlyInput = QStringLiteral("mask_only_in"); +const QString ChromaKeyNode::kUpperToleranceInput = QStringLiteral("upper_tolerence_in"); +const QString ChromaKeyNode::kLowerToleranceInput = QStringLiteral("lower_tolerence_in"); +const QString ChromaKeyNode::kGarbageMatteInput = QStringLiteral("garbage_in"); +const QString ChromaKeyNode::kCoreMatteInput = QStringLiteral("core_in"); +const QString ChromaKeyNode::kShadowsInput = QStringLiteral("shadows_in"); +const QString ChromaKeyNode::kHighlightsInput = QStringLiteral("highlights_in"); + +ChromaKeyNode::ChromaKeyNode() +{ + AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(0.0f, 1.0f, 0.0f, 1.0f))); + + AddInput(kLowerToleranceInput, NodeValue::kFloat, 5.0); + SetInputProperty(kLowerToleranceInput, QStringLiteral("min"), 0.0); + SetInputProperty(kLowerToleranceInput, QStringLiteral("base"), 0.1); + + AddInput(kUpperToleranceInput, NodeValue::kFloat, 25.0); + SetInputProperty(kUpperToleranceInput, QStringLiteral("base"), 0.1); + + // FIXME: Temporarily disabled. This will break if "lower tolerance" is keyframed or connected to + // something and there's currently no solution to remedy that. If there is in the future, + // we can look into re-enabling this. + //SetInputProperty(kUpperToleranceInput, QStringLiteral("min"), GetStandardValue(kLowerToleranceInput).toDouble()); + + AddInput(kGarbageMatteInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + + AddInput(kCoreMatteInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + + AddInput(kHighlightsInput, NodeValue::kFloat, 100.0f); + SetInputProperty(kHighlightsInput, QStringLiteral("min"), 0.0); + SetInputProperty(kHighlightsInput, QStringLiteral("base"), 0.1); + + AddInput(kShadowsInput, NodeValue::kFloat, 100.0f); + SetInputProperty(kShadowsInput, QStringLiteral("min"), 0.0); + SetInputProperty(kShadowsInput, QStringLiteral("base"), 0.1); + + AddInput(kMaskOnlyInput, NodeValue::kBoolean, false); +} + +QString ChromaKeyNode::Name() const +{ + return tr("Chroma Key"); +} + +QString ChromaKeyNode::id() const +{ + return QStringLiteral("org.olivevideoeditor.Olive.chromakey"); +} + +QVector ChromaKeyNode::Category() const +{ + return {kCategoryKeying}; +} + +QString ChromaKeyNode::Description() const +{ + return tr("A simple color key based on the distance from the chroma of a selected color."); +} + +void ChromaKeyNode::Retranslate() +{ + super::Retranslate(); + SetInputName(kTextureInput, tr("Input")); + SetInputName(kGarbageMatteInput, tr("Garbage Matte")); + SetInputName(kCoreMatteInput, tr("Core Matte")); + SetInputName(kColorInput, tr("Key Color")); + SetInputName(kShadowsInput, tr("Shadows")); + SetInputName(kHighlightsInput, tr("Highlights")); + SetInputName(kUpperToleranceInput, tr("Upper Tolerance")); + SetInputName(kLowerToleranceInput, tr("Lower Tolerance")); + SetInputName(kMaskOnlyInput, tr("Show Mask Only")); +} + +void ChromaKeyNode::InputValueChangedEvent(const QString &input, int element) +{ + Q_UNUSED(element); + if (input == kLowerToleranceInput) { + // FIXME: Temporarily disabled. This will break if "lower tolerance" is keyframed or connected to + // something and there's currently no solution to remedy that. If there is in the future, + // we can look into re-enabling this. + //SetInputProperty(kUpperToleranceInput, QStringLiteral("min"), GetStandardValue(kLowerToleranceInput).toDouble()); + } + + GenerateProcessor(); +} + +ShaderCode ChromaKeyNode::GetShaderCode(const ShaderRequest &request) const +{ + return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/chromakey.frag")).arg(request.stub)); +} + +void ChromaKeyNode::GenerateProcessor() +{ + if (manager()){ + try { + ColorTransform transform("cie_xyz_d65_interchange"); + set_processor(ColorProcessor::Create(manager(), manager()->GetReferenceColorSpace(), transform)); + } catch (const OCIO::Exception &e) { + std::cerr << std::endl << e.what() << std::endl; + } + } +} + +void ChromaKeyNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + if (value[kTextureInput].toTexture() && processor()) { + ColorTransformJob job; + + job.Insert(value); + job.SetAlphaChannelRequired(ColorTransformJob::kAlphaForceOn); + job.SetColorProcessor(processor()); + job.SetInputTexture(value[kTextureInput].toTexture()); + job.SetNeedsCustomShader(this); + job.SetFunctionName(QStringLiteral("SceneLinearToCIEXYZ_d65")); + + table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + } +} + +void ChromaKeyNode::ConfigChanged() +{ + GenerateProcessor(); +} + +} // namespace olive diff --git a/app/node/keying/chromakey/chromakey.h b/app/node/keying/chromakey/chromakey.h new file mode 100644 index 000000000..653d31c87 --- /dev/null +++ b/app/node/keying/chromakey/chromakey.h @@ -0,0 +1,62 @@ +/*** + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +***/ + +#ifndef CHROMAKEYNODE_H +#define CHROMAKEYNODE_H + +#include "node/color/ociobase/ociobase.h" + +namespace olive { + +class ChromaKeyNode : public OCIOBaseNode { + Q_OBJECT + public: + ChromaKeyNode(); + + NODE_DEFAULT_FUNCTIONS(ChromaKeyNode) + + virtual QString Name() const override; + virtual QString id() const override; + virtual QVector Category() const override; + virtual QString Description() const override; + + virtual void Retranslate() override; + + virtual void InputValueChangedEvent(const QString& input, int element) override; + + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; + virtual void Value(const NodeValueRow& value, const NodeGlobals& globals, NodeValueTable* table) const override; + + virtual void ConfigChanged() override; + + static const QString kColorInput; + static const QString kMaskOnlyInput; + static const QString kUpperToleranceInput; + static const QString kLowerToleranceInput; + static const QString kGarbageMatteInput; + static const QString kCoreMatteInput; + static const QString kShadowsInput; + static const QString kHighlightsInput; + +private: + void GenerateProcessor(); + + + +}; + +} // namespace olive + +#endif // CHROMAKEYNODE_H diff --git a/app/node/keying/colordifferencekey/colordifferencekey.cpp b/app/node/keying/colordifferencekey/colordifferencekey.cpp index 39595e039..e4629816b 100644 --- a/app/node/keying/colordifferencekey/colordifferencekey.cpp +++ b/app/node/keying/colordifferencekey/colordifferencekey.cpp @@ -37,11 +37,13 @@ ColorDifferenceKeyNode::ColorDifferenceKeyNode() AddInput(kColorInput, NodeValue::kCombo, 0); - AddInput(kHighlightsInput, NodeValue::kFloat, 100.0f); + AddInput(kHighlightsInput, NodeValue::kFloat, 1.0f); SetInputProperty(kHighlightsInput, QStringLiteral("min"), 0.0); + SetInputProperty(kHighlightsInput, QStringLiteral("base"), 0.01); - AddInput(kShadowsInput, NodeValue::kFloat, 100.0f); + AddInput(kShadowsInput, NodeValue::kFloat, 1.0f); SetInputProperty(kShadowsInput, QStringLiteral("min"), 0.0); + SetInputProperty(kShadowsInput, QStringLiteral("base"), 0.01); AddInput(kMaskOnlyInput, NodeValue::kBoolean, false); @@ -49,11 +51,6 @@ ColorDifferenceKeyNode::ColorDifferenceKeyNode() SetEffectInput(kTextureInput); } -Node *ColorDifferenceKeyNode::copy() const -{ - return new ColorDifferenceKeyNode(); -} - QString ColorDifferenceKeyNode::Name() const { return tr("Color Difference Key"); @@ -88,20 +85,20 @@ void ColorDifferenceKeyNode::Retranslate() SetInputName(kMaskOnlyInput, tr("Show Mask Only")); } -ShaderCode ColorDifferenceKeyNode::GetShaderCode(const QString &shader_id) const +ShaderCode ColorDifferenceKeyNode::GetShaderCode(const ShaderRequest &request) const { - Q_UNUSED(shader_id) + Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/colordifferencekey.frag")); } void ColorDifferenceKeyNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { ShaderJob job; - job.InsertValue(value); + job.Insert(value); job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); // If there's no texture, no need to run an operation - if (!job.GetValue(kTextureInput).data().isNull()) { + if (job.Get(kTextureInput).toTexture()) { table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } } diff --git a/app/node/keying/colordifferencekey/colordifferencekey.h b/app/node/keying/colordifferencekey/colordifferencekey.h index 01ddaf718..31cb106a6 100644 --- a/app/node/keying/colordifferencekey/colordifferencekey.h +++ b/app/node/keying/colordifferencekey/colordifferencekey.h @@ -24,7 +24,7 @@ class ColorDifferenceKeyNode : public Node { public: ColorDifferenceKeyNode(); - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(ColorDifferenceKeyNode) virtual QString Name() const override; virtual QString id() const override; @@ -33,7 +33,7 @@ class ColorDifferenceKeyNode : public Node { virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const QString& shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; virtual void Value(const NodeValueRow& value, const NodeGlobals& globals, NodeValueTable* table) const override; static const QString kTextureInput; diff --git a/app/node/keying/despill/despill.cpp b/app/node/keying/despill/despill.cpp index e72124f31..3f8e22723 100644 --- a/app/node/keying/despill/despill.cpp +++ b/app/node/keying/despill/despill.cpp @@ -40,11 +40,6 @@ DespillNode::DespillNode() SetEffectInput(kTextureInput); } -Node* DespillNode::copy() const -{ - return new DespillNode(); -} - QString DespillNode::Name() const { return tr("Despill"); @@ -80,23 +75,23 @@ void DespillNode::Retranslate() SetInputName(kPreserveLuminanceInput, tr("Preserve Luminance")); } -ShaderCode DespillNode::GetShaderCode(const QString& shader_id) const { - Q_UNUSED(shader_id) +ShaderCode DespillNode::GetShaderCode(const ShaderRequest &request) const { + Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/despill.frag")); } void DespillNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { ShaderJob job; - job.InsertValue(value); + job.Insert(value); // Set luma coefficients double luma_coeffs[3] = {0.0f, 0.0f, 0.0f}; project()->color_manager()->GetDefaultLumaCoefs(luma_coeffs); - job.InsertValue(QStringLiteral("luma_coeffs"), + job.Insert(QStringLiteral("luma_coeffs"), NodeValue(NodeValue::kVec3, QVector3D(luma_coeffs[0], luma_coeffs[1], luma_coeffs[2]))); // If there's no texture, no need to run an operation - if (!job.GetValue(kTextureInput).data().isNull()) { + if (job.Get(kTextureInput).toTexture()) { table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } } diff --git a/app/node/keying/despill/despill.h b/app/node/keying/despill/despill.h index c5aaed49d..936402427 100644 --- a/app/node/keying/despill/despill.h +++ b/app/node/keying/despill/despill.h @@ -25,7 +25,7 @@ class DespillNode : public Node { public: DespillNode(); - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(DespillNode) virtual QString Name() const override; virtual QString id() const override; @@ -34,15 +34,15 @@ class DespillNode : public Node { virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const QString& shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; virtual void Value(const NodeValueRow& value, const NodeGlobals& globals, NodeValueTable* table) const override; - + static const QString kTextureInput; static const QString kColorInput; static const QString kMethodInput; static const QString kPreserveLuminanceInput; - - + + }; } // namespace olive diff --git a/app/node/math/math/math.cpp b/app/node/math/math/math.cpp index 932a963d1..6bade4174 100644 --- a/app/node/math/math/math.cpp +++ b/app/node/math/math/math.cpp @@ -42,11 +42,6 @@ MathNode::MathNode() SetInputProperty(kParamBIn, QStringLiteral("autotrim"), true); } -Node *MathNode::copy() const -{ - return new MathNode(); -} - QString MathNode::Name() const { return tr("Math"); @@ -85,9 +80,9 @@ void MathNode::Retranslate() SetComboBoxStrings(kMethodIn, operations); } -ShaderCode MathNode::GetShaderCode(const QString &shader_id) const +ShaderCode MathNode::GetShaderCode(const ShaderRequest &request) const { - return GetShaderCodeInternal(shader_id, kParamAIn, kParamBIn); + return GetShaderCodeInternal(request.id, kParamAIn, kParamBIn); } void MathNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const @@ -114,7 +109,7 @@ void MathNode::Value(const NodeValueRow &value, const NodeGlobals &globals, Node table); } -void MathNode::ProcessSamples(const NodeValueRow &values, const SampleBufferPtr input, SampleBufferPtr output, int index) const +void MathNode::ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const { return ProcessSamplesInternal(values, GetOperation(), kParamAIn, kParamBIn, input, output, index); } diff --git a/app/node/math/math/math.h b/app/node/math/math/math.h index b87b09a19..65a63720e 100644 --- a/app/node/math/math/math.h +++ b/app/node/math/math/math.h @@ -31,9 +31,7 @@ class MathNode : public MathNodeBase public: MathNode(); - NODE_DEFAULT_DESTRUCTOR(MathNode) - - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(MathNode) virtual QString Name() const override; virtual QString id() const override; @@ -42,7 +40,7 @@ public: virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const QString &shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; Operation GetOperation() const { @@ -56,7 +54,7 @@ public: virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; - virtual void ProcessSamples(const NodeValueRow &values, const SampleBufferPtr input, SampleBufferPtr output, int index) const override; + virtual void ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const override; static const QString kMethodIn; static const QString kParamAIn; diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp index 51ef80060..d07658d94 100644 --- a/app/node/math/math/mathbase.cpp +++ b/app/node/math/math/mathbase.cpp @@ -141,12 +141,12 @@ QVector4D MathNodeBase::RetrieveVector(const NodeValue &val) // QVariant doesn't know that QVector*D can convert themselves so we do it here switch (val.type()) { case NodeValue::kVec2: - return val.data().value(); + return val.toVec2(); case NodeValue::kVec3: - return val.data().value(); + return val.toVec3(); case NodeValue::kVec4: default: - return val.data().value(); + return val.toVec4(); } } @@ -225,7 +225,7 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt if (val_a.type() == NodeValue::kRational && val_b.type() == NodeValue::kRational && operation != kOpPower) { // Preserve rationals output->Push(NodeValue::kRational, - QVariant::fromValue(PerformAddSubMultDiv(operation, val_a.data().value(), val_b.data().value())), + QVariant::fromValue(PerformAddSubMultDiv(operation, val_a.toRational(), val_b.toRational())), this); } else { output->Push(NodeValue::kFloat, @@ -247,7 +247,7 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt case kPairMatrixVec: { - QMatrix4x4 matrix = (val_a.type() == NodeValue::kMatrix) ? val_a.data().value() : val_b.data().value(); + QMatrix4x4 matrix = (val_a.type() == NodeValue::kMatrix) ? val_a.toMatrix() : val_b.toMatrix(); QVector4D vec = (val_a.type() == NodeValue::kMatrix) ? RetrieveVector(val_b) : RetrieveVector(val_a); // Only valid operation is multiply @@ -269,16 +269,16 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt case kPairMatrixMatrix: { - QMatrix4x4 mat_a = val_a.data().value(); - QMatrix4x4 mat_b = val_b.data().value(); + QMatrix4x4 mat_a = val_a.toMatrix(); + QMatrix4x4 mat_b = val_b.toMatrix(); output->Push(NodeValue::kMatrix, PerformAddSubMult(operation, mat_a, mat_b), this); break; } case kPairColorColor: { - Color col_a = val_a.data().value(); - Color col_b = val_b.data().value(); + Color col_a = val_a.toColor(); + Color col_b = val_b.toColor(); // Only add and subtract are valid operations output->Push(NodeValue::kColor, QVariant::fromValue(PerformAddSub(operation, col_a, col_b)), this); @@ -288,8 +288,8 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt case kPairNumberColor: { - Color col = (val_a.type() == NodeValue::kColor) ? val_a.data().value() : val_b.data().value(); - float num = (val_a.type() == NodeValue::kColor) ? val_b.data().toFloat() : val_a.data().toFloat(); + Color col = (val_a.type() == NodeValue::kColor) ? val_a.toColor() : val_b.toColor(); + float num = (val_a.type() == NodeValue::kColor) ? val_b.toDouble() : val_a.toDouble(); // Only multiply and divide are valid operations output->Push(NodeValue::kColor, QVariant::fromValue(PerformMult(operation, col, num)), this); @@ -298,18 +298,18 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt case kPairSampleSample: { - SampleBufferPtr samples_a = val_a.data().value(); - SampleBufferPtr samples_b = val_b.data().value(); + SampleBuffer samples_a = val_a.toSamples(); + SampleBuffer samples_b = val_b.toSamples(); - int max_samples = qMax(samples_a->sample_count(), samples_b->sample_count()); - int min_samples = qMin(samples_a->sample_count(), samples_b->sample_count()); + int max_samples = qMax(samples_a.sample_count(), samples_b.sample_count()); + int min_samples = qMin(samples_a.sample_count(), samples_b.sample_count()); - SampleBufferPtr mixed_samples = SampleBuffer::CreateAllocated(samples_a->audio_params(), max_samples); + SampleBuffer mixed_samples = SampleBuffer(samples_a.audio_params(), max_samples); - for (int i=0;iaudio_params().channel_count();i++) { + for (int i=0;idata(i)[j] = PerformAll(operation, samples_a->data(i)[j], samples_b->data(i)[j]); + mixed_samples.data(i)[j] = PerformAll(operation, samples_a.data(i)[j], samples_b.data(i)[j]); } } @@ -317,11 +317,11 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt // Fill in remainder space with 0s int remainder = max_samples - min_samples; - SampleBufferPtr larger_buffer = (max_samples == samples_a->sample_count()) ? samples_a : samples_b; + const SampleBuffer &larger_buffer = (max_samples == samples_a.sample_count()) ? samples_a : samples_b; - for (int i=0;iaudio_params().channel_count();i++) { - memcpy(&mixed_samples->data(i)[min_samples], - &larger_buffer->data(i)[min_samples], + for (int i=0;i(); + TexturePtr texture = texture_val.toTexture(); if (!texture) { operation_is_noop = true; @@ -361,7 +361,7 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt const QVector2D &sequence_res = globals.resolution(); QVector2D texture_res(texture->params().width() * texture->pixel_aspect_ratio().toDouble(), texture->params().height()); - QMatrix4x4 adjusted_matrix = TransformDistortNode::AdjustMatrixByResolutions(number_val.data().value(), + QMatrix4x4 adjusted_matrix = TransformDistortNode::AdjustMatrixByResolutions(number_val.toMatrix(), sequence_res, texture->params().offset(), texture_res); @@ -370,7 +370,7 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt operation_is_noop = true; } else { // Replace with adjusted matrix - job.InsertValue(val_a.type() == NodeValue::kTexture ? param_b_in : param_a_in, + job.Insert(val_a.type() == NodeValue::kTexture ? param_b_in : param_a_in, NodeValue(NodeValue::kMatrix, adjusted_matrix, this)); // It's likely an alpha channel will result from this operation @@ -396,28 +396,27 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt float number = RetrieveNumber(number_val); - SampleJob job(val_a.type() == NodeValue::kSamples ? val_a : val_b); - job.InsertValue(number_param, NodeValue(NodeValue::kFloat, number, this)); + SampleBuffer buffer = val_a.type() == NodeValue::kSamples ? val_a.toSamples() : val_b.toSamples(); - if (job.HasSamples()) { + if (buffer.is_allocated()) { if (IsInputStatic(number_param)) { if (!NumberIsNoOp(operation, number)) { - for (int i=0;iaudio_params().channel_count();i++) { + for (int i=0;idata(i), number, 0, job.samples()->sample_count()); + PerformAllOnFloatBufferSSE(operation, buffer.data(i), number, 0, buffer.sample_count()); #else - PerformAllOnFloatBuffer(operation, job.samples()->data(i), number, 0, job.samples()->sample_count()); + PerformAllOnFloatBuffer(operation, buffer.data(i), number, 0, buffer.sample_count()); #endif } } - output->Push(NodeValue::kSamples, QVariant::fromValue(job.samples()), this); + output->Push(NodeValue::kSamples, QVariant::fromValue(buffer), this); } else { + SampleJob job(val_a.type() == NodeValue::kSamples ? val_a : val_b); + job.Insert(number_param, NodeValue(NodeValue::kFloat, number, this)); output->Push(NodeValue::kSamples, QVariant::fromValue(job), this); } - } else { - output->Push(NodeValue::kSamples, QVariant::fromValue(job), this); } break; } @@ -428,7 +427,7 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt } } -void MathNodeBase::ProcessSamplesInternal(const NodeValueRow &values, MathNodeBase::Operation operation, const QString ¶m_a_in, const QString ¶m_b_in, const SampleBufferPtr input, SampleBufferPtr output, int index) const +void MathNodeBase::ProcessSamplesInternal(const NodeValueRow &values, MathNodeBase::Operation operation, const QString ¶m_a_in, const QString ¶m_b_in, const olive::SampleBuffer &input, olive::SampleBuffer &output, int index) const { // This function is only used for sample+number pairing NodeValue number_val = values[param_a_in]; @@ -443,17 +442,17 @@ void MathNodeBase::ProcessSamplesInternal(const NodeValueRow &values, MathNodeBa float number_flt = RetrieveNumber(number_val); - for (int i=0;iaudio_params().channel_count();i++) { - output->data(i)[index] = PerformAll(operation, input->data(i)[index], number_flt); + for (int i=0;i(operation, input.data(i)[index], number_flt); } } float MathNodeBase::RetrieveNumber(const NodeValue &val) { if (val.type() == NodeValue::kRational) { - return val.data().value().toDouble(); + return val.toRational().toDouble(); } else { - return val.data().toFloat(); + return val.toDouble(); } } diff --git a/app/node/math/math/mathbase.h b/app/node/math/math/mathbase.h index 6391c10d3..80cb792a7 100644 --- a/app/node/math/math/mathbase.h +++ b/app/node/math/math/mathbase.h @@ -30,8 +30,6 @@ class MathNodeBase : public Node public: MathNodeBase() = default; - NODE_DEFAULT_DESTRUCTOR(MathNodeBase) - enum Operation { kOpAdd, kOpSubtract, @@ -123,7 +121,7 @@ protected: void ValueInternal(Operation operation, Pairing pairing, const QString& param_a_in, const NodeValue &val_a, const QString& param_b_in, const NodeValue& val_b, const NodeGlobals &globals, NodeValueTable *output) const; - void ProcessSamplesInternal(const NodeValueRow &values, Operation operation, const QString& param_a_in, const QString& param_b_in, const SampleBufferPtr input, SampleBufferPtr output, int index) const; + void ProcessSamplesInternal(const NodeValueRow &values, Operation operation, const QString& param_a_in, const QString& param_b_in, const SampleBuffer &input, SampleBuffer &output, int index) const; }; diff --git a/app/node/math/merge/merge.cpp b/app/node/math/merge/merge.cpp index 1f1c9be42..4d758f175 100644 --- a/app/node/math/merge/merge.cpp +++ b/app/node/math/merge/merge.cpp @@ -38,11 +38,6 @@ MergeNode::MergeNode() SetFlags(kDontShowInParamView); } -Node *MergeNode::copy() const -{ - return new MergeNode(); -} - QString MergeNode::Name() const { return tr("Merge"); @@ -72,9 +67,9 @@ void MergeNode::Retranslate() SetInputName(kBlendIn, tr("Blend")); } -ShaderCode MergeNode::GetShaderCode(const QString &shader_id) const +ShaderCode MergeNode::GetShaderCode(const ShaderRequest &request) const { - Q_UNUSED(shader_id) + Q_UNUSED(request) return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/alphaover.frag")); } @@ -82,18 +77,18 @@ ShaderCode MergeNode::GetShaderCode(const QString &shader_id) const void MergeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { ShaderJob job; - job.InsertValue(value); + job.Insert(value); - TexturePtr base_tex = job.GetValue(kBaseIn).data().value(); - TexturePtr blend_tex = job.GetValue(kBlendIn).data().value(); + TexturePtr base_tex = job.Get(kBaseIn).toTexture(); + TexturePtr blend_tex = job.Get(kBlendIn).toTexture(); if (base_tex || blend_tex) { if (!base_tex || (blend_tex && blend_tex->channel_count() < VideoParams::kRGBAChannelCount)) { // We only have a blend texture or the blend texture is RGB only, no need to alpha over - table->Push(job.GetValue(kBlendIn)); + table->Push(job.Get(kBlendIn)); } else if (!blend_tex) { // We only have a base texture, no need to alpha over - table->Push(job.GetValue(kBaseIn)); + table->Push(job.Get(kBaseIn)); } else { // We have both textures, push the job if (base_tex->channel_count() < VideoParams::kRGBAChannelCount) { @@ -113,8 +108,8 @@ void MergeNode::Hash(QCryptographicHash &hash, const NodeGlobals &globals, const NodeValueDatabase db = traverser.GenerateDatabase(this, globals.time()); - TexturePtr base_tex = db[kBaseIn].Get(NodeValue::kTexture).value(); - TexturePtr blend_tex = db[kBlendIn].Get(NodeValue::kTexture).value(); + TexturePtr base_tex = db[kBaseIn].Get(NodeValue::kTexture).toTexture(); + TexturePtr blend_tex = db[kBlendIn].Get(NodeValue::kTexture).toTexture(); if (base_tex || blend_tex) { bool passthrough_base = !blend_tex; diff --git a/app/node/math/merge/merge.h b/app/node/math/merge/merge.h index fff8bbd5a..814b2a93b 100644 --- a/app/node/math/merge/merge.h +++ b/app/node/math/merge/merge.h @@ -31,9 +31,7 @@ class MergeNode : public Node public: MergeNode(); - NODE_DEFAULT_DESTRUCTOR(MergeNode) - - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(MergeNode) virtual QString Name() const override; virtual QString id() const override; @@ -42,7 +40,7 @@ public: virtual void Retranslate() override; - virtual ShaderCode GetShaderCode(const QString &shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; static const QString kBaseIn; diff --git a/app/node/math/trigonometry/trigonometry.cpp b/app/node/math/trigonometry/trigonometry.cpp index 5c6e16663..426604ca1 100644 --- a/app/node/math/trigonometry/trigonometry.cpp +++ b/app/node/math/trigonometry/trigonometry.cpp @@ -34,11 +34,6 @@ TrigonometryNode::TrigonometryNode() AddInput(kXIn, NodeValue::kFloat, 0.0); } -olive::Node *olive::TrigonometryNode::copy() const -{ - return new TrigonometryNode(); -} - QString TrigonometryNode::Name() const { return tr("Trigonometry"); @@ -84,7 +79,7 @@ void TrigonometryNode::Retranslate() void TrigonometryNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - double x = value[kXIn].data().toFloat(); + double x = value[kXIn].toDouble(); switch (static_cast(GetStandardValue(kMethodIn).toInt())) { case kOpSine: diff --git a/app/node/math/trigonometry/trigonometry.h b/app/node/math/trigonometry/trigonometry.h index 1f1058f6b..a78f9ea59 100644 --- a/app/node/math/trigonometry/trigonometry.h +++ b/app/node/math/trigonometry/trigonometry.h @@ -31,9 +31,7 @@ class TrigonometryNode : public Node public: TrigonometryNode(); - NODE_DEFAULT_DESTRUCTOR(TrigonometryNode) - - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(TrigonometryNode) virtual QString Name() const override; virtual QString id() const override; diff --git a/app/node/node.cpp b/app/node/node.cpp index 4a0c60b03..e6c542f8d 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -50,8 +50,7 @@ Node::Node() : folder_(nullptr), operation_stack_(0), cache_result_(false), - flags_(kNone), - effect_element_(-1) + flags_(kNone) { AddInput(kEnabledInput, NodeValue::kBoolean, true); } @@ -148,7 +147,7 @@ Color Node::color() const if (override_color_ >= 0) { c = override_color_; } else { - c = Config::Current()[QStringLiteral("CatColor%1").arg(this->Category().first())].toInt(); + c = OLIVE_CONFIG_STR(QStringLiteral("CatColor%1").arg(this->Category().first())).toInt(); } return ColorCoding::GetColor(c); @@ -171,7 +170,7 @@ QLinearGradient Node::gradient_color(qreal top, qreal bottom) const QBrush Node::brush(qreal top, qreal bottom) const { - if (Config::Current()[QStringLiteral("UseGradients")].toBool()) { + if (OLIVE_CONFIG("UseGradients").toBool()) { return gradient_color(top, bottom); } else { return color().toQColor(); @@ -918,6 +917,7 @@ void Node::SetInputFlags(const QString &input, const InputFlags &f) if (i) { i->flags = f; + emit InputFlagsChanged(input, i->flags); } else { ReportInvalidInput("set flags of", input); } @@ -1103,7 +1103,7 @@ Node *Node::CopyNodeInGraph(Node *node, MultiUndoCommand *command) { Node* copy; - if (Config::Current()[QStringLiteral("SplitClipsCopyNodes")].toBool()) { + if (OLIVE_CONFIG("SplitClipsCopyNodes").toBool()) { copy = Node::CopyNodeAndDependencyGraphMinusItems(node, command); } else { copy = node->copy(); @@ -1520,14 +1520,12 @@ QVector Node::GetImmediateDependencies() const return GetDependenciesInternal(false, false); } -ShaderCode Node::GetShaderCode(const QString &shader_id) const +ShaderCode Node::GetShaderCode(const ShaderRequest &request) const { - Q_UNUSED(shader_id) - return ShaderCode(QString(), QString()); } -void Node::ProcessSamples(const NodeValueRow &, const SampleBufferPtr, SampleBufferPtr, int) const +void Node::ProcessSamples(const NodeValueRow &, const SampleBuffer &, SampleBuffer &, int) const { } diff --git a/app/node/node.h b/app/node/node.h index 98fcd8e27..61af71fd7 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -48,6 +48,10 @@ namespace olive { +#define NODE_DEFAULT_FUNCTIONS(x) \ + NODE_DEFAULT_DESTRUCTOR(x) \ + NODE_COPY_FUNCTION(x) + #define NODE_DEFAULT_DESTRUCTOR(x) \ virtual ~x() override {DisconnectAll();} @@ -97,7 +101,13 @@ public: kNone = 0, kDontShowInParamView = 0x1, kVideoEffect = 0x2, - kAudioEffect = 0x4 + kAudioEffect = 0x4, + kDontShowInCreateMenu = 0x8 + }; + + struct ContextPair { + Node *node; + Node *context; }; Node(); @@ -543,7 +553,12 @@ public: NodeInput GetEffectInput() { - return effect_input_.isEmpty() ? NodeInput() : NodeInput(this, effect_input_, effect_element_); + return effect_input_.isEmpty() ? NodeInput() : NodeInput(this, effect_input_); + } + + const QString &GetEffectInputID() const + { + return effect_input_; } class ValueHint { @@ -651,15 +666,32 @@ public: */ QVector GetImmediateDependencies() const; + struct ShaderRequest + { + ShaderRequest(const QString &shader_id) + { + id = shader_id; + } + + ShaderRequest(const QString &shader_id, const QString &shader_stub) + { + id = shader_id; + stub = shader_stub; + } + + QString id; + QString stub; + }; + /** * @brief Generate hardware accelerated code for this Node */ - virtual ShaderCode GetShaderCode(const QString& shader_id) const; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const; /** * @brief If Value() pushes a ShaderJob, this is the function that will process them. */ - virtual void ProcessSamples(const NodeValueRow &values, const SampleBufferPtr input, SampleBufferPtr output, int index) const; + virtual void ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const; /** * @brief If Value() pushes a GenerateJob, override this function for the image to create @@ -1038,10 +1070,9 @@ protected: virtual void childEvent(QChildEvent *event) override; - void SetEffectInput(const QString &input, int element = -1) + void SetEffectInput(const QString &input) { effect_input_ = input; - effect_element_ = element; } void SetToolTip(const QString& s) @@ -1138,6 +1169,8 @@ signals: void NodeRemovedFromContext(Node *node); + void InputFlagsChanged(const QString &input, const InputFlags &flags); + private: class ArrayInsertCommand : public UndoCommand { @@ -1360,7 +1393,6 @@ private: QVector gizmos_; QString effect_input_; - int effect_element_; private slots: /** diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index f4244804b..ce34c2240 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -66,11 +66,6 @@ const Track::Type& Track::type() const return track_type_; } -Node *Track::copy() const -{ - return new Track(); -} - QString Track::Name() const { if (track_type_ == Track::kVideo) { diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 72a1b6a78..237a70f15 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -45,13 +45,11 @@ public: Track(); - NODE_DEFAULT_DESTRUCTOR(Track) + NODE_DEFAULT_FUNCTIONS(Track) const Track::Type& type() const; void set_type(const Track::Type& track_type); - virtual Node* copy() const override; - virtual QString Name() const override; virtual QString id() const override; virtual QVector Category() const override; diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 3c01d17e2..b1d9db487 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -72,11 +72,6 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream timeline_points_ = new TimelinePoints(this); } -Node *ViewerOutput::copy() const -{ - return new ViewerOutput(); -} - QString ViewerOutput::Name() const { return tr("Viewer"); @@ -188,26 +183,26 @@ AudioParams ViewerOutput::GetFirstEnabledAudioStream() const void ViewerOutput::set_default_parameters() { - int width = Config::Current()["DefaultSequenceWidth"].toInt(); - int height = Config::Current()["DefaultSequenceHeight"].toInt(); + int width = OLIVE_CONFIG("DefaultSequenceWidth").toInt(); + int height = OLIVE_CONFIG("DefaultSequenceHeight").toInt(); SetVideoParams(VideoParams( width, height, - Config::Current()["DefaultSequenceFrameRate"].value(), - static_cast(Config::Current()["OfflinePixelFormat"].toInt()), + OLIVE_CONFIG("DefaultSequenceFrameRate").value(), + static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()), VideoParams::kInternalChannelCount, - Config::Current()["DefaultSequencePixelAspect"].value(), - Config::Current()["DefaultSequenceInterlacing"].value(), + OLIVE_CONFIG("DefaultSequencePixelAspect").value(), + OLIVE_CONFIG("DefaultSequenceInterlacing").value(), VideoParams::generate_auto_divider(width, height) )); SetAudioParams(AudioParams( - Config::Current()["DefaultSequenceAudioFrequency"].toInt(), - Config::Current()["DefaultSequenceAudioLayout"].toULongLong(), + OLIVE_CONFIG("DefaultSequenceAudioFrequency").toInt(), + OLIVE_CONFIG("DefaultSequenceAudioLayout").toULongLong(), AudioParams::kInternalFormat )); - SetVideoAutoCacheEnabled(Config::Current()["DefaultSequenceAutoCache"].toBool()); + SetVideoAutoCacheEnabled(OLIVE_CONFIG("DefaultSequenceAutoCache").toBool()); } void ViewerOutput::ShiftVideoCache(const rational &from, const rational &to) @@ -499,7 +494,7 @@ void ViewerOutput::set_parameters_from_footage(const QVector foo SetVideoParams(VideoParams(s.width(), s.height(), using_timebase, - static_cast(Config::Current()[QStringLiteral("OfflinePixelFormat")].toInt()), + static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()), VideoParams::kInternalChannelCount, s.pixel_aspect_ratio(), s.interlacing(), diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index ee207c6ec..2edf2d344 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -46,9 +46,7 @@ class ViewerOutput : public Node public: ViewerOutput(bool create_buffer_inputs = true, bool create_default_streams = true); - NODE_DEFAULT_DESTRUCTOR(ViewerOutput) - - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(ViewerOutput) virtual QString Name() const override; virtual QString id() const override; diff --git a/app/node/param.h b/app/node/param.h index da2e12320..4eb000191 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -65,6 +65,19 @@ public: return *this; } + InputFlags operator|(const InputFlag &f) const + { + InputFlags i = *this; + i |= f; + return i; + } + + InputFlags &operator|=(const InputFlag &f) + { + f_ |= f; + return *this; + } + InputFlags operator&(const InputFlags &f) const { InputFlags i = *this; diff --git a/app/node/project/folder/folder.h b/app/node/project/folder/folder.h index 12677e57b..7c779719d 100644 --- a/app/node/project/folder/folder.h +++ b/app/node/project/folder/folder.h @@ -37,12 +37,7 @@ class Folder : public Node public: Folder(); - NODE_DEFAULT_DESTRUCTOR(Folder) - - virtual Node* copy() const override - { - return new Folder(); - } + NODE_DEFAULT_FUNCTIONS(Folder) virtual QString Name() const override { diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index d3c82304f..825470782 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -118,12 +118,7 @@ void Footage::InputValueChangedEvent(const QString &input, int element) decoder_ = footage_info.decoder(); for (int i=0; i(value[kLoopModeInput].data().toInt()); + LoopMode loop_mode = static_cast(value[kLoopModeInput].toInt()); // If the file exists and the reference is valid, push a footage job to the renderer if (QFileInfo(file).exists()) { diff --git a/app/node/project/footage/footage.h b/app/node/project/footage/footage.h index aed465106..77202dbe8 100644 --- a/app/node/project/footage/footage.h +++ b/app/node/project/footage/footage.h @@ -55,12 +55,7 @@ public: */ Footage(const QString& filename = QString()); - NODE_DEFAULT_DESTRUCTOR(Footage) - - virtual Node* copy() const override - { - return new Footage(); - } + NODE_DEFAULT_FUNCTIONS(Footage) virtual QString Name() const override { diff --git a/app/node/project/projectsettings/projectsettings.h b/app/node/project/projectsettings/projectsettings.h index ce1890847..35b9d578c 100644 --- a/app/node/project/projectsettings/projectsettings.h +++ b/app/node/project/projectsettings/projectsettings.h @@ -31,7 +31,7 @@ class ProjectSettingsNode : public Node public: ProjectSettingsNode(); - NODE_DEFAULT_DESTRUCTOR(ProjectSettingsNode) + NODE_DEFAULT_FUNCTIONS(ProjectSettingsNode) virtual QString Name() const override { @@ -53,11 +53,6 @@ public: return tr("Settings used throughout the project."); } - virtual Node* copy() const override - { - return new ProjectSettingsNode(); - } - enum CacheSetting { kCacheUseDefaultLocation, kCacheStoreAlongsideProject, diff --git a/app/node/project/sequence/sequence.cpp b/app/node/project/sequence/sequence.cpp index 0cb443188..8829089dd 100644 --- a/app/node/project/sequence/sequence.cpp +++ b/app/node/project/sequence/sequence.cpp @@ -118,6 +118,15 @@ void Sequence::Retranslate() } } +void Sequence::InvalidateCache(const TimeRange &range, const QString &from, int element, InvalidateCacheOptions options) +{ + if (from == kTrackInputFormat.arg(Track::kSubtitle)) { + emit SubtitlesChanged(range); + } + + super::InvalidateCache(range, from, element, options); +} + rational Sequence::VerifyLengthInternal(Track::Type type) const { if (!track_lists_.isEmpty()) { diff --git a/app/node/project/sequence/sequence.h b/app/node/project/sequence/sequence.h index 62900b031..d07a89c4f 100644 --- a/app/node/project/sequence/sequence.h +++ b/app/node/project/sequence/sequence.h @@ -36,12 +36,7 @@ class Sequence : public ViewerOutput public: Sequence(); - NODE_DEFAULT_DESTRUCTOR(Sequence) - - virtual Node* copy() const override - { - return new Sequence(); - } + NODE_DEFAULT_FUNCTIONS(Sequence) virtual QString Name() const override { @@ -89,6 +84,8 @@ public: virtual void Retranslate() override; + virtual void InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) override; + static const QString kTrackInputFormat; virtual bool IsItem() const override @@ -107,6 +104,8 @@ signals: void TrackAdded(Track* track); void TrackRemoved(Track* track); + void SubtitlesChanged(const TimeRange &range); + private: QVector track_lists_; diff --git a/app/node/project/serializer/serializer210528.cpp b/app/node/project/serializer/serializer210528.cpp index baf53e442..47700b240 100644 --- a/app/node/project/serializer/serializer210528.cpp +++ b/app/node/project/serializer/serializer210528.cpp @@ -605,7 +605,7 @@ void ProjectSerializer210528::LoadMarkerList(QXmlStreamReader *reader, TimelineM } } - new TimelineMarker(Config::Current()[QStringLiteral("MarkerColor")].toInt(), TimeRange(in, out), name, markers); + new TimelineMarker(OLIVE_CONFIG("MarkerColor").toInt(), TimeRange(in, out), name, markers); } reader->skipCurrentElement(); diff --git a/app/node/project/serializer/serializer210907.cpp b/app/node/project/serializer/serializer210907.cpp index 96b69f214..b65678a04 100644 --- a/app/node/project/serializer/serializer210907.cpp +++ b/app/node/project/serializer/serializer210907.cpp @@ -597,7 +597,7 @@ void ProjectSerializer210907::LoadMarkerList(QXmlStreamReader *reader, TimelineM } } - new TimelineMarker(Config::Current()[QStringLiteral("MarkerColor")].toInt(), TimeRange(in, out), name, markers); + new TimelineMarker(OLIVE_CONFIG("MarkerColor").toInt(), TimeRange(in, out), name, markers); } reader->skipCurrentElement(); diff --git a/app/node/project/serializer/serializer211228.cpp b/app/node/project/serializer/serializer211228.cpp index 139fd2438..70ae3b494 100644 --- a/app/node/project/serializer/serializer211228.cpp +++ b/app/node/project/serializer/serializer211228.cpp @@ -647,7 +647,7 @@ void ProjectSerializer211228::LoadMarkerList(QXmlStreamReader *reader, TimelineM } } - new TimelineMarker(Config::Current()[QStringLiteral("MarkerColor")].toInt(), TimeRange(in, out), name, markers); + new TimelineMarker(OLIVE_CONFIG("MarkerColor").toInt(), TimeRange(in, out), name, markers); } reader->skipCurrentElement(); diff --git a/app/node/time/timeoffset/timeoffsetnode.h b/app/node/time/timeoffset/timeoffsetnode.h index b44a1f1e0..961492245 100644 --- a/app/node/time/timeoffset/timeoffsetnode.h +++ b/app/node/time/timeoffset/timeoffsetnode.h @@ -30,8 +30,7 @@ class TimeOffsetNode : public Node public: TimeOffsetNode(); - NODE_DEFAULT_DESTRUCTOR(TimeOffsetNode) - NODE_COPY_FUNCTION(TimeOffsetNode) + NODE_DEFAULT_FUNCTIONS(TimeOffsetNode) virtual QString Name() const override { diff --git a/app/node/time/timeremap/timeremap.cpp b/app/node/time/timeremap/timeremap.cpp index ff2b1ff77..e903ad8eb 100644 --- a/app/node/time/timeremap/timeremap.cpp +++ b/app/node/time/timeremap/timeremap.cpp @@ -39,11 +39,6 @@ TimeRemapNode::TimeRemapNode() AddInput(kInputInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable)); } -Node *TimeRemapNode::copy() const -{ - return new TimeRemapNode(); -} - QString TimeRemapNode::Name() const { return tr("Time Remap"); diff --git a/app/node/time/timeremap/timeremap.h b/app/node/time/timeremap/timeremap.h index 6cdaf618a..a9a8cdb53 100644 --- a/app/node/time/timeremap/timeremap.h +++ b/app/node/time/timeremap/timeremap.h @@ -31,9 +31,7 @@ class TimeRemapNode : public Node public: TimeRemapNode(); - NODE_DEFAULT_DESTRUCTOR(TimeRemapNode) - - virtual Node* copy() const override; + NODE_DEFAULT_FUNCTIONS(TimeRemapNode) virtual QString Name() const override; virtual QString id() const override; diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index bb914bb03..2139bae4d 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -71,7 +71,7 @@ NodeValue NodeTraverser::GenerateRowValue(const Node *node, const QString &input if (value.array()) { // Resolve each element of array - QVector tables = value.data().value >(); + QVector tables = value.value >(); QVector output(tables.size()); for (int i=0; i(); - if (tex) { + if (TexturePtr tex = it.value().toTexture()) { max_channel_count = qMax(max_channel_count, tex->channel_count()); } } @@ -269,7 +268,15 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const Node::ValueHint return table; } else { - return database.Merge(); + // If this node has an effect input, ensure that is pushed last + NodeValueTable primary; + if (!n->GetEffectInputID().isEmpty()) { + primary = database.Take(n->GetEffectInputID()); + } + + NodeValueTable m = database.Merge(); + m.Push(primary); + return m; } } @@ -287,6 +294,7 @@ NodeValueTable NodeTraverser::GenerateBlockTable(const Track *track, const TimeR return table; } + QVector2D NodeTraverser::GenerateResolution() const { return QVector2D(video_params_.square_pixel_width(), video_params_.height()); @@ -295,11 +303,9 @@ QVector2D NodeTraverser::GenerateResolution() const void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range) { if (val.type() == NodeValue::kTexture || val.type() == NodeValue::kSamples) { - const QVariant &v = val.data(); + if (val.canConvert()) { - if (v.canConvert()) { - - ShaderJob job = v.value(); + ShaderJob job = val.value(); VideoParams tex_params = GetCacheVideoParams(); tex_params.set_channel_count(GetChannelCountFromJob(job)); @@ -309,11 +315,11 @@ void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range) PreProcessRow(range, job.GetValues()); ProcessShader(tex, val.source(), range, job); - val.set_data(QVariant::fromValue(tex)); + val.set_value(tex); - } else if (v.canConvert()) { + } else if (val.canConvert()) { - GenerateJob job = v.value(); + GenerateJob job = val.value(); VideoParams tex_params = GetCacheVideoParams(); tex_params.set_channel_count(GetChannelCountFromJob(job)); @@ -337,11 +343,24 @@ void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range) tex = dest; } - val.set_data(QVariant::fromValue(tex)); + val.set_value(tex); - } else if (v.canConvert()) { + } else if (val.canConvert()) { - FootageJob job = v.value(); + ColorTransformJob job = val.value(); + + VideoParams src_params = job.GetInputTexture()->params(); + src_params.set_channel_count(GetChannelCountFromJob(job)); + + TexturePtr dest = CreateTexture(src_params); + + ProcessColorTransform(dest, val.source(), job); + + val.set_value(dest); + + } else if (val.canConvert()) { + + FootageJob job = val.value(); if (job.type() == Track::kVideo) { @@ -349,6 +368,23 @@ void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range) TexturePtr tex; + // Adjust footage job's divider + VideoParams render_params = GetCacheVideoParams(); + VideoParams job_params = job.video_params(); + + // HACK/FIXME: Override old cached probe data that contains an invalid divider. Might be + // good in the future to version the probe data so we can automatically + // ignore older stuff. + job_params.set_divider(render_params.divider()); + + // See if we can make this divider larger (i.e. if the footage is smaller) + while (job_params.divider() > 1 + && VideoParams::GetScaledDimension(job_params.width(), job_params.divider()-1) < render_params.effective_width() + && VideoParams::GetScaledDimension(job_params.height(), job_params.divider()-1) < render_params.effective_height()) { + job_params.set_divider(job_params.divider() - 1); + } + job.set_video_params(job_params); + if (footage_time.isNaN()) { // Push dummy texture tex = CreateDummyTexture(job.video_params()); @@ -360,22 +396,22 @@ void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range) ProcessVideoFootage(tex, job, footage_time); } - val.set_data(QVariant::fromValue(tex)); + val.set_value(tex); } else if (job.type() == Track::kAudio) { - SampleBufferPtr buffer = CreateSampleBuffer(GetCacheAudioParams(), range.length()); + SampleBuffer buffer = CreateSampleBuffer(GetCacheAudioParams(), range.length()); ProcessAudioFootage(buffer, job, range); - val.set_data(QVariant::fromValue(buffer)); + val.set_value(buffer); } - } else if (v.canConvert()) { + } else if (val.canConvert()) { - SampleJob job = v.value(); - SampleBufferPtr output_buffer = CreateSampleBuffer(job.samples()->audio_params(), job.samples()->sample_count()); + SampleJob job = val.value(); + SampleBuffer output_buffer = CreateSampleBuffer(job.samples().audio_params(), job.samples().sample_count()); ProcessSamples(output_buffer, val.source(), range, job); - val.set_data(QVariant::fromValue(output_buffer)); + val.set_value(QVariant::fromValue(output_buffer)); } diff --git a/app/node/traverser.h b/app/node/traverser.h index 01a57ce29..090599673 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -27,6 +27,7 @@ #include "common/cancelableobject.h" #include "node/output/track/track.h" #include "render/job/footagejob.h" +#include "render/job/colortransformjob.h" #include "value.h" namespace olive { @@ -84,11 +85,13 @@ protected: virtual void ProcessVideoFootage(TexturePtr destination, const FootageJob &stream, const rational &input_time){} - virtual void ProcessAudioFootage(SampleBufferPtr destination, const FootageJob &stream, const TimeRange &input_time){} + virtual void ProcessAudioFootage(SampleBuffer &destination, const FootageJob &stream, const TimeRange &input_time){} virtual void ProcessShader(TexturePtr destination, const Node *node, const TimeRange &range, const ShaderJob& job){} - virtual void ProcessSamples(SampleBufferPtr destination, const Node *node, const TimeRange &range, const SampleJob &job){} + virtual void ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob& job){} + + virtual void ProcessSamples(SampleBuffer &destination, const Node *node, const TimeRange &range, const SampleJob &job){} virtual void ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob& job){} @@ -99,15 +102,19 @@ protected: return CreateDummyTexture(p); } - virtual SampleBufferPtr CreateSampleBuffer(const AudioParams ¶ms, int sample_count) + virtual SampleBuffer CreateSampleBuffer(const AudioParams ¶ms, int sample_count) { // Return dummy by default - return SampleBuffer::Create(); + return SampleBuffer(); } - SampleBufferPtr CreateSampleBuffer(const AudioParams ¶ms, const rational &length) + SampleBuffer CreateSampleBuffer(const AudioParams ¶ms, const rational &length) { - return CreateSampleBuffer(params, params.time_to_samples(length)); + if (params.is_valid()) { + return CreateSampleBuffer(params, params.time_to_samples(length)); + } else { + return SampleBuffer(); + } } virtual bool CanCacheFrames() diff --git a/app/node/value.cpp b/app/node/value.cpp index cdafe2151..5b1355451 100644 --- a/app/node/value.cpp +++ b/app/node/value.cpp @@ -34,10 +34,6 @@ namespace olive { -const QVector NodeValue::kNumber = {kFloat, kInt, kRational}; -const QVector NodeValue::kBuffer = {kTexture, kSamples}; -const QVector NodeValue::kVector = {kVec2, kVec3, kVec4}; - QString NodeValue::ValueToString(Type data_type, const QVariant &value, bool value_is_a_key_track) { if (!value_is_a_key_track && data_type == kVec2) { @@ -418,7 +414,7 @@ NodeValue::Type NodeValue::GetDataTypeFromName(const QString &n) return NodeValue::kNone; } -NodeValue NodeValueTable::GetWithMeta(const QVector &type, const QString &tag) const +NodeValue NodeValueTable::Get(const QVector &type, const QString &tag) const { int value_index = GetValueIndex(type, tag); @@ -429,7 +425,7 @@ NodeValue NodeValueTable::GetWithMeta(const QVector &type, cons return NodeValue(); } -NodeValue NodeValueTable::TakeWithMeta(const QVector &type, const QString &tag) +NodeValue NodeValueTable::Take(const QVector &type, const QString &tag) { int value_index = GetValueIndex(type, tag); diff --git a/app/node/value.h b/app/node/value.h index d894a7d35..49f977ad0 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -21,10 +21,16 @@ #ifndef NODEVALUE_H #define NODEVALUE_H +#include #include #include #include +#include "codec/samplebuffer.h" +#include "common/bezier.h" +#include "node/splitvalue.h" +#include "render/color.h" +#include "render/texture.h" #include "undo/undocommand.h" namespace olive { @@ -178,10 +184,6 @@ public: kDataTypeCount }; - static const QVector kNumber; - static const QVector kBuffer; - static const QVector kVector; - NodeValue() : type_(kNone), from_(nullptr), @@ -189,13 +191,14 @@ public: { } - NodeValue(Type type, const QVariant& data, const Node* from = nullptr, bool array = false, const QString& tag = QString()) : + template + NodeValue(Type type, const T& data, const Node* from = nullptr, bool array = false, const QString& tag = QString()) : type_(type), - data_(data), from_(from), tag_(tag), array_(array) { + set_value(data); } Type type() const @@ -203,14 +206,22 @@ public: return type_; } - const QVariant& data() const + template + T value() const { - return data_; + return data_.value(); } - void set_data(const QVariant& data) + template + void set_value(const T &v) { - data_ = data; + data_ = QVariant::fromValue(v); + } + + template + bool canConvert() const + { + return data_.canConvert(); } const QString& tag() const @@ -239,6 +250,10 @@ public: static NodeValue::Type GetDataTypeFromName(const QString &n); static QString ValueToString(Type data_type, const QVariant& value, bool value_is_a_key_track); + static QString ValueToString(const NodeValue &v, bool value_is_a_key_track) + { + return ValueToString(v.type_, v.data_, value_is_a_key_track); + } static QVariant StringToValue(Type data_type, const QString &string, bool value_is_a_key_track); @@ -248,13 +263,18 @@ public: static QByteArray ValueToBytes(Type type, const QVariant& value); static QByteArray ValueToBytes(const NodeValue &value) { - return ValueToBytes(value.type(), value.data()); + return ValueToBytes(value.type(), value.data_); } static QVector split_normal_value_into_track_values(Type type, const QVariant &value); static QVariant combine_track_values_into_normal_value(Type type, const QVector& split); + SplitValue to_split_value() const + { + return split_normal_value_into_track_values(type_, data_); + } + /** * @brief Returns whether a data type can be interpolated or not */ @@ -271,18 +291,44 @@ public: static bool type_is_numeric(NodeValue::Type type) { - return kNumber.contains(type); + return type == kFloat + || type == kInt + || type == kRational; } static bool type_is_vector(NodeValue::Type type) { - return kVector.contains(type); + return type == kVec2 + || type == kVec3 + || type == kVec4; + } + + static bool type_is_buffer(NodeValue::Type type) + { + return type == kTexture + || type == kSamples; } static int get_number_of_keyframe_tracks(Type type); static void ValidateVectorString(QStringList* list, int count); + TexturePtr toTexture() const { return value(); } + SampleBuffer toSamples() const { return value(); } + bool toBool() const { return value(); } + double toDouble() const { return value(); } + int64_t toInt() const { return value(); } + rational toRational() const { return value(); } + QString toString() const { return value(); } + Color toColor() const { return value(); } + QMatrix4x4 toMatrix() const { return value(); } + VideoParams toVideoParams() const { return value(); } + AudioParams toAudioParams() const { return value(); } + QVector2D toVec2() const { return value(); } + QVector3D toVec3() const { return value(); } + QVector4D toVec4() const { return value(); } + Bezier toBezier() const { return value(); } + private: Type type_; QVariant data_; @@ -297,50 +343,34 @@ class NodeValueTable public: NodeValueTable() = default; - QVariant Get(NodeValue::Type type, const QString& tag = QString()) const + NodeValue Get(NodeValue::Type type, const QString& tag = QString()) const { QVector types = {type}; return Get(types, tag); } - QVariant Get(const QVector& type, const QString& tag = QString()) const - { - return GetWithMeta(type, tag).data(); - } + NodeValue Get(const QVector& type, const QString& tag = QString()) const; - NodeValue GetWithMeta(NodeValue::Type type, const QString& tag = QString()) const - { - QVector types = {type}; - return GetWithMeta(types, tag); - } - - NodeValue GetWithMeta(const QVector& type, const QString& tag = QString()) const; - - QVariant Take(NodeValue::Type type, const QString& tag = QString()) + NodeValue Take(NodeValue::Type type, const QString& tag = QString()) { QVector types = {type}; return Take(types, tag); } - QVariant Take(const QVector& type, const QString& tag = QString()) - { - return TakeWithMeta(type, tag).data(); - } - - NodeValue TakeWithMeta(NodeValue::Type type, const QString& tag = QString()) - { - QVector types = {type}; - return TakeWithMeta(types, tag); - } - - NodeValue TakeWithMeta(const QVector& type, const QString& tag = QString()); + NodeValue Take(const QVector& type, const QString& tag = QString()); void Push(const NodeValue& value) { values_.append(value); } - void Push(NodeValue::Type type, const QVariant& data, const Node *from, bool array = false, const QString& tag = QString()) + void Push(const NodeValueTable& value) + { + values_.append(value.values_); + } + + template + void Push(NodeValue::Type type, const T& data, const Node *from, bool array = false, const QString& tag = QString()) { Push(NodeValue(type, data, from, array, tag)); } @@ -350,7 +380,8 @@ public: values_.prepend(value); } - void Prepend(NodeValue::Type type, const QVariant& data, const Node *from, bool array = false, const QString& tag = QString()) + template + void Prepend(NodeValue::Type type, const T& data, const Node *from, bool array = false, const QString& tag = QString()) { Prepend(NodeValue(type, data, from, array, tag)); } diff --git a/app/node/valuedatabase.h b/app/node/valuedatabase.h index 725ed1e62..80574b371 100644 --- a/app/node/valuedatabase.h +++ b/app/node/valuedatabase.h @@ -41,6 +41,11 @@ public: tables_.insert(key, value); } + NodeValueTable Take(const QString &key) + { + return tables_.take(key); + } + NodeValueTable Merge() const; using Tables = QHash; diff --git a/app/packaging/macos/MacOSXBundleInfo.plist.in b/app/packaging/macos/MacOSXBundleInfo.plist.in index 286f53ae2..4b8ea9a1a 100644 --- a/app/packaging/macos/MacOSXBundleInfo.plist.in +++ b/app/packaging/macos/MacOSXBundleInfo.plist.in @@ -28,5 +28,7 @@ ${MACOSX_BUNDLE_COPYRIGHT} NSPrincipalClass NSApplication + NSMicrophoneUsageDescription + This app requires microphone access to record audio tracks. diff --git a/app/panel/node/node.cpp b/app/panel/node/node.cpp index 5e694867b..d618c7d80 100644 --- a/app/panel/node/node.cpp +++ b/app/panel/node/node.cpp @@ -28,9 +28,10 @@ NodePanel::NodePanel(QWidget *parent) : node_widget_ = new NodeWidget(); connect(this, &NodePanel::visibilityChanged, node_widget_->view(), &NodeView::CenterOnItemsBoundingRect); - // Connect node view signals to this panel - MAY REMOVE connect(node_widget_->view(), &NodeView::NodesSelected, this, &NodePanel::NodesSelected); connect(node_widget_->view(), &NodeView::NodesDeselected, this, &NodePanel::NodesDeselected); + connect(node_widget_->view(), &NodeView::NodeSelectionChanged, this, &NodePanel::NodeSelectionChanged); + connect(node_widget_->view(), &NodeView::NodeSelectionChangedWithContexts, this, &NodePanel::NodeSelectionChangedWithContexts); connect(node_widget_->view(), &NodeView::NodeGroupOpened, this, &NodePanel::NodeGroupOpened); connect(node_widget_->view(), &NodeView::NodeGroupClosed, this, &NodePanel::NodeGroupClosed); diff --git a/app/panel/node/node.h b/app/panel/node/node.h index b9a5ebf35..c8ebccaaa 100644 --- a/app/panel/node/node.h +++ b/app/panel/node/node.h @@ -105,10 +105,9 @@ public: } public slots: - void Select(const QVector& nodes, bool center_view_on_item) + void Select(const QVector &p) { - node_widget_->view()->Select(nodes, center_view_on_item); - this->raise(); + node_widget_->view()->Select(p, true); } signals: @@ -116,6 +115,9 @@ signals: void NodesDeselected(const QVector& nodes); + void NodeSelectionChanged(const QVector& nodes); + void NodeSelectionChangedWithContexts(const QVector& nodes); + void NodeGroupOpened(NodeGroup *group); void NodeGroupClosed(); diff --git a/app/panel/panelmanager.cpp b/app/panel/panelmanager.cpp index e3959bfbf..edef10377 100644 --- a/app/panel/panelmanager.cpp +++ b/app/panel/panelmanager.cpp @@ -49,7 +49,7 @@ PanelWidget *PanelManager::CurrentlyFocused(bool enable_hover) const { // If hover focus is enabled, find the currently hovered panel and return it (if no panel is hovered, resort to // default behavior) - if (enable_hover && Config::Current()[QStringLiteral("HoverFocus")].toBool()) { + if (enable_hover && OLIVE_CONFIG("HoverFocus").toBool()) { PanelWidget* hovered = CurrentlyHovered(); if (hovered != nullptr) { diff --git a/app/panel/param/param.cpp b/app/panel/param/param.cpp index 8504a86b4..368a6abd2 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -28,24 +28,14 @@ ParamPanel::ParamPanel(QWidget* parent) : TimeBasedPanel(QStringLiteral("ParamPanel"), parent) { NodeParamView* view = new NodeParamView(); - connect(view, &NodeParamView::RequestSelectNode, this, &ParamPanel::RequestSelectNode); connect(view, &NodeParamView::FocusedNodeChanged, this, &ParamPanel::FocusedNodeChanged); + connect(view, &NodeParamView::SelectedNodesChanged, this, &ParamPanel::SelectedNodesChanged); connect(this, &ParamPanel::visibilityChanged, view, &NodeParamView::UpdateElementY); SetTimeBasedWidget(view); Retranslate(); } -void ParamPanel::SelectNodes(const QVector &nodes) -{ - static_cast(GetTimeBasedWidget())->SelectNodes(nodes); -} - -void ParamPanel::DeselectNodes(const QVector &nodes) -{ - static_cast(GetTimeBasedWidget())->DeselectNodes(nodes); -} - void ParamPanel::DeleteSelected() { static_cast(GetTimeBasedWidget())->DeleteSelected(); diff --git a/app/panel/param/param.h b/app/panel/param/param.h index 25b50a2db..9cfb881c0 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -49,8 +49,10 @@ public: } public slots: - void SelectNodes(const QVector& nodes); - void DeselectNodes(const QVector& nodes); + void SetSelectedNodes(const QVector &nodes) + { + GetParamView()->SetSelectedNodes(nodes, false); + } virtual void DeleteSelected() override; @@ -61,10 +63,10 @@ public slots: void SetContexts(const QVector &contexts); signals: - void RequestSelectNode(const QVector& target); - void FocusedNodeChanged(Node* n); + void SelectedNodesChanged(const QVector &nodes); + protected: virtual void Retranslate() override; diff --git a/app/audio/packedprocessor.h b/app/render/alphaassoc.h similarity index 59% rename from app/audio/packedprocessor.h rename to app/render/alphaassoc.h index 8b1bb59c5..22eb74835 100644 --- a/app/audio/packedprocessor.h +++ b/app/render/alphaassoc.h @@ -18,43 +18,17 @@ ***/ -#ifndef PACKEDPROCESSOR_H -#define PACKEDPROCESSOR_H - -extern "C" { -#include -} - -#include "codec/samplebuffer.h" -#include "render/audioparams.h" +#ifndef ALPHAASSOC_H +#define ALPHAASSOC_H namespace olive { -class PackedProcessor -{ -public: - PackedProcessor(); - - ~PackedProcessor(); - - DISABLE_COPY_MOVE(PackedProcessor) - - bool Open(const AudioParams ¶ms); - - QByteArray Convert(SampleBufferPtr planar); - - void Close(); - - bool IsOpen() const - { - return swr_ctx_; - } - -private: - SwrContext *swr_ctx_; - +enum AlphaAssociated { + kAlphaNone, + kAlphaUnassociated, + kAlphaAssociated }; } -#endif // PACKEDPROCESSOR_H +#endif // ALPHAASSOC_H diff --git a/app/render/audioparams.h b/app/render/audioparams.h index 9213fd872..cb4c591b7 100644 --- a/app/render/audioparams.h +++ b/app/render/audioparams.h @@ -186,6 +186,26 @@ public: duration_ = duration; } + static bool FormatIsPacked(Format f) + { + return f >= kPackedStart && f < kPackedEnd; + } + + bool FormatIsPacked() const + { + return FormatIsPacked(format_); + } + + static bool FormatIsPlanar(Format f) + { + return f >= kPlanarStart && f < kPlanarEnd; + } + + bool FormatIsPlanar() const + { + return FormatIsPlanar(format_); + } + qint64 time_to_bytes(const double& time) const; qint64 time_to_bytes(const rational& time) const; qint64 time_to_bytes_per_channel(const double& time) const; diff --git a/app/render/audioplaybackcache.cpp b/app/render/audioplaybackcache.cpp index e96d6cead..f014a2cf6 100644 --- a/app/render/audioplaybackcache.cpp +++ b/app/render/audioplaybackcache.cpp @@ -58,7 +58,7 @@ void AudioPlaybackCache::SetParameters(const AudioParams ¶ms) emit ParametersChanged(); } -void AudioPlaybackCache::WritePCM(const TimeRange &range, const TimeRangeList &valid_ranges, SampleBufferPtr samples) +void AudioPlaybackCache::WritePCM(const TimeRange &range, const TimeRangeList &valid_ranges, const SampleBuffer &samples) { // Ensure if we have enough segments to write this data, creating more if not qint64 length_diff = params_.time_to_bytes_per_channel(range.out()) - playlist_.GetLength(); @@ -72,7 +72,7 @@ void AudioPlaybackCache::WritePCM(const TimeRange &range, const TimeRangeList &v TimeRangeList ranges_we_validated; // Calculate buffer size per channel - qint64 buffer_size_per_channel = samples ? samples->sample_count() * params_.bytes_per_sample_per_channel() : 0; + qint64 buffer_size_per_channel = samples.sample_count() * params_.bytes_per_sample_per_channel(); // Write each valid range to the segments foreach (const TimeRange& r, valid_ranges) { @@ -114,7 +114,7 @@ void AudioPlaybackCache::WritePCM(const TimeRange &range, const TimeRangeList &v if (possible_write_length > 0) { // Assume `samples` is valid if we're here, or else `buffer_size_per_channel` and // therefore `possible_write_length` will be 0. - seg_file.write(reinterpret_cast(samples->data(i)) + src_offset, possible_write_length); + seg_file.write(reinterpret_cast(samples.data(i)) + src_offset, possible_write_length); } if (possible_write_length < total_write_length) { @@ -167,7 +167,7 @@ void AudioPlaybackCache::WriteSilence(const TimeRange &range) { // WritePCM will automatically fill non-existent bytes with silence, so we just have to send // it an empty sample buffer - WritePCM(range, {range}, nullptr); + WritePCM(range, {range}, SampleBuffer()); } void AudioPlaybackCache::ShiftEvent(const rational &from_in_time, const rational &to_in_time) diff --git a/app/render/audioplaybackcache.h b/app/render/audioplaybackcache.h index 8394975a9..8a75b0173 100644 --- a/app/render/audioplaybackcache.h +++ b/app/render/audioplaybackcache.h @@ -66,7 +66,7 @@ public: void SetParameters(const AudioParams& params); - void WritePCM(const TimeRange &range, const TimeRangeList &valid_ranges, SampleBufferPtr samples); + void WritePCM(const TimeRange &range, const TimeRangeList &valid_ranges, const SampleBuffer &samples); void WriteWaveform(const TimeRange &range, const TimeRangeList &valid_ranges, const AudioVisualWaveform *waveform); diff --git a/app/render/color.cpp b/app/render/color.cpp index bbf9a9601..ea00896d4 100644 --- a/app/render/color.cpp +++ b/app/render/color.cpp @@ -27,12 +27,12 @@ namespace olive { -Color Color::fromHsv(const float &h, const float &s, const float &v) +Color Color::fromHsv(const DataType &h, const DataType &s, const DataType &v) { - float C = s * v; - float X = C * (1.0 - abs(fmod(h / 60.0, 2.0) - 1.0)); - float m = v - C; - float Rs, Gs, Bs; + DataType C = s * v; + DataType X = C * (1.0 - abs(fmod(h / 60.0, 2.0) - 1.0)); + DataType m = v - C; + DataType Rs, Gs, Bs; if(h >= 0.0 && h < 60.0) { Rs = C; @@ -81,11 +81,11 @@ Color::Color(const QColor &c) set_alpha(c.alphaF()); } -void Color::toHsv(float *hue, float *sat, float *val) const +void Color::toHsv(DataType *hue, DataType *sat, DataType *val) const { - float fCMax = qMax(qMax(red(), green()), blue()); - float fCMin = qMin(qMin(red(), green()), blue()); - float fDelta = fCMax - fCMin; + DataType fCMax = qMax(qMax(red(), green()), blue()); + DataType fCMin = qMin(qMin(red(), green()), blue()); + DataType fDelta = fCMax - fCMin; if(fDelta > 0) { if(fCMax == red()) { @@ -114,31 +114,31 @@ void Color::toHsv(float *hue, float *sat, float *val) const } } -float Color::hsv_hue() const +Color::DataType Color::hsv_hue() const { - float h, s, v; + DataType h, s, v; toHsv(&h, &s, &v); return h; } -float Color::hsv_saturation() const +Color::DataType Color::hsv_saturation() const { - float h, s, v; + DataType h, s, v; toHsv(&h, &s, &v); return s; } -float Color::value() const +Color::DataType Color::value() const { - float h, s, v; + DataType h, s, v; toHsv(&h, &s, &v); return v; } -void Color::toHsl(float *hue, float *sat, float *lightness) const +void Color::toHsl(DataType *hue, DataType *sat, DataType *lightness) const { - float fCMin = qMin(red(), qMin(green(), blue())); - float fCMax = qMax(red(), qMax(green(), blue())); + DataType fCMin = qMin(red(), qMin(green(), blue())); + DataType fCMax = qMax(red(), qMax(green(), blue())); *lightness = 0.5 * (fCMin + fCMax); @@ -176,23 +176,23 @@ void Color::toHsl(float *hue, float *sat, float *lightness) const } } -float Color::hsl_hue() const +Color::DataType Color::hsl_hue() const { - float h, s, l; + DataType h, s, l; toHsl(&h, &s, &l); return h; } -float Color::hsl_saturation() const +Color::DataType Color::hsl_saturation() const { - float h, s, l; + DataType h, s, l; toHsl(&h, &s, &l); return s; } -float Color::lightness() const +Color::DataType Color::lightness() const { - float h, s, l; + DataType h, s, l; toHsl(&h, &s, &l); return l; } @@ -232,12 +232,12 @@ QColor Color::toQColor() const return c; } -float Color::GetRoughLuminance() const +Color::DataType Color::GetRoughLuminance() const { return (2*red()+blue()+3*green())/6.0; } -const Color &Color::operator+=(const Color &rhs) +Color &Color::operator+=(const Color &rhs) { for (int i=0;imutex()); @@ -41,6 +41,7 @@ ColorProcessor::ColorProcessor(ColorManager *config, const QString &input, const display_transform->setSrc(input.toUtf8()); display_transform->setDisplay(output.toUtf8()); display_transform->setView(view.toUtf8()); + display_transform->setDirection(direction == kNormal ? OCIO::TRANSFORM_DIR_FORWARD : OCIO::TRANSFORM_DIR_INVERSE); OCIO_SET_C_LOCALE_FOR_SCOPE; @@ -69,13 +70,25 @@ ColorProcessor::ColorProcessor(ColorManager *config, const QString &input, const } else { OCIO_SET_C_LOCALE_FOR_SCOPE; - processor_ = config->GetConfig()->getProcessor(input.toUtf8(), - output.toUtf8()); + try { + if (direction == kNormal) { + processor_ = config->GetConfig()->getProcessor(input.toUtf8(), output.toUtf8()); + } else { + processor_ = config->GetConfig()->getProcessor(output.toUtf8(), input.toUtf8()); + } + } catch (OCIO::Exception &e) { + qWarning() << "ColorProcessor exception:" << e.what(); + } } cpu_processor_ = processor_->getDefaultCPUProcessor(); - id_ = GenerateID(config, input, transform); +} + +ColorProcessor::ColorProcessor(OCIO::ConstProcessorRcPtr processor) +{ + processor_ = processor; + cpu_processor_ = processor_->getDefaultCPUProcessor(); } void ColorProcessor::ConvertFrame(Frame *f) @@ -109,18 +122,14 @@ Color ColorProcessor::ConvertColor(const Color& in) return Color(c[0], c[1], c[2], c[3]); } -QString ColorProcessor::GenerateID(ColorManager *config, const QString &input, const ColorTransform &transform) +ColorProcessorPtr ColorProcessor::Create(ColorManager *config, const QString& input, const ColorTransform &transform, Direction direction) { - return QStringLiteral("%1:%2:%3:%4:%5").arg(config->GetConfigFilename(), - input, - transform.display(), - transform.view(), - transform.look()); + return std::make_shared(config, input, transform, direction); } -ColorProcessorPtr ColorProcessor::Create(ColorManager *config, const QString& input, const ColorTransform &transform) +ColorProcessorPtr ColorProcessor::Create(OCIO::ConstProcessorRcPtr processor) { - return std::make_shared(config, input, transform); + return std::make_shared(processor); } OCIO::ConstProcessorRcPtr ColorProcessor::GetProcessor() diff --git a/app/render/colorprocessor.h b/app/render/colorprocessor.h index 60205055a..9d1c9130a 100644 --- a/app/render/colorprocessor.h +++ b/app/render/colorprocessor.h @@ -41,11 +41,13 @@ public: kInverse }; - ColorProcessor(ColorManager* config, const QString& input, const ColorTransform& dest_space); + ColorProcessor(ColorManager* config, const QString& input, const ColorTransform& dest_space, Direction direction = kNormal); + ColorProcessor(OCIO::ConstProcessorRcPtr processor); DISABLE_COPY_MOVE(ColorProcessor) - static ColorProcessorPtr Create(ColorManager* config, const QString& input, const ColorTransform& dest_space); + static ColorProcessorPtr Create(ColorManager* config, const QString& input, const ColorTransform& dest_space, Direction direction = kNormal); + static ColorProcessorPtr Create(OCIO::ConstProcessorRcPtr processor); OCIO::ConstProcessorRcPtr GetProcessor(); @@ -54,20 +56,16 @@ public: Color ConvertColor(const Color &in); - const QString& id() const + const char *id() const { - return id_; + return processor_->getCacheID(); } - static QString GenerateID(ColorManager* config, const QString& input, const ColorTransform& dest_space); - private: OCIO::ConstProcessorRcPtr processor_; OCIO::ConstCPUProcessorRcPtr cpu_processor_; - QString id_; - }; using ColorProcessorChain = QVector; diff --git a/app/render/diskmanager.cpp b/app/render/diskmanager.cpp index af67d6bfa..21b4b0413 100644 --- a/app/render/diskmanager.cpp +++ b/app/render/diskmanager.cpp @@ -196,7 +196,7 @@ DiskCacheFolder::DiskCacheFolder(const QString &path, QObject *parent) : { SetPath(path); - save_timer_.setInterval(Config::Current()[QStringLiteral("DiskCacheSaveInterval")].toInt()); + save_timer_.setInterval(OLIVE_CONFIG("DiskCacheSaveInterval").toInt()); connect(&save_timer_, &QTimer::timeout, this, &DiskCacheFolder::SaveDiskCacheIndex); save_timer_.start(); } diff --git a/app/render/job/acceleratedjob.h b/app/render/job/acceleratedjob.h index 534f4a528..c09138585 100644 --- a/app/render/job/acceleratedjob.h +++ b/app/render/job/acceleratedjob.h @@ -30,22 +30,22 @@ class AcceleratedJob { public: AcceleratedJob() = default; - NodeValue GetValue(const QString& input) const + NodeValue Get(const QString& input) const { return value_map_.value(input); } - void InsertValue(const QString &input, const NodeValueRow &row) + void Insert(const QString &input, const NodeValueRow &row) { value_map_.insert(input, row.value(input)); } - void InsertValue(const QString& input, const NodeValue& value) + void Insert(const QString& input, const NodeValue& value) { value_map_.insert(input, value); } - void InsertValue(const NodeValueRow &row) + void Insert(const NodeValueRow &row) { #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) value_map_.insert(row); diff --git a/app/render/job/colortransformjob.h b/app/render/job/colortransformjob.h new file mode 100644 index 000000000..18f48a7cb --- /dev/null +++ b/app/render/job/colortransformjob.h @@ -0,0 +1,113 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef COLORTRANSFORMJOB_H +#define COLORTRANSFORMJOB_H + +#include +#include + +#include "render/job/generatejob.h" +#include "render/alphaassoc.h" +#include "render/colorprocessor.h" +#include "render/texture.h" + +namespace olive { + +class Node; + +class ColorTransformJob : public GenerateJob +{ +public: + ColorTransformJob() + { + processor_ = nullptr; + input_texture_ = nullptr; + custom_shader_src_ = nullptr; + input_alpha_association_ = kAlphaNone; + clear_destination_ = true; + } + + QString id() const + { + if (id_.isEmpty()) { + return processor_->id(); + } else { + return id_; + } + } + + void SetOverrideID(const QString &id) { id_ = id; } + + TexturePtr GetInputTexture() const { return input_texture_; } + void SetInputTexture(TexturePtr tex) { input_texture_ = tex; } + + ColorProcessorPtr GetColorProcessor() const { return processor_; } + void SetColorProcessor(ColorProcessorPtr p) { processor_ = p; } + + const AlphaAssociated &GetInputAlphaAssociation() const { return input_alpha_association_; } + void SetInputAlphaAssociation(const AlphaAssociated &e) { input_alpha_association_ = e; } + + const Node *CustomShaderSource() const { return custom_shader_src_; } + const QString &CustomShaderID() const { return custom_shader_id_; } + void SetNeedsCustomShader(const Node *node, const QString &id = QString()) + { + custom_shader_src_ = node; + custom_shader_id_ = id; + } + + bool IsClearDestinationEnabled() const { return clear_destination_; } + void SetClearDestinationEnabled(bool e) { clear_destination_ = e; } + + const QMatrix4x4 &GetTransformMatrix() const { return matrix_; } + void SetTransformMatrix(const QMatrix4x4 &m) { matrix_ = m; } + + const QMatrix4x4 &GetCropMatrix() const { return crop_matrix_; } + void SetCropMatrix(const QMatrix4x4 &m) { crop_matrix_ = m; } + + const QString &GetFunctionName() const { return function_name_; } + void SetFunctionName(const QString &function_name = QString()) { function_name_ = function_name; }; + +private: + ColorProcessorPtr processor_; + QString id_; + + TexturePtr input_texture_; + + const Node *custom_shader_src_; + QString custom_shader_id_; + + AlphaAssociated input_alpha_association_; + + bool clear_destination_; + + QMatrix4x4 matrix_; + + QMatrix4x4 crop_matrix_; + + QString function_name_; + +}; + +} + +Q_DECLARE_METATYPE(olive::ColorTransformJob) + +#endif // COLORTRANSFORMJOB_H diff --git a/app/render/job/samplejob.h b/app/render/job/samplejob.h index ad78bb6d2..f2cd928e6 100644 --- a/app/render/job/samplejob.h +++ b/app/render/job/samplejob.h @@ -30,31 +30,30 @@ class SampleJob : public AcceleratedJob { public: SampleJob() { - samples_ = nullptr; } SampleJob(const NodeValue& value) { - samples_ = value.data().value(); + samples_ = value.toSamples(); } SampleJob(const QString& from, const NodeValueRow& row) { - samples_ = row[from].data().value(); + samples_ = row[from].toSamples(); } - SampleBufferPtr samples() const + const SampleBuffer &samples() const { return samples_; } bool HasSamples() const { - return samples_ && samples_->is_allocated(); + return samples_.is_allocated(); } private: - SampleBufferPtr samples_; + SampleBuffer samples_; }; diff --git a/app/render/job/shaderjob.h b/app/render/job/shaderjob.h index 77a08ae9f..53cd836d6 100644 --- a/app/render/job/shaderjob.h +++ b/app/render/job/shaderjob.h @@ -25,6 +25,7 @@ #include #include "generatejob.h" +#include "render/colorprocessor.h" #include "render/texture.h" namespace olive { diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index 628ed8f15..7f7119143 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -449,48 +449,48 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video case NodeValue::kInt: // kInt technically specifies a LongLong, but OpenGL doesn't support those. This may lead to // over/underflows if the number is large enough, but the likelihood of that is quite low. - functions_->glUniform1i(variable_location, value.data().toInt()); + functions_->glUniform1i(variable_location, value.toInt()); break; case NodeValue::kFloat: // kFloat technically specifies a double but as above, OpenGL doesn't support those. - functions_->glUniform1f(variable_location, value.data().toFloat()); + functions_->glUniform1f(variable_location, value.toDouble()); break; case NodeValue::kVec2: { - QVector2D v = value.data().value(); + QVector2D v = value.toVec2(); functions_->glUniform2fv(variable_location, 1, reinterpret_cast(&v)); break; } case NodeValue::kVec3: { - QVector3D v = value.data().value(); + QVector3D v = value.toVec3(); functions_->glUniform3fv(variable_location, 1, reinterpret_cast(&v)); break; } case NodeValue::kVec4: { - QVector4D v = value.data().value(); + QVector4D v = value.toVec4(); functions_->glUniform4fv(variable_location, 1, reinterpret_cast(&v)); break; } case NodeValue::kMatrix: - functions_->glUniformMatrix4fv(variable_location, 1, false, value.data().value().constData()); + functions_->glUniformMatrix4fv(variable_location, 1, false, value.toMatrix().constData()); break; case NodeValue::kCombo: - functions_->glUniform1i(variable_location, value.data().value()); + functions_->glUniform1i(variable_location, value.toInt()); break; case NodeValue::kColor: { - Color color = value.data().value(); + Color color = value.toColor(); functions_->glUniform4f(variable_location, color.red(), color.green(), color.blue(), color.alpha()); break; } case NodeValue::kBoolean: - functions_->glUniform1i(variable_location, value.data().toBool()); + functions_->glUniform1i(variable_location, value.toBool()); break; case NodeValue::kTexture: { - TexturePtr texture = value.data().value(); + TexturePtr texture = value.toTexture(); // Set value to bound texture functions_->glUniform1i(variable_location, textures_to_bind.size()); @@ -552,7 +552,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video // Ensure matrix is set, at least to identity GLint mvpmat_location = functions_->glGetUniformLocation(shader, "ove_mvpmat"); if (mvpmat_location > -1) { - functions_->glUniformMatrix4fv(mvpmat_location, 1, false, job.GetValue(QStringLiteral("ove_mvpmat")).data().value().constData()); + functions_->glUniformMatrix4fv(mvpmat_location, 1, false, job.Get(QStringLiteral("ove_mvpmat")).toMatrix().constData()); } // Set the viewport to the "physical" resolution of the destination diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index dcdb3d20a..98e1e73d1 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -48,7 +48,7 @@ PreviewAutoCacher::PreviewAutoCacher() : SetPlayhead(0); // Wait a certain amount of time before requeuing when we receive an invalidate signal - delayed_requeue_timer_.setInterval(Config::Current()[QStringLiteral("AutoCacheDelay")].toInt()); + delayed_requeue_timer_.setInterval(OLIVE_CONFIG("AutoCacheDelay").toInt()); delayed_requeue_timer_.setSingleShot(true); connect(&delayed_requeue_timer_, &QTimer::timeout, this, &PreviewAutoCacher::RequeueFrames); @@ -207,7 +207,7 @@ void PreviewAutoCacher::AudioRendered() // WritePCM is tolerant to its buffer being null, it will just write silence instead viewer_node_->audio_playback_cache()->WritePCM(range, valid_ranges, - watcher->Get().value()); + watcher->Get().value()); } viewer_node_->audio_playback_cache()->WriteWaveform(range, valid_ranges, &waveform); @@ -221,39 +221,39 @@ void PreviewAutoCacher::AudioRendered() // Wait for conform audio_needing_conform_.insert(range); } - } + } else{ + // Retrieve visual waveforms + QVector waveform_list = watcher->GetTicket()->property("waveforms").value< QVector >(); + foreach (const RenderProcessor::RenderedWaveform& waveform_info, waveform_list) { + // Find original track + ClipBlock* block = nullptr; - // Retrieve visual waveforms - QVector waveform_list = watcher->GetTicket()->property("waveforms").value< QVector >(); - foreach (const RenderProcessor::RenderedWaveform& waveform_info, waveform_list) { - // Find original track - ClipBlock* block = nullptr; - - for (auto it=copy_map_.cbegin(); it!=copy_map_.cend(); it++) { - if (it.value() == waveform_info.block) { - block = static_cast(it.key()); - break; - } - } - - if (block && !valid_ranges.isEmpty()) { - // Generate visual waveform in this background thread - block->waveform().set_channel_count(viewer_node_->GetAudioParams().channel_count()); - - // Determine which of the waveform ranges we got intersects with the valid ranges - TimeRangeList intersections = valid_ranges.Intersects(waveform_info.range + block->in()); - foreach (TimeRange r, intersections) { - // For each range, adjust it relative to the block and write it - r -= block->in(); - - if (waveform_info.silence) { - block->waveform().OverwriteSilence(r.in(), r.length()); - } else { - block->waveform().OverwriteSums(waveform_info.waveform, r.in(), r.in() - waveform_info.range.in(), r.length()); + for (auto it=copy_map_.cbegin(); it!=copy_map_.cend(); it++) { + if (it.value() == waveform_info.block) { + block = static_cast(it.key()); + break; } } - emit block->PreviewChanged(); + if (block && !valid_ranges.isEmpty()) { + // Generate visual waveform in this background thread + block->waveform().set_channel_count(viewer_node_->GetAudioParams().channel_count()); + + // Determine which of the waveform ranges we got intersects with the valid ranges + TimeRangeList intersections = valid_ranges.Intersects(waveform_info.range + block->in()); + foreach (TimeRange r, intersections) { + // For each range, adjust it relative to the block and write it + r -= block->in(); + + if (waveform_info.silence) { + block->waveform().OverwriteSilence(r.in(), r.length()); + } else { + block->waveform().OverwriteSums(waveform_info.waveform, r.in(), r.in() - waveform_info.range.in(), r.length()); + } + } + + emit block->PreviewChanged(); + } } } } @@ -502,8 +502,8 @@ void PreviewAutoCacher::StartCachingAudioRange(const TimeRange &range) void PreviewAutoCacher::SetPlayhead(const rational &playhead) { - cache_range_ = TimeRange(playhead - Config::Current()[QStringLiteral("DiskCacheBehind")].value(), - playhead + Config::Current()[QStringLiteral("DiskCacheAhead")].value()); + cache_range_ = TimeRange(playhead - OLIVE_CONFIG("DiskCacheBehind").value(), + playhead + OLIVE_CONFIG("DiskCacheAhead").value()); RequeueFrames(); } diff --git a/app/render/renderer.cpp b/app/render/renderer.cpp index b78df3df4..a75d6c02f 100644 --- a/app/render/renderer.cpp +++ b/app/render/renderer.cpp @@ -52,16 +52,6 @@ TexturePtr Renderer::CreateTexture(const VideoParams ¶ms, const void *data, return CreateTexture(params, Texture::k2D, data, linesize); } -void Renderer::BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, AlphaAssociated source_alpha_association, Texture *destination, bool clear_destination, const QMatrix4x4 &matrix, const QMatrix4x4 &crop_matrix) -{ - BlitColorManagedInternal(color_processor, source, source_alpha_association, destination, destination->params(), clear_destination, matrix, crop_matrix); -} - -void Renderer::BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, AlphaAssociated source_alpha_association, VideoParams params, bool clear_destination, const QMatrix4x4& matrix, const QMatrix4x4 &crop_matrix) -{ - BlitColorManagedInternal(color_processor, source, source_alpha_association, nullptr, params, clear_destination, matrix, crop_matrix); -} - TexturePtr Renderer::InterlaceTexture(TexturePtr top, TexturePtr bottom, const VideoParams ¶ms) { color_cache_mutex_.lock(); @@ -71,9 +61,9 @@ TexturePtr Renderer::InterlaceTexture(TexturePtr top, TexturePtr bottom, const V color_cache_mutex_.unlock(); ShaderJob job; - job.InsertValue(QStringLiteral("top_tex_in"), NodeValue(NodeValue::kTexture, QVariant::fromValue(top))); - job.InsertValue(QStringLiteral("bottom_tex_in"), NodeValue(NodeValue::kTexture, QVariant::fromValue(bottom))); - job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, QVector2D(params.effective_width(), params.effective_height()))); + job.Insert(QStringLiteral("top_tex_in"), NodeValue(NodeValue::kTexture, QVariant::fromValue(top))); + job.Insert(QStringLiteral("bottom_tex_in"), NodeValue(NodeValue::kTexture, QVariant::fromValue(bottom))); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, QVector2D(params.effective_width(), params.effective_height()))); TexturePtr output = CreateTexture(params); @@ -103,35 +93,45 @@ TexturePtr Renderer::CreateTextureFromNativeHandle(const QVariant &v, const Vide return std::make_shared(this, v, params, type); } -bool Renderer::GetColorContext(ColorProcessorPtr color_processor, Renderer::ColorContext *ctx) +bool Renderer::GetColorContext(const ColorTransformJob &color_job, Renderer::ColorContext *ctx) { QMutexLocker locker(&color_cache_mutex_); ColorContext& color_ctx = *ctx; - if (color_cache_.contains(color_processor->id())) { - color_ctx = color_cache_.value(color_processor->id()); + QString proc_id = color_job.id(); + + if (color_cache_.contains(proc_id)) { + color_ctx = color_cache_.value(proc_id); return true; } else { // Create shader description - const char* ocio_func_name = "OCIODisplay"; + QString ocio_func_name; + if (color_job.GetFunctionName().isEmpty()) { + ocio_func_name = "OCIODisplay"; + } else { + ocio_func_name = color_job.GetFunctionName(); + } auto shader_desc = OCIO::GpuShaderDesc::CreateShaderDesc(); shader_desc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_ES_3_0); - shader_desc->setFunctionName(ocio_func_name); + shader_desc->setFunctionName(ocio_func_name.toUtf8()); shader_desc->setResourcePrefix("ocio_"); // Generate shader - color_processor->GetProcessor()->getDefaultGPUProcessor()->extractGpuShaderInfo(shader_desc); + color_job.GetColorProcessor()->GetProcessor()->getDefaultGPUProcessor()->extractGpuShaderInfo(shader_desc); - // Generate shader code using OCIO stub and our auto-generated name - QString shader_frag = FileFunctions::ReadFileAsString(QStringLiteral(":shaders/colormanage.frag")).arg( - shader_desc->getShaderText(), - ocio_func_name - ); + ShaderCode code; + if (const Node *shader_src = color_job.CustomShaderSource()) { + // Use shader code from associated node + code = shader_src->GetShaderCode({color_job.CustomShaderID(), shader_desc->getShaderText()}); + } else { + // Generate shader code using OCIO stub and our auto-generated name + code = FileFunctions::ReadFileAsString(QStringLiteral(":shaders/colormanage.frag")); + code.set_frag_code(code.frag_code().arg(shader_desc->getShaderText())); + } // Try to compile shader - color_ctx.compiled_shader = CreateNativeShader(ShaderCode(shader_frag, - FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/default.vert")))); + color_ctx.compiled_shader = CreateNativeShader(code); if (color_ctx.compiled_shader.isNull()) { return false; @@ -199,42 +199,40 @@ bool Renderer::GetColorContext(ColorProcessorPtr color_processor, Renderer::Colo color_ctx.lut1d_textures[i].interpolation = (interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest : Texture::kLinear; } - color_cache_.insert(color_processor->id(), color_ctx); + color_cache_.insert(proc_id, color_ctx); return true; } } -void Renderer::BlitColorManagedInternal(ColorProcessorPtr color_processor, TexturePtr source, - AlphaAssociated source_alpha_association, Texture *destination, - VideoParams params, bool clear_destination, const QMatrix4x4& matrix, - const QMatrix4x4& crop_matrix) +void Renderer::BlitColorManaged(const ColorTransformJob &color_job, Texture *destination, const VideoParams ¶ms) { ColorContext color_ctx; - if (!GetColorContext(color_processor, &color_ctx)) { + if (!GetColorContext(color_job, &color_ctx)) { return; } ShaderJob job; - - job.InsertValue(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(source))); - job.InsertValue(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, matrix)); - job.InsertValue(QStringLiteral("ove_cropmatrix"), NodeValue(NodeValue::kMatrix, crop_matrix.inverted())); - job.InsertValue(QStringLiteral("ove_maintex_alpha"), NodeValue(NodeValue::kInt, source_alpha_association)); + job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(color_job.GetInputTexture()))); + job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, color_job.GetTransformMatrix())); + job.Insert(QStringLiteral("ove_cropmatrix"), NodeValue(NodeValue::kMatrix, color_job.GetCropMatrix().inverted())); + job.Insert(QStringLiteral("ove_maintex_alpha"), NodeValue(NodeValue::kInt, int(color_job.GetInputAlphaAssociation()))); + job.Insert(color_job.GetValues()); + job.SetAlphaChannelRequired(color_job.GetAlphaChannelRequired()); foreach (const ColorContext::LUT& l, color_ctx.lut3d_textures) { - job.InsertValue(l.name, NodeValue(NodeValue::kTexture, QVariant::fromValue(l.texture))); + job.Insert(l.name, NodeValue(NodeValue::kTexture, QVariant::fromValue(l.texture))); job.SetInterpolation(l.name, l.interpolation); } foreach (const ColorContext::LUT& l, color_ctx.lut1d_textures) { - job.InsertValue(l.name, NodeValue(NodeValue::kTexture, QVariant::fromValue(l.texture))); + job.Insert(l.name, NodeValue(NodeValue::kTexture, QVariant::fromValue(l.texture))); job.SetInterpolation(l.name, l.interpolation); } if (destination) { - BlitToTexture(color_ctx.compiled_shader, job, destination, clear_destination); + BlitToTexture(color_ctx.compiled_shader, job, destination, color_job.IsClearDestinationEnabled()); } else { - Blit(color_ctx.compiled_shader, job, params, clear_destination); + Blit(color_ctx.compiled_shader, job, params, color_job.IsClearDestinationEnabled()); } } diff --git a/app/render/renderer.h b/app/render/renderer.h index 977fc98ce..e46ec7990 100644 --- a/app/render/renderer.h +++ b/app/render/renderer.h @@ -29,6 +29,7 @@ #include "common/timerange.h" #include "node/node.h" #include "render/colorprocessor.h" +#include "render/job/colortransformjob.h" #include "render/videoparams.h" #include "texture.h" @@ -63,14 +64,15 @@ public: Blit(shader, job, nullptr, params, clear_destination); } - enum AlphaAssociated { - kAlphaNone, - kAlphaUnassociated, - kAlphaAssociated - }; - - void BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, AlphaAssociated source_alpha_association, Texture* destination, bool clear_destination = true, const QMatrix4x4& matrix = QMatrix4x4(), const QMatrix4x4 &crop_matrix = QMatrix4x4()); - void BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, AlphaAssociated source_alpha_association, VideoParams params, bool clear_destination = true, const QMatrix4x4& matrix = QMatrix4x4(), const QMatrix4x4 &crop_matrix = QMatrix4x4()); + void BlitColorManaged(const ColorTransformJob &color_job, Texture* destination, const VideoParams ¶ms); + void BlitColorManaged(const ColorTransformJob &job, Texture* destination) + { + BlitColorManaged(job, destination, destination->params()); + } + void BlitColorManaged(const ColorTransformJob &job, const VideoParams ¶ms) + { + BlitColorManaged(job, nullptr, params); + } TexturePtr InterlaceTexture(TexturePtr top, TexturePtr bottom, const VideoParams ¶ms); @@ -126,12 +128,7 @@ private: }; - bool GetColorContext(ColorProcessorPtr color_processor, ColorContext* ctx); - - void BlitColorManagedInternal(ColorProcessorPtr color_processor, TexturePtr source, - AlphaAssociated source_alpha_association, - Texture* destination, VideoParams params, bool clear_destination, - const QMatrix4x4 &matrix, const QMatrix4x4 &crop_matrix); + bool GetColorContext(const ColorTransformJob &color_job, ColorContext* ctx); QHash color_cache_; diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index cdb742d55..eb0ebff20 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -25,9 +25,7 @@ #include #include -#include "audio/packedprocessor.h" -#include "audio/planarprocessor.h" -#include "audio/tempoprocessor.h" +#include "audio/audioprocessor.h" #include "node/block/clip/clip.h" #include "node/block/transition/transition.h" #include "node/project/project.h" @@ -57,11 +55,11 @@ TexturePtr RenderProcessor::GenerateTexture(const rational &time, const rational table = GenerateTable(texture_output, viewer->GetValueHintForInput(ViewerOutput::kTextureInput), range); } - NodeValue tex_val = table.GetWithMeta(NodeValue::kTexture); + NodeValue tex_val = table.Get(NodeValue::kTexture); ResolveJobs(tex_val, range); - return tex_val.data().value(); + return tex_val.toTexture(); } FramePtr RenderProcessor::GenerateFrame(TexturePtr texture, const rational& time) @@ -105,14 +103,19 @@ FramePtr RenderProcessor::GenerateFrame(TexturePtr texture, const rational& time if (output_color_transform) { // Yes color transform, blit color managed - render_ctx_->BlitColorManaged(output_color_transform, texture, - Config::Current()[QStringLiteral("ReassocLinToNonLin")].toBool() ? Renderer::kAlphaAssociated : Renderer::kAlphaNone, - blit_tex.get(), true, matrix); + ColorTransformJob job; + + job.SetColorProcessor(output_color_transform); + job.SetInputTexture(texture); + job.SetInputAlphaAssociation(OLIVE_CONFIG("ReassocLinToNonLin").toBool() ? kAlphaAssociated : kAlphaNone); + job.SetTransformMatrix(matrix); + + render_ctx_->BlitColorManaged(job, blit_tex.get()); } else { // No color transform, just blit ShaderJob job; - job.InsertValue(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture))); - job.InsertValue(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, matrix)); + job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture))); + job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, matrix)); render_ctx_->BlitToTexture(default_shader_, job, blit_tex.get()); } @@ -193,19 +196,22 @@ void RenderProcessor::Run() table = GenerateTable(texture_output, viewer->GetValueHintForInput(ViewerOutput::kSamplesInput),time); } - QVariant sample_variant = table.Get(NodeValue::kSamples); - SampleBufferPtr samples = sample_variant.value(); - if (samples && ticket_->property("enablewaveforms").toBool()) { + NodeValue sample_val = table.Get(NodeValue::kSamples); + + ResolveJobs(sample_val, time); + + SampleBuffer samples = sample_val.toSamples(); + if (samples.is_allocated() && ticket_->property("enablewaveforms").toBool()) { AudioVisualWaveform vis; - vis.set_channel_count(samples->audio_params().channel_count()); - vis.OverwriteSamples(samples, samples->audio_params().sample_rate()); + vis.set_channel_count(samples.audio_params().channel_count()); + vis.OverwriteSamples(samples, samples.audio_params().sample_rate()); ticket_->setProperty("waveform", QVariant::fromValue(vis)); } if (ticket_->IsCancelled()) { ticket_->Finish(); } else { - ticket_->Finish(sample_variant); + ticket_->Finish(QVariant::fromValue(samples)); } break; } @@ -269,9 +275,8 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim QVector active_blocks = track->BlocksAtTimeRange(range); // All these blocks will need to output to a buffer so we create one here - SampleBufferPtr block_range_buffer = SampleBuffer::CreateAllocated(audio_params, - audio_params.time_to_samples(range.length())); - block_range_buffer->silence(); + SampleBuffer block_range_buffer(audio_params, range.length()); + block_range_buffer.silence(); NodeValueTable merged_table; @@ -286,10 +291,10 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim // Destination buffer NodeValueTable table = GenerateTable(b, track->GetValueHintForInput(Track::kBlockInput, track->GetArrayIndexFromBlock(b)),Track::TransformRangeForBlock(b, range_for_block)); - SampleBufferPtr samples_from_this_block = table.Take(NodeValue::kSamples).value(); + SampleBuffer samples_from_this_block = table.Take(NodeValue::kSamples).toSamples(); ClipBlock *clip_cast = dynamic_cast(b); - if (samples_from_this_block) { + if (samples_from_this_block.is_allocated()) { // If this is a clip, we might have extra speed/reverse information if (clip_cast) { double speed_value = clip_cast->speed(); @@ -297,17 +302,13 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim if (qIsNull(speed_value)) { // Just silence, don't think there's any other practical application of 0 speed audio - samples_from_this_block->silence(); + samples_from_this_block.silence(); } else if (!qFuzzyCompare(speed_value, 1.0)) { if (clip_cast->maintain_audio_pitch()) { - PackedProcessor packer; - packer.Open(samples_from_this_block->audio_params()); + AudioProcessor processor; - QByteArray packed = packer.Convert(samples_from_this_block); - - if (!packed.isEmpty()) { - TempoProcessor tp; - tp.Open(samples_from_this_block->audio_params(), speed_value); + if (processor.Open(samples_from_this_block.audio_params(), samples_from_this_block.audio_params(), speed_value)) { + AudioProcessor::Buffer out; // FIXME: This is not the best way to do this, the TempoProcessor works best // when it's given a continuous stream of audio, which is challenging @@ -315,33 +316,46 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim // well on export (assuming audio is all generated at once on export), but // users may hear clicks and pops in the audio during preview due to this // approach. - tp.Push(packed); - tp.Flush(); - packed = tp.Pull(); - tp.Close(); + int r = processor.Convert(samples_from_this_block.to_raw_ptrs().data(), samples_from_this_block.sample_count(), nullptr); - if (!packed.isEmpty()) { - PlanarProcessor planar; - planar.Open(samples_from_this_block->audio_params()); - samples_from_this_block = planar.Convert(packed); + if (r < 0) { + qCritical() << "Failed to change tempo of audio:" << r; + } else { + processor.Flush(); + + processor.Convert(nullptr, 0, &out); + + if (!out.empty()) { + int nb_samples = out.front().size() * samples_from_this_block.audio_params().bytes_per_sample_per_channel(); + + if (nb_samples) { + SampleBuffer new_samples(samples_from_this_block.audio_params(), nb_samples); + + for (int i=0; ispeed(speed_value); + samples_from_this_block.speed(speed_value); } } if (reversed) { - samples_from_this_block->reverse(); + samples_from_this_block.reverse(); } } - int copy_length = qMin(max_dest_sz, samples_from_this_block->sample_count()); + int copy_length = qMin(max_dest_sz, samples_from_this_block.sample_count()); // Copy samples into destination buffer - for (int i=0; iaudio_params().channel_count(); i++) { - block_range_buffer->set(i, samples_from_this_block->data(i), destination_offset, copy_length); + for (int i=0; iin(); - if (!(waveform_info.silence = !samples_from_this_block.get())) { + if (!(waveform_info.silence = !samples_from_this_block.is_allocated())) { // Generate a visual waveform from the samples acquired from this block AudioVisualWaveform visual_waveform; visual_waveform.set_channel_count(audio_params.channel_count()); @@ -388,19 +402,10 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ // Check the still frame cache. On large frames such as high resolution still images, uploading // and color managing them for every frame is a waste of time, so we implement a small cache here // to optimize such a situation - const VideoParams& render_params = GetCacheVideoParams(); VideoParams stream_data = stream.video_params(); ColorManager* color_manager = Node::ValueToPtr(ticket_->property("colormanager")); - // See if we can make this divider larger (i.e. if the fooage is smaller) - int footage_divider = render_params.divider(); - while (footage_divider > 1 - && VideoParams::GetScaledDimension(stream_data.width(), footage_divider-1) < render_params.effective_width() - && VideoParams::GetScaledDimension(stream_data.height(), footage_divider-1) < render_params.effective_height()) { - footage_divider--; - } - QString using_colorspace = stream_data.colorspace(); if (using_colorspace.isEmpty()) { @@ -435,7 +440,7 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ if (decoder) { Decoder::RetrieveVideoParams p; - p.divider = footage_divider; + p.divider = stream.video_params().divider(); p.src_interlacing = stream_data.interlacing(); p.dst_interlacing = GetCacheVideoParams().interlacing(); @@ -454,25 +459,27 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ using_colorspace, color_manager->GetReferenceColorSpace()); - Renderer::AlphaAssociated alpha_assoc; + ColorTransformJob job; + + job.SetColorProcessor(processor); + job.SetInputTexture(unmanaged_texture); + if (stream_data.channel_count() != VideoParams::kRGBAChannelCount || stream_data.colorspace() == color_manager->GetReferenceColorSpace()) { - alpha_assoc = Renderer::kAlphaNone; + job.SetInputAlphaAssociation(kAlphaNone); } else if (stream_data.premultiplied_alpha()) { - alpha_assoc = Renderer::kAlphaAssociated; + job.SetInputAlphaAssociation(kAlphaAssociated); } else { - alpha_assoc = Renderer::kAlphaUnassociated; + job.SetInputAlphaAssociation(kAlphaUnassociated); } - render_ctx_->BlitColorManaged(processor, unmanaged_texture, - alpha_assoc, - destination.get()); + render_ctx_->BlitColorManaged(job, destination.get()); } } } } -void RenderProcessor::ProcessAudioFootage(SampleBufferPtr destination, const FootageJob &stream, const TimeRange &input_time) +void RenderProcessor::ProcessAudioFootage(SampleBuffer &destination, const FootageJob &stream, const TimeRange &input_time) { DecoderPtr decoder = ResolveDecoderFromInput(stream.decoder(), Decoder::CodecStream(stream.filename(), stream.audio_params().stream_index())); @@ -515,9 +522,9 @@ void RenderProcessor::ProcessShader(TexturePtr destination, const Node *node, co render_ctx_->BlitToTexture(shader, job, destination.get()); } -void RenderProcessor::ProcessSamples(SampleBufferPtr destination, const Node *node, const TimeRange &range, const SampleJob &job) +void RenderProcessor::ProcessSamples(SampleBuffer &destination, const Node *node, const TimeRange &range, const SampleJob &job) { - if (!job.samples() || !job.samples()->is_allocated()) { + if (!job.samples().is_allocated()) { return; } @@ -525,7 +532,7 @@ void RenderProcessor::ProcessSamples(SampleBufferPtr destination, const Node *no const AudioParams& audio_params = GetCacheAudioParams(); - for (int i=0;isample_count();i++) { + for (int i=0;i(i) / static_cast(audio_params.sample_rate()); @@ -545,6 +552,11 @@ void RenderProcessor::ProcessSamples(SampleBufferPtr destination, const Node *no } } +void RenderProcessor::ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob &job) +{ + render_ctx_->BlitColorManaged(job, destination.get()); +} + void RenderProcessor::ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob &job) { FramePtr frame = Frame::Create(); @@ -566,7 +578,14 @@ void RenderProcessor::ConvertToReferenceSpace(TexturePtr destination, TexturePtr { ColorManager* color_manager = Node::ValueToPtr(ticket_->property("colormanager")); ColorProcessorPtr cp = ColorProcessor::Create(color_manager, input_cs, color_manager->GetReferenceColorSpace()); - render_ctx_->BlitColorManaged(cp, source, Renderer::kAlphaAssociated, destination.get()); + + ColorTransformJob ctj; + + ctj.SetColorProcessor(cp); + ctj.SetInputTexture(source); + ctj.SetInputAlphaAssociation(kAlphaAssociated); + + render_ctx_->BlitColorManaged(ctj, destination.get()); } } diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h index b39f2d7ff..017cedada 100644 --- a/app/render/renderprocessor.h +++ b/app/render/renderprocessor.h @@ -46,11 +46,13 @@ protected: virtual void ProcessVideoFootage(TexturePtr destination, const FootageJob &stream, const rational &input_time) override; - virtual void ProcessAudioFootage(SampleBufferPtr destination, const FootageJob &stream, const TimeRange &input_time) override; + virtual void ProcessAudioFootage(SampleBuffer &destination, const FootageJob &stream, const TimeRange &input_time) override; virtual void ProcessShader(TexturePtr destination, const Node *node, const TimeRange &range, const ShaderJob& job) override; - virtual void ProcessSamples(SampleBufferPtr destination, const Node *node, const TimeRange &range, const SampleJob &job) override; + virtual void ProcessSamples(SampleBuffer &destination, const Node *node, const TimeRange &range, const SampleJob &job) override; + + virtual void ProcessColorTransform(TexturePtr destination, const Node *node, const ColorTransformJob& job) override; virtual void ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob& job) override; @@ -61,9 +63,9 @@ protected: return render_ctx_->CreateTexture(p); } - virtual SampleBufferPtr CreateSampleBuffer(const AudioParams ¶ms, int sample_count) override + virtual SampleBuffer CreateSampleBuffer(const AudioParams ¶ms, int sample_count) override { - return SampleBuffer::CreateAllocated(params, sample_count); + return SampleBuffer(params, sample_count); } virtual void ConvertToReferenceSpace(TexturePtr destination, TexturePtr source, const QString &input_cs) override; diff --git a/app/render/shadercode.h b/app/render/shadercode.h index c41422122..20c8b8079 100644 --- a/app/render/shadercode.h +++ b/app/render/shadercode.h @@ -33,15 +33,11 @@ public: { } - const QString& frag_code() const - { - return frag_code_; - } + const QString& frag_code() const { return frag_code_; } + void set_frag_code(const QString &f) { frag_code_ = f; } - const QString& vert_code() const - { - return vert_code_; - } + const QString& vert_code() const { return vert_code_; } + void set_vert_code(const QString &v) { vert_code_ = v; } private: QString frag_code_; diff --git a/app/shaders/blur.frag b/app/shaders/blur.frag index e0378a4bf..426362813 100644 --- a/app/shaders/blur.frag +++ b/app/shaders/blur.frag @@ -6,6 +6,12 @@ uniform bool vert_in; uniform bool repeat_edge_pixels_in; uniform vec2 resolution_in; +// Directional +uniform float directional_degrees_in; + +// Radial +uniform vec2 radial_center_in; + uniform int ove_iteration; in vec2 ove_texcoord; @@ -17,6 +23,8 @@ out vec4 frag_color; // Methods #define METHOD_BOX_BLUR 0 #define METHOD_GAUSSIAN_BLUR 1 +#define METHOD_DIRECTIONAL_BLUR 2 +#define METHOD_RADIAL_BLUR 3 // Mode #define MODE_NONE 0 @@ -60,6 +68,19 @@ int determine_mode() { } } +vec4 add_to_composite(vec4 composite, vec2 pixel_coord, float weight) +{ + if (repeat_edge_pixels_in + || (pixel_coord.x >= 0.0 + && pixel_coord.x < 1.0 + && pixel_coord.y >= 0.0 + && pixel_coord.y < 1.0)) { + composite += texture(tex_in, pixel_coord) * weight; + } + + return composite; +} + void main(void) { int mode = determine_mode(); @@ -75,7 +96,13 @@ void main(void) { float divider, sigma; - if (method_in == METHOD_BOX_BLUR) { + if (method_in == METHOD_DIRECTIONAL_BLUR || method_in == METHOD_RADIAL_BLUR) { + // Despite similar math, these are lighter methods perceptually, so we double the radius to + // better match box/gaussian + real_radius *= 2.0; + } + + if (method_in == METHOD_BOX_BLUR || method_in == METHOD_DIRECTIONAL_BLUR) { // Calculate the weight of each pixel based on the radius divider = 1.0 / real_radius; @@ -95,28 +122,53 @@ void main(void) { } - for (float i = -real_radius + 0.5; i <= real_radius; i += 2.0) { - float weight; + if (method_in == METHOD_BOX_BLUR || method_in == METHOD_GAUSSIAN_BLUR) { + for (float i = -real_radius + 0.5; i <= real_radius; i += 2.0) { + float weight; - if (method_in == METHOD_BOX_BLUR) { - weight = divider; - } else if (method_in == METHOD_GAUSSIAN_BLUR) { - weight = gaussian2(i, 0.0, sigma) / divider; + if (method_in == METHOD_BOX_BLUR) { + weight = divider; + } else if (method_in == METHOD_GAUSSIAN_BLUR) { + weight = gaussian2(i, 0.0, sigma) / divider; + } + + vec2 pixel_coord = ove_texcoord; + if (mode == MODE_HORIZONTAL) { + pixel_coord.x += i / resolution_in.x; + } else if (mode == MODE_VERTICAL) { + pixel_coord.y += i / resolution_in.y; + } + + composite = add_to_composite(composite, pixel_coord, weight); + } + } else if (method_in == METHOD_DIRECTIONAL_BLUR || method_in == METHOD_RADIAL_BLUR) { + float angle; + + if (method_in == METHOD_DIRECTIONAL_BLUR) { + // Convert directional degrees to radians + angle = (directional_degrees_in*M_PI)/180.0; + } else { + // Calculate angle from distance of center to current coordinate + vec2 distance = (ove_texcoord - 0.5) * (resolution_in) - radial_center_in; + angle = atan(distance.y/distance.x); + + float multiplier = length(distance) / resolution_in.y * 2.0; + + real_radius = ceil(radius_in * multiplier); + divider = 1.0 / real_radius; } - vec2 pixel_coord = ove_texcoord; - if (mode == MODE_HORIZONTAL) { - pixel_coord.x += i / resolution_in.x; - } else if (mode == MODE_VERTICAL) { - pixel_coord.y += i / resolution_in.y; - } + // Get angles + float sin_angle = sin(angle); + float cos_angle = cos(angle); - if (repeat_edge_pixels_in - || (pixel_coord.x >= 0.0 - && pixel_coord.x < 1.0 - && pixel_coord.y >= 0.0 - && pixel_coord.y < 1.0)) { - composite += texture(tex_in, pixel_coord) * weight; + for (float i = -real_radius + 0.5; i <= real_radius; i += 2.0) { + vec2 pixel_coord = ove_texcoord; + + pixel_coord.y += sin_angle * i / resolution_in.y; + pixel_coord.x += cos_angle * i / resolution_in.x; + + composite = add_to_composite(composite, pixel_coord, divider); } } diff --git a/app/shaders/chromakey.frag b/app/shaders/chromakey.frag new file mode 100644 index 000000000..8c707e644 --- /dev/null +++ b/app/shaders/chromakey.frag @@ -0,0 +1,105 @@ +// Main texture input +uniform sampler2D tex_in; +uniform vec4 color_key; +uniform bool mask_only_in; +uniform float upper_tolerence_in; +uniform float lower_tolerence_in; + +uniform sampler2D garbage_in; +uniform sampler2D core_in; +uniform bool garbage_in_enabled; +uniform bool core_in_enabled; + +uniform float highlights_in; +uniform float shadows_in; + + +// Main texture coordinate +in vec2 ove_texcoord; +out vec4 frag_color; + +// Program will replace this with OCIO's auto-generated shader code +%1 + +// Assume D65 white point +float Xn = 95.0489; +float Yn = 100.0; +float Zn = 108.8840; +float delta = 0.20689655172; // 6/29 + +float func(float t) { + if (t > pow(delta, 3.0)){ + return pow(t, 1.0/3.0); + } else{ + return (t / (3.0 * pow(delta, 2))) + 4.0/29.0; + } +} + +vec4 CIExyz_to_Lab(vec4 CIE) { + vec4 lab; + lab.r = 116.0 * func(CIE.g / Yn) - 16.0; + lab.g = 500.0 * (func(CIE.r / Xn) - func(CIE.g / Yn)); + lab.b = 200.0 * (func(CIE.g / Yn) - func(CIE.b / Zn)); + lab.w = CIE.w; + + return lab; +} + +float colorclose(vec4 col, vec4 key, float tola,float tolb) { + // Decides if a color is close to the specified hue + float temp = sqrt(((key.g-col.g)*(key.g-col.g))+((key.b-col.b)*(key.b-col.b))); + if (temp < tola) {return (0.0);} + if (temp < tolb) {return ((temp-tola)/(tolb-tola));} + return (1.0); +} + + +void main() { + + vec4 col = texture(tex_in, ove_texcoord); + + vec4 unassoc = col; + if (unassoc.a > 0) { + unassoc.rgb /= unassoc.a; + } + + // Perform color conversion + vec4 cie_xyz = SceneLinearToCIEXYZ_d65(unassoc); + vec4 lab = CIExyz_to_Lab(cie_xyz); + + vec4 cie_xyz_key = SceneLinearToCIEXYZ_d65(color_key); + vec4 lab_key = CIExyz_to_Lab(cie_xyz_key); + + float mask = colorclose(lab, lab_key, lower_tolerence_in, upper_tolerence_in); + + mask = clamp(mask, 0.0, 1.0); + + if (garbage_in_enabled) { + // Force anything we want to remove to be 0.0 + vec4 garbage = texture(garbage_in, ove_texcoord); + // Assumes garbage is achromatic + mask -= garbage.r; + mask = clamp(mask, 0.0, 1.0); + } + + if (core_in_enabled) { + // Force anything we want to keep to be 1.0 + vec3 core = texture(core_in, ove_texcoord).rgb; + // Assumes core is achromatic + mask += core.r; + mask = clamp(mask, 0.0, 1.0); + } + + // Crush blacks and push whites + mask = shadows_in * 0.01 * (highlights_in * 0.01 * mask - 1.0) + 1.0; + mask = clamp(mask, 0.0, 1.0); + + col.rgb *= mask; + col.w = mask; + + if (!mask_only_in) { + frag_color = col; + } else { + frag_color = vec4(vec3(mask), 1.0); + } +} diff --git a/app/shaders/colordifferencekey.frag b/app/shaders/colordifferencekey.frag index 5ed5fb9ec..e47ee88db 100644 --- a/app/shaders/colordifferencekey.frag +++ b/app/shaders/colordifferencekey.frag @@ -51,7 +51,7 @@ void main(void) { } // Crush blacks and push whites - mask = highlights_in * 0.01 * (shadows_in * 0.01 * mask - 1.0) + 1.0; + mask = highlights_in * (shadows_in * mask - 1.0) + 1.0; mask = clamp(mask, 0.0, 1.0); // Invert mask diff --git a/app/shaders/colormanage.frag b/app/shaders/colormanage.frag index e9461e71e..25efe5b50 100644 --- a/app/shaders/colormanage.frag +++ b/app/shaders/colormanage.frag @@ -44,7 +44,7 @@ void main() { } // Perform color conversion - col = %2(col); + col = OCIODisplay(col); // Associate or re-associate here if (ove_maintex_alpha == ALPHA_ASSOC) { diff --git a/app/shaders/multiply.frag b/app/shaders/multiply.frag new file mode 100644 index 000000000..901a310c3 --- /dev/null +++ b/app/shaders/multiply.frag @@ -0,0 +1,11 @@ +// Input texture +uniform sampler2D tex_a; +uniform sampler2D tex_b; + +// Input texture coordinate +in vec2 ove_texcoord; +out vec4 frag_color; + +void main() { + frag_color = texture(tex_a, ove_texcoord) * texture(tex_b, ove_texcoord); +} diff --git a/app/task/export/export.cpp b/app/task/export/export.cpp index 819d9251e..21c330c29 100644 --- a/app/task/export/export.cpp +++ b/app/task/export/export.cpp @@ -192,7 +192,7 @@ bool ExportTask::FrameDownloaded(FramePtr f, const QByteArray &hash, const QVect return true; } -bool ExportTask::AudioDownloaded(const TimeRange &range, SampleBufferPtr samples) +bool ExportTask::AudioDownloaded(const TimeRange &range, const SampleBuffer &samples) { TimeRange adjusted_range = range; @@ -221,7 +221,7 @@ bool ExportTask::EncodeSubtitle(const SubtitleBlock *sub) } } -bool ExportTask::WriteAudioLoop(const TimeRange& time, SampleBufferPtr samples) +bool ExportTask::WriteAudioLoop(const TimeRange& time, const SampleBuffer &samples) { if (!encoder_->WriteAudio(samples)) { SetError(encoder_->GetError()); @@ -232,7 +232,7 @@ bool ExportTask::WriteAudioLoop(const TimeRange& time, SampleBufferPtr samples) for (auto it=audio_map_.begin(); it!=audio_map_.end(); it++) { TimeRange t = it.key(); - SampleBufferPtr s = it.value(); + SampleBuffer s = it.value(); if (t.in() == audio_time_) { // Erase from audio map since we're just about to write it diff --git a/app/task/export/export.h b/app/task/export/export.h index 2efade730..2ab27f917 100644 --- a/app/task/export/export.h +++ b/app/task/export/export.h @@ -40,7 +40,7 @@ protected: virtual bool FrameDownloaded(FramePtr frame, const QByteArray& hash, const QVector& times) override; - virtual bool AudioDownloaded(const TimeRange& range, SampleBufferPtr samples) override; + virtual bool AudioDownloaded(const TimeRange& range, const SampleBuffer &samples) override; virtual bool EncodeSubtitle(const SubtitleBlock *sub) override; @@ -50,11 +50,11 @@ protected: } private: - bool WriteAudioLoop(const TimeRange &time, SampleBufferPtr samples); + bool WriteAudioLoop(const TimeRange &time, const SampleBuffer &samples); QHash time_map_; - QHash audio_map_; + QHash audio_map_; ColorManager* color_manager_; diff --git a/app/task/precache/precachetask.cpp b/app/task/precache/precachetask.cpp index bb1af998f..71cedb900 100644 --- a/app/task/precache/precachetask.cpp +++ b/app/task/precache/precachetask.cpp @@ -97,7 +97,7 @@ bool PreCacheTask::FrameDownloaded(FramePtr frame, const QByteArray &hash, const return true; } -bool PreCacheTask::AudioDownloaded(const TimeRange &range, SampleBufferPtr samples) +bool PreCacheTask::AudioDownloaded(const TimeRange &range, const SampleBuffer &samples) { // Pre-cache doesn't cache any audio diff --git a/app/task/precache/precachetask.h b/app/task/precache/precachetask.h index 78e153744..dcd9369bf 100644 --- a/app/task/precache/precachetask.h +++ b/app/task/precache/precachetask.h @@ -40,7 +40,7 @@ protected: virtual bool FrameDownloaded(FramePtr frame, const QByteArray& hash, const QVector& times) override; - virtual bool AudioDownloaded(const TimeRange& range, SampleBufferPtr samples) override; + virtual bool AudioDownloaded(const TimeRange& range, const SampleBuffer &samples) override; private: Project* project_; diff --git a/app/task/project/import/import.cpp b/app/task/project/import/import.cpp index 352a8f175..150fcaedf 100644 --- a/app/task/project/import/import.cpp +++ b/app/task/project/import/import.cpp @@ -203,7 +203,7 @@ void ProjectImportTask::ValidateImageSequence(Footage *footage, QFileInfoList& i // User has confirmed it is a still image, let's set it accordingly. video_stream.set_video_type(VideoParams::kVideoTypeImageSequence); - rational default_timebase = Config::Current()[QStringLiteral("DefaultSequenceFrameRate")].value(); + rational default_timebase = OLIVE_CONFIG("DefaultSequenceFrameRate").value(); video_stream.set_time_base(default_timebase); video_stream.set_frame_rate(default_timebase.flipped()); @@ -222,7 +222,7 @@ void ProjectImportTask::ValidateImageSequence(Footage *footage, QFileInfoList& i void ProjectImportTask::AddItemToFolder(Folder *folder, Node *item, MultiUndoCommand *command) { // Create undoable command that adds the items to the model - Project* project = folder->project(); + Project* project = folder_->project(); NodeAddCommand* nac = new NodeAddCommand(project, item); nac->PushToThread(project->thread()); diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp index f3cd07d5c..32b214e8b 100644 --- a/app/task/render/render.cpp +++ b/app/task/render/render.cpp @@ -87,7 +87,7 @@ bool RenderTask::Render(ColorManager* manager, rational r; for (int i=0; iterator.GetNext(&r); i++) { if (IsCancelled()) { - return true; + break; } times[i] = r; @@ -97,7 +97,7 @@ bool RenderTask::Render(ColorManager* manager, // Filter out duplicates for (int i=0; iBlocks().at(block_indexes.at(tracks_to_push.at(i))); if (const SubtitleBlock *sub = dynamic_cast(this_block)) { - if (!EncodeSubtitle(sub)) { - result = false; - break; + if (sub->is_enabled()) { + if (!EncodeSubtitle(sub)) { + result = false; + break; + } } } @@ -193,7 +195,7 @@ bool RenderTask::Render(ColorManager* manager, TimeRange range = watcher->property("range").value(); - if (!AudioDownloaded(range, watcher->Get().value())) { + if (!AudioDownloaded(range, watcher->Get().value())) { result = false; } @@ -266,9 +268,14 @@ bool RenderTask::Render(ColorManager* manager, if (IsCancelled() || !result) { // Cancel every watcher we created foreach (RenderTicketWatcher* watcher, running_watchers_) { + watcher->Cancel(); disconnect(watcher, &RenderTicketWatcher::Finished, this, &RenderTask::TicketDone); RenderManager::instance()->RemoveTicket(watcher->GetTicket()); } + + foreach (RenderTicketWatcher* watcher, running_watchers_) { + watcher->WaitForFinished(); + } } watcher_thread.quit(); diff --git a/app/task/render/render.h b/app/task/render/render.h index 75d6c73c0..01909081f 100644 --- a/app/task/render/render.h +++ b/app/task/render/render.h @@ -53,7 +53,7 @@ protected: virtual bool FrameDownloaded(FramePtr frame, const QByteArray& hash, const QVector& times) = 0; - virtual bool AudioDownloaded(const TimeRange& range, SampleBufferPtr samples) = 0; + virtual bool AudioDownloaded(const TimeRange& range, const SampleBuffer &samples) = 0; virtual bool EncodeSubtitle(const SubtitleBlock *subtitle); diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index 32eb98976..cc770e894 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -29,7 +29,7 @@ namespace olive { TimelineMarker::TimelineMarker(QObject *parent) : - color_(Config::Current()[QStringLiteral("MarkerColor")].toInt()) + color_(OLIVE_CONFIG("MarkerColor").toInt()) { setParent(parent); } @@ -204,9 +204,7 @@ void TimelineMarkerList::HandleMarkerTimeChange() auto it = std::find(markers_.begin(), markers_.end(), m); - if ((it+1 != markers_.end() && (*(it+1))->time() < m->time()) - || (it != markers_.begin() && (*(it-1))->time() > m->time())) { - // Re-sort into list + if (it != markers_.end()) { markers_.erase(it); InsertIntoList(m); } @@ -304,8 +302,9 @@ void MarkerChangeNameCommand::undo() marker_->set_name(old_name_); } -MarkerChangeTimeCommand::MarkerChangeTimeCommand(TimelineMarker* marker, TimeRange time) : +MarkerChangeTimeCommand::MarkerChangeTimeCommand(TimelineMarker* marker, const TimeRange &time, const TimeRange &old_time) : marker_(marker), + old_time_(old_time), new_time_(time) { } diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index 492c61b9b..ae7cc4484 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -227,7 +227,10 @@ private: class MarkerChangeTimeCommand : public UndoCommand { public: - MarkerChangeTimeCommand(TimelineMarker* marker, TimeRange time); + MarkerChangeTimeCommand(TimelineMarker* marker, const TimeRange &time, const TimeRange &old_time); + MarkerChangeTimeCommand(TimelineMarker* marker, const TimeRange &time) : + MarkerChangeTimeCommand(marker, time, marker->time_range()) + {} virtual Project* GetRelevantProject() const override; diff --git a/app/ui/style/olive-dark/png/text-edit.128.disabled.png b/app/ui/style/olive-dark/png/text-edit.128.disabled.png new file mode 100644 index 000000000..f64c27582 Binary files /dev/null and b/app/ui/style/olive-dark/png/text-edit.128.disabled.png differ diff --git a/app/ui/style/olive-dark/png/text-edit.128.png b/app/ui/style/olive-dark/png/text-edit.128.png new file mode 100644 index 000000000..a5da296fa Binary files /dev/null and b/app/ui/style/olive-dark/png/text-edit.128.png differ diff --git a/app/ui/style/olive-dark/png/text-edit.16.disabled.png b/app/ui/style/olive-dark/png/text-edit.16.disabled.png new file mode 100644 index 000000000..0ebeb13f7 Binary files /dev/null and b/app/ui/style/olive-dark/png/text-edit.16.disabled.png differ diff --git a/app/ui/style/olive-dark/png/text-edit.16.png b/app/ui/style/olive-dark/png/text-edit.16.png new file mode 100644 index 000000000..c34850025 Binary files /dev/null and b/app/ui/style/olive-dark/png/text-edit.16.png differ diff --git a/app/ui/style/olive-dark/png/text-edit.32.disabled.png b/app/ui/style/olive-dark/png/text-edit.32.disabled.png new file mode 100644 index 000000000..336da94d0 Binary files /dev/null and b/app/ui/style/olive-dark/png/text-edit.32.disabled.png differ diff --git a/app/ui/style/olive-dark/png/text-edit.32.png b/app/ui/style/olive-dark/png/text-edit.32.png new file mode 100644 index 000000000..37cfb6059 Binary files /dev/null and b/app/ui/style/olive-dark/png/text-edit.32.png differ diff --git a/app/ui/style/olive-dark/png/text-edit.64.disabled.png b/app/ui/style/olive-dark/png/text-edit.64.disabled.png new file mode 100644 index 000000000..b4cb5b566 Binary files /dev/null and b/app/ui/style/olive-dark/png/text-edit.64.disabled.png differ diff --git a/app/ui/style/olive-dark/png/text-edit.64.png b/app/ui/style/olive-dark/png/text-edit.64.png new file mode 100644 index 000000000..24693b751 Binary files /dev/null and b/app/ui/style/olive-dark/png/text-edit.64.png differ diff --git a/app/ui/style/olive-dark/svg/text-edit.svg b/app/ui/style/olive-dark/svg/text-edit.svg new file mode 100644 index 000000000..a63d30511 --- /dev/null +++ b/app/ui/style/olive-dark/svg/text-edit.svg @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Martin Ruskov + + + http://commons.wikimedia.org/wiki/Tango_icon + + + + + + + + + + + + + + + + + diff --git a/app/ui/style/olive-light/png/text-edit.128.disabled.png b/app/ui/style/olive-light/png/text-edit.128.disabled.png new file mode 100644 index 000000000..725316db9 Binary files /dev/null and b/app/ui/style/olive-light/png/text-edit.128.disabled.png differ diff --git a/app/ui/style/olive-light/png/text-edit.128.png b/app/ui/style/olive-light/png/text-edit.128.png new file mode 100644 index 000000000..075fb7fd5 Binary files /dev/null and b/app/ui/style/olive-light/png/text-edit.128.png differ diff --git a/app/ui/style/olive-light/png/text-edit.16.disabled.png b/app/ui/style/olive-light/png/text-edit.16.disabled.png new file mode 100644 index 000000000..edaaf4c9d Binary files /dev/null and b/app/ui/style/olive-light/png/text-edit.16.disabled.png differ diff --git a/app/ui/style/olive-light/png/text-edit.16.png b/app/ui/style/olive-light/png/text-edit.16.png new file mode 100644 index 000000000..083099b6d Binary files /dev/null and b/app/ui/style/olive-light/png/text-edit.16.png differ diff --git a/app/ui/style/olive-light/png/text-edit.32.disabled.png b/app/ui/style/olive-light/png/text-edit.32.disabled.png new file mode 100644 index 000000000..71cad0b10 Binary files /dev/null and b/app/ui/style/olive-light/png/text-edit.32.disabled.png differ diff --git a/app/ui/style/olive-light/png/text-edit.32.png b/app/ui/style/olive-light/png/text-edit.32.png new file mode 100644 index 000000000..772eb2ebf Binary files /dev/null and b/app/ui/style/olive-light/png/text-edit.32.png differ diff --git a/app/ui/style/olive-light/png/text-edit.64.disabled.png b/app/ui/style/olive-light/png/text-edit.64.disabled.png new file mode 100644 index 000000000..b066cd6c0 Binary files /dev/null and b/app/ui/style/olive-light/png/text-edit.64.disabled.png differ diff --git a/app/ui/style/olive-light/png/text-edit.64.png b/app/ui/style/olive-light/png/text-edit.64.png new file mode 100644 index 000000000..84f1caa08 Binary files /dev/null and b/app/ui/style/olive-light/png/text-edit.64.png differ diff --git a/app/ui/style/olive-light/svg/text-edit.svg b/app/ui/style/olive-light/svg/text-edit.svg new file mode 100644 index 000000000..fcc472610 --- /dev/null +++ b/app/ui/style/olive-light/svg/text-edit.svg @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Martin Ruskov + + + http://commons.wikimedia.org/wiki/Tango_icon + + + + + + + + + + + + + + + + + diff --git a/app/ui/style/style.cpp b/app/ui/style/style.cpp index a63845984..af255fa38 100644 --- a/app/ui/style/style.cpp +++ b/app/ui/style/style.cpp @@ -136,7 +136,7 @@ void StyleManager::Init() available_themes_.insert(QStringLiteral("olive-dark"), QStringLiteral("Olive Dark")); available_themes_.insert(QStringLiteral("olive-light"), QStringLiteral("Olive Light")); - QString config_style = Config::Current()["Style"].toString(); + QString config_style = OLIVE_CONFIG("Style").toString(); if (config_style.isEmpty() || !available_themes_.contains(config_style)) { SetStyle(kDefaultStyle); diff --git a/app/widget/audiomonitor/audiomonitor.cpp b/app/widget/audiomonitor/audiomonitor.cpp index 6a196e095..982bb152a 100644 --- a/app/widget/audiomonitor/audiomonitor.cpp +++ b/app/widget/audiomonitor/audiomonitor.cpp @@ -37,7 +37,6 @@ QVector AudioMonitor::instances_; AudioMonitor::AudioMonitor(QWidget *parent) : QOpenGLWidget(parent), - file_(nullptr), waveform_(nullptr), cached_channels_(0) { @@ -70,15 +69,13 @@ void AudioMonitor::SetParams(const AudioParams ¶ms) void AudioMonitor::Stop() { - delete file_; - file_ = nullptr; waveform_ = nullptr; // We don't stop the update loop here so that the monitor can show a smooth fade out. The update // loop will stop itself since file_ and waveform_ are null. } -void AudioMonitor::PushBytes(const QByteArray &d) +void AudioMonitor::PushSampleBuffer(const SampleBuffer &d) { if (!params_.channel_count()) { return; @@ -86,9 +83,12 @@ void AudioMonitor::PushBytes(const QByteArray &d) QVector v(params_.channel_count(), 0); - BytesToSampleSummary(d, v); + AudioVisualWaveform::Sample summed = AudioVisualWaveform::SumSamples(d, 0, d.sample_count()); - PushValue(v); + AudioVisualWaveformSampleToInternalValues(summed, v); + + // Fill values because they get averaged out for smoothing + values_.fill(v); SetUpdateLoop(true); } @@ -236,13 +236,7 @@ void AudioMonitor::paintGL() delta_time *= abs_speed; } - if (file_) { - UpdateValuesFromFile(v, delta_time); - - if (file_->atEnd()) { - Stop(); - } - } else if (waveform_) { + if (waveform_) { UpdateValuesFromWaveform(v, delta_time); if (waveform_time_ >= waveform_->length()) { @@ -304,30 +298,6 @@ void AudioMonitor::mousePressEvent(QMouseEvent *) update(); } -void AudioMonitor::UpdateValuesFromFile(QVector& v, qint64 delta_time) -{ - // Convert ms to float seconds and determine how many bytes that is - qint64 bytes_to_read = params_.time_to_bytes(static_cast(delta_time) * 0.001); - - if (playback_speed_ < 0) { - // If reversing, jump back by the amount of bytes we're going to read - bytes_to_read = qMin(bytes_to_read, file_->pos()); - - file_->seek(file_->pos() - bytes_to_read); - } - - // Read bytes in from file - QByteArray b = file_->read(bytes_to_read); - - if (playback_speed_ < 0) { - // If reversing, head back to where we were before the read so that the next read starts - // from where we left off - file_->seek(file_->pos() - bytes_to_read); - } - - BytesToSampleSummary(b, v); -} - void AudioMonitor::UpdateValuesFromWaveform(QVector &v, qint64 delta_time) { // Delta time is provided in milliseconds, so we convert to seconds in rational @@ -335,18 +305,23 @@ void AudioMonitor::UpdateValuesFromWaveform(QVector &v, qint64 delta_tim AudioVisualWaveform::Sample sum = waveform_->GetSummaryFromTime(waveform_time_, length); - for (int i=0; i v.at(output_index)) { - v[output_index] = max; - } - } + AudioVisualWaveformSampleToInternalValues(sum, v); waveform_time_ += length; } +void AudioMonitor::AudioVisualWaveformSampleToInternalValues(const AudioVisualWaveform::Sample &in, QVector &out) +{ + for (int i=0; i out.at(output_index)) { + out[output_index] = max; + } + } +} + void AudioMonitor::PushValue(const QVector &v) { int lim = values_.size()-1; diff --git a/app/widget/audiomonitor/audiomonitor.h b/app/widget/audiomonitor/audiomonitor.h index e9bdf05b3..3c5e27d81 100644 --- a/app/widget/audiomonitor/audiomonitor.h +++ b/app/widget/audiomonitor/audiomonitor.h @@ -42,7 +42,7 @@ public: bool IsPlaying() const { - return file_ || waveform_; + return waveform_; } static void StartWaveformOnAll(const AudioVisualWaveform *waveform, const rational& start, int playback_speed) @@ -59,10 +59,10 @@ public: } } - static void PushBytesOnAll(const QByteArray &d) + static void PushSampleBufferOnAll(const SampleBuffer &d) { foreach (AudioMonitor *m, instances_) { - m->PushBytes(d); + m->PushSampleBuffer(d); } } @@ -71,7 +71,7 @@ public slots: void Stop(); - void PushBytes(const QByteArray& d); + void PushSampleBuffer(const SampleBuffer &samples); void StartWaveform(const AudioVisualWaveform *waveform, const rational& start, int playback_speed); @@ -83,10 +83,10 @@ protected: private: void SetUpdateLoop(bool e); - void UpdateValuesFromFile(QVector &v, qint64 delta_time); - void UpdateValuesFromWaveform(QVector &v, qint64 delta_time); + void AudioVisualWaveformSampleToInternalValues(const AudioVisualWaveform::Sample &in, QVector &out); + void PushValue(const QVector& v); void BytesToSampleSummary(const QByteArray& bytes, QVector& v); @@ -95,7 +95,6 @@ private: AudioParams params_; - QIODevice* file_; qint64 last_time_; const AudioVisualWaveform* waveform_; diff --git a/app/widget/colorwheel/colorvalueswidget.cpp b/app/widget/colorwheel/colorvalueswidget.cpp index d65cf6b33..cb0218395 100644 --- a/app/widget/colorwheel/colorvalueswidget.cpp +++ b/app/widget/colorwheel/colorvalueswidget.cpp @@ -233,7 +233,7 @@ ColorValuesTab::ColorValuesTab(bool with_legacy_option, QWidget *parent) : if (with_legacy_option) { legacy_box_ = new QCheckBox(tr("Use legacy (8-bit) values")); - legacy_box_->setChecked(Config::Current()[QStringLiteral("UseLegacyColorInInputTab")].toBool()); + legacy_box_->setChecked(OLIVE_CONFIG("UseLegacyColorInInputTab").toBool()); connect(legacy_box_, &QCheckBox::clicked, this, &ColorValuesTab::LegacyChanged); layout->addWidget(legacy_box_, row, 0, 1, 2); row++; @@ -356,7 +356,7 @@ void ColorValuesTab::SliderChanged() void ColorValuesTab::LegacyChanged(bool legacy) { - Config::Current()[QStringLiteral("UseLegacyColorInInputTab")] = legacy; + OLIVE_CONFIG("UseLegacyColorInInputTab") = legacy; double legacy_multiplier = legacy ? kLegacyMultiplier : 1.0/kLegacyMultiplier; int decimal_places = legacy ? 0 : 5; diff --git a/app/widget/handmovableview/handmovableview.cpp b/app/widget/handmovableview/handmovableview.cpp index 5e0daa151..87086dcdc 100644 --- a/app/widget/handmovableview/handmovableview.cpp +++ b/app/widget/handmovableview/handmovableview.cpp @@ -32,7 +32,7 @@ namespace olive { HandMovableView::HandMovableView(QWidget* parent) : super(parent), dragging_hand_(false), - scroll_zooms_by_default_(Config::Current()[QStringLiteral("ScrollZooms")].toBool()) + scroll_zooms_by_default_(OLIVE_CONFIG("ScrollZooms").toBool()) { connect(Core::instance(), &Core::ToolChanged, this, &HandMovableView::ApplicationToolChanged); } diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index f812c121e..2f97d8133 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -155,10 +155,11 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : SetScale(120); // Pickup on widget focus changes - connect(qApp, + // DISABLED - we now just handle this with item/titlebar clicking (see ToggleSelect) + /*connect(qApp, &QApplication::focusChanged, this, - &NodeParamView::FocusChanged); + &NodeParamView::FocusChanged);*/ } NodeParamView::~NodeParamView() @@ -297,12 +298,25 @@ void NodeParamView::ItemAboutToBeRemoved(NodeParamViewItem *item) } } - if (focused_node_ == item) { - focused_node_ = nullptr; - emit FocusedNodeChanged(nullptr); + QVector copy = selected_nodes_; + if (copy.removeOne(item)) { + SetSelectedNodes(copy); } } +void NodeParamView::ItemClicked() +{ + ToggleSelect(static_cast(sender())); +} + +void NodeParamView::SelectNodeFromConnectedLink(Node *node) +{ + NodeParamViewItem *item = static_cast(sender()); + + Node::ContextPair p = {node, item->GetContext()}; + SetSelectedNodes({p}); +} + void NodeParamView::SetContexts(const QVector &contexts) { // Setting contexts is expensive, so we queue it here to prevent multiple calls in a short timespan @@ -371,41 +385,138 @@ Node *NodeParamView::GetTimeTarget() const return time_target_; } +void ReconnectOutputsIfNotDeletingNode(MultiUndoCommand *c, NodeViewDeleteCommand *dc, Node *output, Node *deleting, Node *context) +{ + for (auto it=deleting->output_connections().cbegin(); it!=deleting->output_connections().cend(); it++) { + const NodeInput &proposed_reconnect = it->second; + + if (dc->ContainsNode(proposed_reconnect.node(), context)) { + // Uh-oh we're deleting this node too, instead connect to its outputs + ReconnectOutputsIfNotDeletingNode(c, dc, output, proposed_reconnect.node(), context); + } else { + c->add_child(new NodeEdgeAddCommand(output, it->second)); + } + } +} + void NodeParamView::DeleteSelected() { if (keyframe_view_ && keyframe_view_->hasFocus()) { keyframe_view_->DeleteSelected(); - } else if (focused_node_) { + } else if (!selected_nodes_.isEmpty()) { MultiUndoCommand *c = new MultiUndoCommand(); - Node *n = focused_node_->GetNode(); // Create command to delete node from context and/or graph NodeViewDeleteCommand *dc = new NodeViewDeleteCommand(); - dc->AddNode(n, focused_node_->GetContext()); c->add_child(dc); - // Copy any outputs that were connected - if (n->GetEffectInput().IsValid()) { - if (Node *out = n->GetEffectInput().GetConnectedOutput()) { - for (auto it=n->output_connections().cbegin(); it!=n->output_connections().cend(); it++) { - c->add_child(new NodeEdgeAddCommand(out, it->second)); - } - } + // Add all nodes + foreach (NodeParamViewItem *item, selected_nodes_) { + Node *n = item->GetNode(); + dc->AddNode(n, item->GetContext()); } + // Make reconnections where possible + foreach (NodeParamViewItem *item, selected_nodes_) { + Node *n = item->GetNode(); + + Node *node_being_deleted = n; + Node *connected_to_effect_input = n; + + while (true) { + if (node_being_deleted->GetEffectInput().IsValid()) { + if ((connected_to_effect_input = node_being_deleted->GetEffectInput().GetConnectedOutput())) { + if (dc->ContainsNode(connected_to_effect_input, item->GetContext())) { + // Node's getting deleted, recurse + node_being_deleted = connected_to_effect_input; + continue; + } + } + } + + break; + } + + if (connected_to_effect_input) { + ReconnectOutputsIfNotDeletingNode(c, dc, connected_to_effect_input, n, item->GetContext()); + } + } Core::instance()->undo_stack()->push(c); } } -void NodeParamView::SelectNodes(const QVector &nodes) +void NodeParamView::SetSelectedNodes(const QVector &nodes, bool handle_focused_node, bool emit_signal) { - // Do nothing, this is a placeholder if we ever need this to do anything in the future + if (handle_focused_node) { + handle_focused_node = !focused_node_ || selected_nodes_.contains(focused_node_); + } + + foreach (NodeParamViewItem *n, selected_nodes_) { + n->SetHighlighted(false); + } + + selected_nodes_ = nodes; + + QVector p; + if (emit_signal) { + p.resize(selected_nodes_.size()); + } + + for (int i=0; iSetHighlighted(true); + + if (emit_signal) { + p[i] = {n->GetNode(), n->GetContext()}; + } + } + + if (handle_focused_node) { + focused_node_ = nullptr; + + foreach (NodeParamViewItem *n, selected_nodes_) { + if (n->GetNode()->HasGizmos()) { + focused_node_ = n; + break; + } + } + + Node *n = focused_node_ ? focused_node_->GetNode() : nullptr; + emit FocusedNodeChanged(n); + } + + if (emit_signal) { + emit SelectedNodesChanged(p); + } } -void NodeParamView::DeselectNodes(const QVector &nodes) +void NodeParamView::SetSelectedNodes(const QVector &nodes, bool emit_signal) { - // Do nothing, this is a placeholder if we ever need this to do anything in the future + QVector items; + + foreach (const Node::ContextPair &n, nodes) { + for (auto it=context_items_.cbegin(); it!=context_items_.cend(); it++) { + NodeParamViewContext *ctx = *it; + + NodeParamViewItem *item = ctx->GetItem(n.node, n.context); + + if (item) { + items.append(item); + } + } + } + + SetSelectedNodes(items, true, emit_signal); + + if (!selected_nodes_.empty()) { + NodeParamViewItem *scrolled_to = selected_nodes_.front(); + param_scroll_area_->ensureWidgetVisible(scrolled_to, 0, 0); + + QPoint viewport_pos = scrolled_to->mapTo(param_scroll_area_, scrolled_to->geometry().topLeft()); + + param_scroll_area_->verticalScrollBar()->setValue(viewport_pos.y()); + } } void NodeParamView::UpdateItemTime(const rational &time) @@ -461,9 +572,10 @@ void NodeParamView::AddNode(Node *n, Node *ctx, NodeParamViewContext *context) NodeParamViewItem* item = new NodeParamViewItem(n, IsGroupMode() ? kCheckBoxesOnNonConnected : kNoCheckBoxes, context); connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::SetTimeAndSignal); - connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode); + connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::SelectNodeFromConnectedLink); connect(item, &NodeParamViewItem::PinToggled, this, &NodeParamView::PinNode); connect(item, &NodeParamViewItem::InputCheckedChanged, this, &NodeParamView::InputCheckBoxChanged); + connect(item, &NodeParamViewItem::Clicked, this, &NodeParamView::ItemClicked); item->SetContext(ctx); item->SetTimeTarget(GetTimeTarget()); @@ -474,9 +586,7 @@ void NodeParamView::AddNode(Node *n, Node *ctx, NodeParamViewContext *context) if (!focused_node_ && n->HasGizmos()) { // We'll focus this node now - item->SetHighlighted(true); - focused_node_ = item; - emit FocusedNodeChanged(n); + SetSelectedNodes({item}); } if (keyframe_view_) { @@ -561,6 +671,36 @@ NodeParamViewContext *NodeParamView::GetContextItemFromContext(Node *ctx) return context_items_.at(ctx_type); } +void NodeParamView::ToggleSelect(NodeParamViewItem *item) +{ + QVector new_sel; + + if (qApp->keyboardModifiers() & Qt::ShiftModifier) { + new_sel = selected_nodes_; + } + + if (selected_nodes_.contains(item)) { + // De-select this node + if (qApp->keyboardModifiers() & Qt::ShiftModifier) { + new_sel.removeOne(item); + SetSelectedNodes(new_sel, true); + } + } else { + new_sel.append(item); + SetSelectedNodes(new_sel, false); + + if (item->GetNode()->HasGizmos() || !new_sel.contains(focused_node_)) { + if (item->GetNode()->HasGizmos()) { + focused_node_ = item; + } else { + focused_node_ = nullptr; + } + + emit FocusedNodeChanged(focused_node_ ? focused_node_->GetNode() : nullptr); + } + } +} + void NodeParamView::UpdateGlobalScrollBar() { if (keyframe_view_) { @@ -584,7 +724,7 @@ void NodeParamView::PinNode(bool pin) } } -void NodeParamView::FocusChanged(QWidget* old, QWidget* now) +/*void NodeParamView::FocusChanged(QWidget* old, QWidget* now) { Q_UNUSED(old) @@ -592,39 +732,28 @@ void NodeParamView::FocusChanged(QWidget* old, QWidget* now) while (parent) { if (NodeParamViewItem* item = dynamic_cast(parent)) { - if (item != focused_node_) { - // Found a NodeParamViewItem that isn't already focused, see if it belongs to us - bool ours = false; + // Found a NodeParamViewItem that isn't already focused, see if it belongs to us + bool ours = false; - do { - parent = parent->parent(); + do { + parent = parent->parent(); - if (parent == this) { - ours = true; - break; - } - } while (parent); - - if (ours) { - // This item is ours, - if (focused_node_) { - // De-focus current node - focused_node_->SetHighlighted(false); - } - - focused_node_ = item; - - item->SetHighlighted(true); - - emit FocusedNodeChanged(item->GetNode()); + if (parent == this) { + ours = true; + break; } + } while (parent); + + if (ours) { + //ToggleSelect(item); + Q_UNUSED(item) } break; } parent = parent->parent(); } -} +}*/ void NodeParamView::KeyframeViewDragged(int x, int y) { diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index f04072e52..3b90e22b7 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -61,8 +61,8 @@ public: keyframe_view_->DeselectAll(); } - void SelectNodes(const QVector &nodes); - void DeselectNodes(const QVector &nodes); + void SetSelectedNodes(const QVector &nodes, bool handle_focused_node = true, bool emit_signal = true); + void SetSelectedNodes(const QVector &nodes, bool emit_signal = true); const QVector &GetContexts() const { @@ -75,10 +75,10 @@ public slots: void UpdateElementY(); signals: - void RequestSelectNode(const QVector& target); - void FocusedNodeChanged(Node* n); + void SelectedNodesChanged(const QVector &nodes); + protected: virtual void resizeEvent(QResizeEvent *event) override; @@ -118,6 +118,8 @@ private: return contexts_.size() == 1 && dynamic_cast(contexts_.first()); } + void ToggleSelect(NodeParamViewItem *item); + KeyframeView* keyframe_view_; QVector context_items_; @@ -137,6 +139,7 @@ private: QVector active_nodes_; NodeParamViewItem* focused_node_; + QVector selected_nodes_; Node *time_target_; @@ -150,7 +153,7 @@ private slots: void PinNode(bool pin); - void FocusChanged(QWidget *old, QWidget *now); + //void FocusChanged(QWidget *old, QWidget *now); void KeyframeViewDragged(int x, int y); @@ -168,6 +171,10 @@ private slots: void ItemAboutToBeRemoved(NodeParamViewItem *item); + void ItemClicked(); + + void SelectNodeFromConnectedLink(Node *node); + }; } diff --git a/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp b/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp index 2810113a1..d55fbe103 100644 --- a/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp +++ b/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp @@ -135,7 +135,7 @@ void NodeParamViewConnectedLabel::ShowLabelContextMenu() void NodeParamViewConnectedLabel::ConnectionClicked() { if (connected_node_) { - emit RequestSelectNode({connected_node_}); + emit RequestSelectNode(connected_node_); } } diff --git a/app/widget/nodeparamview/nodeparamviewconnectedlabel.h b/app/widget/nodeparamview/nodeparamviewconnectedlabel.h index 621a697e1..3e4fc5f55 100644 --- a/app/widget/nodeparamview/nodeparamviewconnectedlabel.h +++ b/app/widget/nodeparamview/nodeparamviewconnectedlabel.h @@ -35,7 +35,7 @@ public: void SetTime(const rational &time); signals: - void RequestSelectNode(const QVector& node); + void RequestSelectNode(Node *n); private slots: void InputConnected(Node *output, const NodeInput &input); diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 9a61e0718..9cba6f83c 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -46,21 +46,22 @@ const int NodeParamViewItemBody::kMaxWidgetColumn = kKeyControlColumn; NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) : super(parent), + body_(nullptr), node_(node), + create_checkboxes_(create_checkboxes), ctx_(nullptr) { node_->Retranslate(); // Create and add contents widget - body_ = new NodeParamViewItemBody(node_, create_checkboxes); - connect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode); - connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); - connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged); - connect(body_, &NodeParamViewItemBody::InputCheckedChanged, this, &NodeParamViewItem::InputCheckedChanged); - SetBody(body_); + RecreateBody(); connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate); + // FIXME: Implemented to pick up when an input is set to hidden or not - DEFINITELY not a fast + // way of doing this, but "fine" for now. + connect(node_, &Node::InputFlagsChanged, this, &NodeParamViewItem::RecreateBody); + setBackgroundRole(QPalette::Window); // Connect title bar enabled checkbox @@ -80,6 +81,23 @@ void NodeParamViewItem::Retranslate() body_->Retranslate(); } +void NodeParamViewItem::RecreateBody() +{ + QWidget *old_body = body_; + + body_ = new NodeParamViewItemBody(node_, create_checkboxes_); + connect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode); + connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); + connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged); + connect(body_, &NodeParamViewItemBody::InputCheckedChanged, this, &NodeParamViewItem::InputCheckedChanged); + body_->Retranslate(); + body_->SetTime(time_); + body_->SetTimebase(timebase_); + SetBody(body_); + + old_body->deleteLater(); +} + int NodeParamViewItem::GetElementY(const NodeInput &c) const { if (IsExpanded()) { diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 95ee238d8..1653a5ada 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -67,7 +67,7 @@ public: signals: void RequestSetTime(const rational& time); - void RequestSelectNode(const QVector& node); + void RequestSelectNode(Node *node); void ArrayExpandedChanged(bool e); @@ -179,6 +179,8 @@ public: void SetTimebase(const rational& timebase) { + timebase_ = timebase; + body_->SetTimebase(timebase); } @@ -214,7 +216,7 @@ public: signals: void RequestSetTime(const rational& time); - void RequestSelectNode(const QVector& node); + void RequestSelectNode(Node *node); void ArrayExpandedChanged(bool e); @@ -228,12 +230,18 @@ private: Node* node_; + NodeParamViewCheckBoxBehavior create_checkboxes_; + Node *ctx_; rational time_; + rational timebase_; KeyframeView::NodeConnections keyframe_connections_; +private slots: + void RecreateBody(); + }; } diff --git a/app/widget/nodeparamview/nodeparamviewitembase.cpp b/app/widget/nodeparamview/nodeparamviewitembase.cpp index f29d70702..645ee79f1 100644 --- a/app/widget/nodeparamview/nodeparamviewitembase.cpp +++ b/app/widget/nodeparamview/nodeparamviewitembase.cpp @@ -40,6 +40,7 @@ NodeParamViewItemBase::NodeParamViewItemBase(QWidget *parent) : // Connect title bar to this connect(title_bar_, &NodeParamViewItemTitleBar::ExpandedStateChanged, this, &NodeParamViewItemBase::SetExpanded); connect(title_bar_, &NodeParamViewItemTitleBar::PinToggled, this, &NodeParamViewItemBase::PinToggled); + connect(title_bar_, &NodeParamViewItemTitleBar::Clicked, this, &NodeParamViewItemBase::Clicked); // Use dummy QWidget to retain width when not expanded (QDockWidget seems to ignore the titlebar // size hints and will shrink as small as possible if the body is hidden) @@ -115,4 +116,11 @@ void NodeParamViewItemBase::moveEvent(QMoveEvent *event) emit Moved(); } +void NodeParamViewItemBase::mousePressEvent(QMouseEvent *e) +{ + super::mousePressEvent(e); + + emit Clicked(); +} + } diff --git a/app/widget/nodeparamview/nodeparamviewitembase.h b/app/widget/nodeparamview/nodeparamviewitembase.h index d5f570656..a728bb93f 100644 --- a/app/widget/nodeparamview/nodeparamviewitembase.h +++ b/app/widget/nodeparamview/nodeparamviewitembase.h @@ -60,6 +60,8 @@ signals: void Moved(); + void Clicked(); + protected: void SetBody(QWidget *body); @@ -74,6 +76,8 @@ protected: virtual void moveEvent(QMoveEvent *event) override; + virtual void mousePressEvent(QMouseEvent *e) override; + protected slots: virtual void Retranslate(){} diff --git a/app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp b/app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp index 1d79ff4d9..efe3d2768 100644 --- a/app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp +++ b/app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp @@ -85,6 +85,13 @@ void NodeParamViewItemTitleBar::paintEvent(QPaintEvent *event) } } +void NodeParamViewItemTitleBar::mousePressEvent(QMouseEvent *event) +{ + QWidget::mousePressEvent(event); + + emit Clicked(); +} + void NodeParamViewItemTitleBar::mouseDoubleClickEvent(QMouseEvent *event) { QWidget::mouseDoubleClickEvent(event); diff --git a/app/widget/nodeparamview/nodeparamviewitemtitlebar.h b/app/widget/nodeparamview/nodeparamviewitemtitlebar.h index 9bea7f06f..7024c9e82 100644 --- a/app/widget/nodeparamview/nodeparamviewitemtitlebar.h +++ b/app/widget/nodeparamview/nodeparamviewitemtitlebar.h @@ -79,9 +79,12 @@ signals: void EnabledCheckBoxClicked(bool e); + void Clicked(); + protected: virtual void paintEvent(QPaintEvent *event) override; + virtual void mousePressEvent(QMouseEvent *event) override; virtual void mouseDoubleClickEvent(QMouseEvent *event) override; private: diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index e030d1b15..c25848f03 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -547,22 +547,33 @@ void NodeParamViewWidgetBridge::SetProperty(const QString &key, const QVariant & NodeValue::Type data_type = GetDataType(); // Parameters for all types - if (key == QStringLiteral("enabled")) { - foreach (QWidget* w, widgets_) { - w->setEnabled(value.toBool()); + bool key_is_disable = key.startsWith(QStringLiteral("disable")); + if (key_is_disable || key.startsWith(QStringLiteral("enabled"))) { + + bool e = value.toBool(); + if (key_is_disable) { + e = !e; } + + if (key.size() == 7) { // just the word "disable" or "enabled" + for (int i=0; isetEnabled(e); + } + } else { // set specific track/widget + bool ok; + int element = key.midRef(7).toInt(&ok); + int tracks = NodeValue::get_number_of_keyframe_tracks(data_type); + + if (ok && element >= 0 && element < tracks) { + widgets_.at(element)->setEnabled(e); + } + } + } - // Parameters for vectors only - if (NodeValue::type_is_vector(data_type)) { - if (key == QStringLiteral("disablex")) { - static_cast(widgets_.at(0))->setEnabled(!value.toBool()); - } else if (key == QStringLiteral("disabley")) { - static_cast(widgets_.at(1))->setEnabled(!value.toBool()); - } else if (widgets_.size() > 2 && key == QStringLiteral("disablez")) { - static_cast(widgets_.at(2))->setEnabled(!value.toBool()); - } else if (widgets_.size() > 3 && key == QStringLiteral("disablew")) { - static_cast(widgets_.at(3))->setEnabled(!value.toBool()); + if (key == QStringLiteral("tooltip")) { + for (int i = 0; i < widgets_.size(); i++) { + widgets_.at(i)->setToolTip(value.toString()); } } @@ -645,6 +656,7 @@ void NodeParamViewWidgetBridge::SetProperty(const QString &key, const QVariant & break; } } else if (key == QStringLiteral("offset")) { + int tracks = NodeValue::get_number_of_keyframe_tracks(data_type); QVector offsets = NodeValue::split_normal_value_into_track_values(data_type, value); @@ -654,6 +666,33 @@ void NodeParamViewWidgetBridge::SetProperty(const QString &key, const QVariant & } UpdateWidgetValues(); + + } else if (key.startsWith(QStringLiteral("color"))) { + + QColor c(value.toString()); + + int tracks = NodeValue::get_number_of_keyframe_tracks(data_type); + + if (key.size() == 5) { + // Set for all tracks + for (int i=0; i(widgets_.at(i))->SetColor(c); + } + } else { + bool ok; + int element = key.midRef(5).toInt(&ok); + if (ok && element >= 0 && element < tracks) { + static_cast(widgets_.at(element))->SetColor(c); + } + } + + } else if (key == QStringLiteral("base")) { + + double d = value.toDouble(); + for (int i=0; i(widgets_.at(i))->SetDragMultiplier(d); + } + } } diff --git a/app/widget/nodetableview/nodetableview.cpp b/app/widget/nodetableview/nodetableview.cpp index 174b3df3e..8981552fa 100644 --- a/app/widget/nodetableview/nodetableview.cpp +++ b/app/widget/nodetableview/nodetableview.cpp @@ -151,7 +151,7 @@ void NodeTableView::SetTime(const rational &time) } default: { - QVector split_values = NodeValue::split_normal_value_into_track_values(node->GetInputDataType(l.key()), value.data()); + QVector split_values = value.to_split_value(); for (int k=0;ksetText(2 + k, NodeValue::ValueToString(value.type(), split_values.at(k), true)); } diff --git a/app/widget/nodevaluetree/nodevaluetree.cpp b/app/widget/nodevaluetree/nodevaluetree.cpp index 689330237..2f263b125 100644 --- a/app/widget/nodevaluetree/nodevaluetree.cpp +++ b/app/widget/nodevaluetree/nodevaluetree.cpp @@ -53,7 +53,7 @@ void NodeValueTree::SetNode(const NodeInput &input, const rational &time) setItemWidget(item, 0, radio); item->setText(1, NodeValue::GetPrettyDataTypeName(value.type())); - item->setText(2, NodeValue::ValueToString(value.type(), value.data(), false)); + item->setText(2, NodeValue::ValueToString(value, false)); item->setText(3, value.source()->GetLabelAndName()); } } diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 07251995d..f8af45ca6 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -51,7 +51,8 @@ NodeView::NodeView(QWidget *parent) : create_edge_output_item_(nullptr), create_edge_input_item_(nullptr), overlay_view_(nullptr), - scale_(1.0) + scale_(1.0), + dont_emit_selection_signals_(false) { setScene(&scene_); SetDefaultDragMode(RubberBandDrag); @@ -171,9 +172,11 @@ void NodeView::DeselectAll() // Just emit all the nodes that are currently selected as no longer selected emit NodesDeselected(selected_nodes_); selected_nodes_.clear(); + emit NodeSelectionChanged(selected_nodes_); + emit NodeSelectionChangedWithContexts(QVector()); } -void NodeView::Select(const QVector &nodes, bool center_view_on_item) +void NodeView::Select(const QVector &nodes, bool center_view_on_item) { // Optimization: rather than respond to every single item being selected, ignore the signal and // then handle them all at the end. @@ -184,18 +187,27 @@ void NodeView::Select(const QVector &nodes, bool center_view_on_item) scene_.DeselectAll(); - foreach (NodeViewContext *context, scene_.context_map()) { - context->Select(nodes); + foreach (const Node::ContextPair &p, nodes) { + NodeViewContext *ctx = scene_.context_map().value(p.context); + if (ctx) { + NodeViewItem *item = ctx->GetItemFromMap(p.node); + if (item) { + item->setSelected(true); + } + } } // Center on something if (center_view_on_item && !nodes.isEmpty()) { - QMetaObject::invokeMethod(this, "CenterOnNode", Qt::QueuedConnection, OLIVE_NS_ARG(Node*, nodes.first())); + QMetaObject::invokeMethod(this, "CenterOnNode", Qt::QueuedConnection, OLIVE_NS_ARG(Node*, nodes.first().node)); } ConnectSelectionChangedSignal(); + // Don't signal when this function was likely triggered from another widget's signal anyway + dont_emit_selection_signals_ = true; UpdateSelectionCache(); + dont_emit_selection_signals_ = false; } void NodeView::CopySelected(bool cut) @@ -559,6 +571,11 @@ void NodeView::mouseMoveEvent(QMouseEvent *event) } else { // Otherwise, we may have to iterate to find a valid one for (const QString& input : attached_node->inputs()) { + if (input == Node::kEnabledInput) { + // Ignore enabled input + continue; + } + NodeInput i(attached_node, input); if (attached_node->IsInputConnectable(input)) { @@ -734,13 +751,18 @@ void NodeView::UpdateSelectionCache() QVector selected; QVector deselected; + QVector sel_with_ctx(current_selection.size()); + // Determine which nodes are newly selected - foreach (NodeViewItem* i, current_selection) { + for (int j=0; jGetNode(); if (!selected_nodes_.contains(n)) { selected.append(n); selected_nodes_.append(n); } + + sel_with_ctx[j] = {n, i->GetContext()}; } // Determine which nodes are newly deselected @@ -773,6 +795,11 @@ void NodeView::UpdateSelectionCache() if (!selected.isEmpty()) { emit NodesSelected(selected); } + + if (!dont_emit_selection_signals_) { + emit NodeSelectionChanged(selected_nodes_); + emit NodeSelectionChangedWithContexts(sel_with_ctx); + } } void NodeView::ShowContextMenu(const QPoint &pos) @@ -1284,7 +1311,7 @@ void NodeView::UngroupNodes() return; } - NodeGroup *group; + NodeGroup *group = nullptr; foreach (NodeViewItem *i, items) { if ((group = dynamic_cast(i->GetNode()))) { group_item = i; @@ -1344,6 +1371,8 @@ void NodeView::ShowNodeProperties() overlay_view_->setFocus(); emit NodesDeselected(selected_nodes_); + emit NodeSelectionChanged(QVector()); + emit NodeSelectionChangedWithContexts(QVector()); overlay_view_->SelectAll(); emit NodeGroupOpened(group); diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 26894511e..d18d2d185 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -77,7 +77,7 @@ public: void SelectAll(); void DeselectAll(); - void Select(const QVector &nodes, bool center_view_on_item); + void Select(const QVector &nodes, bool center_view_on_item); void CopySelected(bool cut); void Paste(); @@ -117,6 +117,9 @@ signals: void NodesDeselected(const QVector& nodes); + void NodeSelectionChanged(const QVector& nodes); + void NodeSelectionChangedWithContexts(const QVector& nodes); + void NodeGroupOpened(NodeGroup *group); void NodeGroupClosed(); @@ -218,6 +221,8 @@ private: double scale_; + bool dont_emit_selection_signals_; + static const double kMinimumScale; private slots: diff --git a/app/widget/nodeview/nodeviewundo.cpp b/app/widget/nodeview/nodeviewundo.cpp index 54106070f..2ed0ef28b 100644 --- a/app/widget/nodeview/nodeviewundo.cpp +++ b/app/widget/nodeview/nodeviewundo.cpp @@ -199,13 +199,12 @@ NodeViewDeleteCommand::NodeViewDeleteCommand() void NodeViewDeleteCommand::AddNode(Node *node, Node *context) { - foreach (const NodePair &pair, nodes_) { - if (pair.first == node && pair.second == context) { - return; - } + if (ContainsNode(node, context)) { + return; } - nodes_.append(NodePair({node, context})); + Node::ContextPair p = {node, context}; + nodes_.append(p); for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { if (context->ContextContainsNode(it->second)) { @@ -231,10 +230,21 @@ void NodeViewDeleteCommand::AddEdge(Node *output, const NodeInput &input) edges_.append({output, input}); } +bool NodeViewDeleteCommand::ContainsNode(Node *node, Node *context) +{ + foreach (const Node::ContextPair &pair, nodes_) { + if (pair.node == node && pair.context == context) { + return true; + } + } + + return false; +} + Project *NodeViewDeleteCommand::GetRelevantProject() const { if (!nodes_.isEmpty()) { - return nodes_.first().first->project(); + return nodes_.first().node->project(); } if (!edges_.isEmpty()) { @@ -250,11 +260,11 @@ void NodeViewDeleteCommand::redo() Node::DisconnectEdge(edge.first, edge.second); } - foreach (const NodePair &pair, nodes_) { + foreach (const Node::ContextPair &pair, nodes_) { RemovedNode rn; - rn.node = pair.first; - rn.context = pair.second; + rn.node = pair.node; + rn.context = pair.context; rn.pos = rn.context->GetNodePositionInContext(rn.node); rn.context->RemoveNodeFromContext(rn.node); diff --git a/app/widget/nodeview/nodeviewundo.h b/app/widget/nodeview/nodeviewundo.h index 567198b5b..bb4588444 100644 --- a/app/widget/nodeview/nodeviewundo.h +++ b/app/widget/nodeview/nodeviewundo.h @@ -372,6 +372,8 @@ public: void AddEdge(Node *output, const NodeInput &input); + bool ContainsNode(Node *node, Node *context); + virtual Project * GetRelevantProject() const override; protected: @@ -380,9 +382,7 @@ protected: virtual void undo() override; private: - using NodePair = QPair; - - QVector nodes_; + QVector nodes_; QVector edges_; diff --git a/app/widget/scope/histogram/histogram.cpp b/app/widget/scope/histogram/histogram.cpp index c03138a36..1027333a4 100644 --- a/app/widget/scope/histogram/histogram.cpp +++ b/app/widget/scope/histogram/histogram.cpp @@ -70,9 +70,9 @@ void HistogramScope::DrawScope(TexturePtr managed_tex, QVariant pipeline) ShaderJob shader_job; - shader_job.InsertValue(QStringLiteral("viewport"), NodeValue(NodeValue::kVec2, QVector2D(width(), height()))); - shader_job.InsertValue(QStringLiteral("histogram_scale"), NodeValue(NodeValue::kFloat, histogram_scale)); - shader_job.InsertValue(QStringLiteral("histogram_power"), NodeValue(NodeValue::kFloat, histogram_power)); + shader_job.Insert(QStringLiteral("viewport"), NodeValue(NodeValue::kVec2, QVector2D(width(), height()))); + shader_job.Insert(QStringLiteral("histogram_scale"), NodeValue(NodeValue::kFloat, histogram_scale)); + shader_job.Insert(QStringLiteral("histogram_power"), NodeValue(NodeValue::kFloat, histogram_power)); if (!texture_row_sums_ || texture_row_sums_->width() != this->width() @@ -83,11 +83,11 @@ void HistogramScope::DrawScope(TexturePtr managed_tex, QVariant pipeline) } // Draw managed texture to a sums texture - shader_job.InsertValue(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(managed_tex))); + shader_job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(managed_tex))); renderer()->BlitToTexture(pipeline, shader_job, texture_row_sums_.get()); // Draw sums into a histogram - shader_job.InsertValue(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture_row_sums_))); + shader_job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture_row_sums_))); renderer()->Blit(pipeline_secondary_, shader_job, texture_row_sums_->params()); // Draw line overlays diff --git a/app/widget/scope/scopebase/scopebase.cpp b/app/widget/scope/scopebase/scopebase.cpp index 8e6074e74..e01b4978c 100644 --- a/app/widget/scope/scopebase/scopebase.cpp +++ b/app/widget/scope/scopebase/scopebase.cpp @@ -50,10 +50,10 @@ void ScopeBase::DrawScope(TexturePtr managed_tex, QVariant pipeline) { ShaderJob job; - job.InsertValue(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(managed_tex))); + job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(managed_tex))); renderer()->Blit(pipeline, job, VideoParams(width(), height(), - static_cast(Config::Current()["OfflinePixelFormat"].toInt()), + static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()), VideoParams::kInternalChannelCount)); } @@ -74,7 +74,13 @@ void ScopeBase::OnPaint() if (!managed_tex_ || !managed_tex_up_to_date_ || managed_tex_->params() != texture_->params()) { managed_tex_ = renderer()->CreateTexture(texture_->params()); - renderer()->BlitColorManaged(color_service(), texture_, Renderer::kAlphaNone, managed_tex_.get()); + + ColorTransformJob job; + job.SetColorProcessor(color_service()); + job.SetInputTexture(texture_); + job.SetInputAlphaAssociation(kAlphaNone); + + renderer()->BlitColorManaged(job, managed_tex_.get()); } DrawScope(managed_tex_, pipeline_); diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index 1962be72f..9e664cb49 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -54,26 +54,26 @@ void WaveformScope::DrawScope(TexturePtr managed_tex, QVariant pipeline) ShaderJob job; // Set viewport size - job.InsertValue(QStringLiteral("viewport"), + job.Insert(QStringLiteral("viewport"), NodeValue(NodeValue::kVec2, QVector2D(width(), height()))); // Set luma coefficients double luma_coeffs[3] = {0.0f, 0.0f, 0.0f}; color_manager()->GetDefaultLumaCoefs(luma_coeffs); - job.InsertValue(QStringLiteral("luma_coeffs"), + job.Insert(QStringLiteral("luma_coeffs"), NodeValue(NodeValue::kVec3, QVector3D(luma_coeffs[0], luma_coeffs[1], luma_coeffs[2]))); // Scale of the waveform relative to the viewport surface. - job.InsertValue(QStringLiteral("waveform_scale"), + job.Insert(QStringLiteral("waveform_scale"), NodeValue(NodeValue::kFloat, waveform_scale)); // Insert source texture - job.InsertValue(QStringLiteral("ove_maintex"), + job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(managed_tex))); renderer()->Blit(pipeline, job, VideoParams(width(), height(), - static_cast(Config::Current()["OfflinePixelFormat"].toInt()), + static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()), VideoParams::kInternalChannelCount)); float waveform_dim_x = ceil((width() - 1.0) * waveform_scale); diff --git a/app/widget/slider/base/numericsliderbase.cpp b/app/widget/slider/base/numericsliderbase.cpp index 5dc4b7d6f..81b3951ac 100644 --- a/app/widget/slider/base/numericsliderbase.cpp +++ b/app/widget/slider/base/numericsliderbase.cpp @@ -69,7 +69,7 @@ void NumericSliderBase::LadderDragged(int value, double multiplier) { dragged_ = true; - dragged_diff_ += value * drag_multiplier_ * multiplier; + dragged_diff_ += value * multiplier; // Store current value to try and prevent any unnecessary signalling if the value doesn't change QVariant pre_set_value = GetValueInternal(); @@ -139,7 +139,7 @@ bool NumericSliderBase::IsDragging() const bool NumericSliderBase::UsingLadders() const { - return ladder_element_count_ > 0 && Config::Current()[QStringLiteral("UseSliderLadders")].toBool(); + return ladder_element_count_ > 0 && OLIVE_CONFIG("UseSliderLadders").toBool(); } QVariant NumericSliderBase::AdjustValue(const QVariant &value) const diff --git a/app/widget/slider/base/sliderbase.h b/app/widget/slider/base/sliderbase.h index 64d4ed2f4..4663525d9 100644 --- a/app/widget/slider/base/sliderbase.h +++ b/app/widget/slider/base/sliderbase.h @@ -55,6 +55,11 @@ public: UpdateLabel(); } + void SetColor(const QColor &c) + { + label_->SetColor(c); + } + public slots: void ShowEditor(); diff --git a/app/widget/slider/base/sliderlabel.cpp b/app/widget/slider/base/sliderlabel.cpp index b5c851515..4271bd1e5 100644 --- a/app/widget/slider/base/sliderlabel.cpp +++ b/app/widget/slider/base/sliderlabel.cpp @@ -27,7 +27,8 @@ namespace olive { SliderLabel::SliderLabel(QWidget *parent) : - QLabel(parent) + QLabel(parent), + override_color_enabled_(false) { QPalette p = palette(); @@ -52,6 +53,26 @@ SliderLabel::SliderLabel(QWidget *parent) : setContextMenuPolicy(Qt::CustomContextMenu); } +void SliderLabel::SetColor(const QColor &c) +{ + // Prevent infinite loop in changeEvent when we set the stylesheet + override_color_enabled_ = false; + override_color_ = c; + + // Different colors will look different depending on the theme (light/dark mode). We abstract + // that away here so that other classes can simply choose a color and we will handle making it + // more legible based on the background + QColor adjusted; + if (palette().window().color().lightness() < 128) { + adjusted = override_color_.lighter(150); + } else { + adjusted = override_color_.darker(150); + } + + setStyleSheet(QStringLiteral("color: %1").arg(adjusted.name())); + override_color_enabled_ = true; +} + void SliderLabel::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { @@ -81,4 +102,13 @@ void SliderLabel::focusInEvent(QFocusEvent *event) } } +void SliderLabel::changeEvent(QEvent *event) +{ + QWidget::changeEvent(event); + + if (override_color_enabled_ && event->type() == QEvent::StyleChange) { + SetColor(override_color_); + } +} + } diff --git a/app/widget/slider/base/sliderlabel.h b/app/widget/slider/base/sliderlabel.h index 747ad44ce..948b0d77a 100644 --- a/app/widget/slider/base/sliderlabel.h +++ b/app/widget/slider/base/sliderlabel.h @@ -33,6 +33,8 @@ class SliderLabel : public QLabel public: SliderLabel(QWidget* parent); + void SetColor(const QColor &c); + protected: virtual void mousePressEvent(QMouseEvent *e) override; @@ -40,6 +42,8 @@ protected: virtual void focusInEvent(QFocusEvent *event) override; + virtual void changeEvent(QEvent *event) override; + signals: void LabelPressed(); @@ -51,6 +55,10 @@ signals: void ChangeSliderType(); +private: + bool override_color_enabled_; + QColor override_color_; + }; } diff --git a/app/widget/slider/base/sliderladder.cpp b/app/widget/slider/base/sliderladder.cpp index b85ed4d95..c7a5ec7ad 100644 --- a/app/widget/slider/base/sliderladder.cpp +++ b/app/widget/slider/base/sliderladder.cpp @@ -48,7 +48,7 @@ SliderLadder::SliderLadder(double drag_multiplier, int nb_outer_values, QString setFrameShape(QFrame::Box); setLineWidth(1); - if (!Config::Current()[QStringLiteral("UseSliderLadders")].toBool()) { + if (!OLIVE_CONFIG("UseSliderLadders").toBool()) { nb_outer_values = 0; } diff --git a/app/widget/standardcombos/sampleformatcombobox.h b/app/widget/standardcombos/sampleformatcombobox.h index a48e9016a..d52a20709 100644 --- a/app/widget/standardcombos/sampleformatcombobox.h +++ b/app/widget/standardcombos/sampleformatcombobox.h @@ -57,6 +57,24 @@ public: } } + void SetPackedFormats() + { + AudioParams::Format tmp = AudioParams::kFormatInvalid; + + if (attempt_to_restore_format_) { + tmp = GetSampleFormat(); + } + + clear(); + for (int i=AudioParams::kPackedStart; i(i)); + } + + if (attempt_to_restore_format_) { + SetSampleFormat(tmp); + } + } + AudioParams::Format GetSampleFormat() const { return static_cast(this->currentData().toInt()); diff --git a/app/widget/timebased/timebasedviewselectionmanager.h b/app/widget/timebased/timebasedviewselectionmanager.h index 106eef182..8c8a0cb6a 100644 --- a/app/widget/timebased/timebasedviewselectionmanager.h +++ b/app/widget/timebased/timebasedviewselectionmanager.h @@ -195,8 +195,26 @@ public: rational time_diff = view_->SceneToTimeNoGrid(view_->mapToScene(event->pos()).x() - drag_mouse_start_.x()); // Snap points + rational presnap_time_diff = time_diff; SnapPoints(&time_diff); + // Validate snapping + if (Core::instance()->snapping() && view_->GetSnapService()) { + for (size_t i=0; ihas_sibling_at_time(proposed_time)) { + // Unsnap + time_diff = presnap_time_diff; + if (view_->GetSnapService()) { + view_->GetSnapService()->HideSnaps(); + } + break; + } + } + } + // Validate movement for (size_t i=0; i(Config::Current()["Autoscroll"].toInt())) { + switch (static_cast(OLIVE_CONFIG("Autoscroll").toInt())) { case AutoScroll::kNone: // Do nothing break; @@ -486,7 +486,7 @@ void TimeBasedWidget::SetPoint(Timeline::MovementMode m, const rational& time) } // Set workarea - command->add_child(new WorkareaSetRangeCommand(viewer_node_->project(), points, TimeRange(in_point, out_point))); + command->add_child(new WorkareaSetRangeCommand(points->workarea(), TimeRange(in_point, out_point))); Core::instance()->undo_stack()->push(command); } @@ -511,7 +511,7 @@ void TimeBasedWidget::ResetPoint(Timeline::MovementMode m) r.set_out(TimelineWorkArea::kResetOut); } - Core::instance()->undo_stack()->push(new WorkareaSetRangeCommand(viewer_node_->project(), points, r)); + Core::instance()->undo_stack()->push(new WorkareaSetRangeCommand(points->workarea(), r)); } void TimeBasedWidget::PageScrollInternal(QScrollBar *bar, int maximum, int screen_position, bool whole_page_scroll) @@ -606,12 +606,12 @@ void TimeBasedWidget::SetMarker() color = closest->color(); } else { // Fallback to default color in preferences - color = Config::Current()[QStringLiteral("MarkerColor")].toInt(); + color = OLIVE_CONFIG("MarkerColor").toInt(); } TimelineMarker *marker = new TimelineMarker(color, TimeRange(GetTime(), GetTime())); - if (Config::Current()[QStringLiteral("SetNameWithMarker")].toBool()) { + if (OLIVE_CONFIG("SetNameWithMarker").toBool()) { MarkerPropertiesDialog mpd({marker}, timebase(), this); if (mpd.exec() != QDialog::Accepted) { delete marker; @@ -772,6 +772,11 @@ bool TimeBasedWidget::SnapPoint(const std::vector &start_times, ration for (auto it=ruler()->GetTimelinePoints()->markers()->cbegin(); it!=ruler()->GetTimelinePoints()->markers()->cend(); it++) { TimelineMarker* m = *it; + // Ignore selected markers + if (std::find(ruler()->GetSelectedMarkers().cbegin(), ruler()->GetSelectedMarkers().cend(), m) != ruler()->GetSelectedMarkers().cend()) { + continue; + } + qreal marker_pos = TimeToScene(m->time_range().in()); AttemptSnap(potential_snaps, screen_pt, marker_pos, start_times, m->time_range().in()); @@ -782,6 +787,14 @@ bool TimeBasedWidget::SnapPoint(const std::vector &start_times, ration } } + if ((snap_points & kSnapToWorkarea) && ruler()->GetTimelinePoints()) { + const rational &workarea_in = ruler()->GetTimelinePoints()->workarea()->in(); + const rational &workarea_out = ruler()->GetTimelinePoints()->workarea()->out(); + + AttemptSnap(potential_snaps, screen_pt, TimeToScene(workarea_in), start_times, workarea_in); + AttemptSnap(potential_snaps, screen_pt, TimeToScene(workarea_out), start_times, workarea_out); + } + if ((snap_points & kSnapToKeyframes) && GetSnapKeyframes()) { for (auto it=GetSnapKeyframes()->cbegin(); it!=GetSnapKeyframes()->cend(); it++) { const QVector &keys = (*it)->GetKeyframes(); diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index 3dce38e5a..3555cad98 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -62,6 +62,7 @@ public: kSnapToPlayhead = 0x2, kSnapToMarkers = 0x4, kSnapToKeyframes = 0x8, + kSnapToWorkarea = 0x10, kSnapAll = UINT32_MAX }; @@ -140,6 +141,7 @@ protected: virtual const QVector *GetSnapBlocks() const { return nullptr; } virtual const QVector *GetSnapKeyframes() const { return nullptr; } virtual const std::vector *GetSnapIgnoreKeyframes() const { return nullptr; } + virtual const std::vector *GetSnapIgnoreMarkers() const { return nullptr; } protected slots: /** @@ -163,8 +165,6 @@ signals: void ConnectedNodeChanged(ViewerOutput* old, ViewerOutput* now); private: - - /** * @brief Set either in or out point to the current playhead * diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index eba333b85..4f2a67647 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1079,6 +1079,9 @@ void TimelineWidget::ShowContextMenu() } } + QAction* rename_action = menu.addAction(tr("Rename")); + connect(rename_action, &QAction::triggered, this, &TimelineWidget::RenameSelectedBlocks); + QAction* properties_action = menu.addAction(tr("Properties")); connect(properties_action, &QAction::triggered, this, &TimelineWidget::ShowSpeedDurationDialogForSelectedClips); } @@ -1232,6 +1235,19 @@ void TimelineWidget::RevealInProject() emit RevealViewerInProject(item_to_reveal); } +void TimelineWidget::RenameSelectedBlocks() +{ + MultiUndoCommand *command = new MultiUndoCommand(); + QVector nodes(selected_blocks_.size()); + + for (int i=0; iLabelNodes(nodes); + Core::instance()->undo_stack()->pushIfHasChildren(command); +} + void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost) { ghost_items_.append(ghost); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index a0b9afaaa..cd515b67f 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -414,6 +414,8 @@ private slots: void RevealInProject(); + void RenameSelectedBlocks(); + }; } diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index fdcd49afc..ed81a6f8b 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -222,7 +222,7 @@ void ImportTool::FootageToGhosts(rational ghost_start, const DraggedFootageData if (footage_duration.isNull()) { // Fallback to still length if legngth was 0 - footage_duration = Config::Current()[QStringLiteral("DefaultStillLength")].value(); + footage_duration = OLIVE_CONFIG("DefaultStillLength").value(); } } @@ -285,7 +285,7 @@ void ImportTool::DropGhosts(bool insert) } else { // There's no active timeline here, ask the user what to do - DropWithoutSequenceBehavior behavior = static_cast(Config::Current()["DropWithoutSequenceBehavior"].toInt()); + DropWithoutSequenceBehavior behavior = static_cast(OLIVE_CONFIG("DropWithoutSequenceBehavior").toInt()); if (behavior == kDWSAsk) { QCheckBox* dont_ask_again_box = new QCheckBox(QCoreApplication::translate("ImportTool", "Don't ask me again")); @@ -312,7 +312,7 @@ void ImportTool::DropGhosts(bool insert) } if (behavior != kDWSDisable && dont_ask_again_box->isChecked()) { - Config::Current()["DropWithoutSequenceBehavior"] = behavior; + OLIVE_CONFIG("DropWithoutSequenceBehavior") = behavior; } } diff --git a/app/widget/timelinewidget/undo/timelineundogeneral.h b/app/widget/timelinewidget/undo/timelineundogeneral.h index 40396f549..7c3df0a72 100644 --- a/app/widget/timelinewidget/undo/timelineundogeneral.h +++ b/app/widget/timelinewidget/undo/timelineundogeneral.h @@ -107,7 +107,7 @@ private: class TimelineAddTrackCommand : public UndoCommand { public: TimelineAddTrackCommand(TrackList *timeline) : - TimelineAddTrackCommand(timeline, Config::Current()[QStringLiteral("AutoMergeTracks")].toBool()) + TimelineAddTrackCommand(timeline, OLIVE_CONFIG("AutoMergeTracks").toBool()) { } diff --git a/app/widget/timelinewidget/undo/timelineundoworkarea.h b/app/widget/timelinewidget/undo/timelineundoworkarea.h index c431178ec..a7b1e2655 100644 --- a/app/widget/timelinewidget/undo/timelineundoworkarea.h +++ b/app/widget/timelinewidget/undo/timelineundoworkarea.h @@ -65,34 +65,36 @@ private: class WorkareaSetRangeCommand : public UndoCommand { public: - WorkareaSetRangeCommand(Project *project, TimelinePoints* points, const TimeRange& range) : - project_(project), - points_(points), - old_range_(points_->workarea()->range()), + WorkareaSetRangeCommand(TimelineWorkArea *workarea, const TimeRange& range, const TimeRange &old_range) : + workarea_(workarea), + old_range_(old_range), new_range_(range) { } + WorkareaSetRangeCommand(TimelineWorkArea *workarea, const TimeRange& range) : + WorkareaSetRangeCommand(workarea, range, workarea->range()) + { + } + virtual Project* GetRelevantProject() const override { - return project_; + return Project::GetProjectFromObject(workarea_); } protected: virtual void redo() override { - points_->workarea()->set_range(new_range_); + workarea_->set_range(new_range_); } virtual void undo() override { - points_->workarea()->set_range(old_range_); + workarea_->set_range(old_range_); } private: - Project* project_; - - TimelinePoints* points_; + TimelineWorkArea *workarea_; TimeRange old_range_; diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 32f268c03..567d1d0f1 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -158,7 +158,7 @@ void TimelineView::wheelEvent(QWheelEvent *event) QPoint angle_delta = event->angleDelta(); - if (Config::Current()[QStringLiteral("InvertTimelineScrollAxes")].toBool() // Check if config is set to invert timeline axes + if (OLIVE_CONFIG("InvertTimelineScrollAxes").toBool() // Check if config is set to invert timeline axes && event->source() != Qt::MouseEventSynthesizedBySystem) { // Never flip axes on Apple trackpads though angle_delta = QPoint(angle_delta.y(), angle_delta.x()); } @@ -184,7 +184,7 @@ void TimelineView::wheelEvent(QWheelEvent *event) Qt::Orientation orientation = event->orientation(); - if (Config::Current()["InvertTimelineScrollAxes"].toBool()) { + if (OLIVE_CONFIG("InvertTimelineScrollAxes").toBool()) { orientation = (orientation == Qt::Horizontal) ? Qt::Vertical : Qt::Horizontal; } @@ -307,7 +307,7 @@ void TimelineView::drawForeground(QPainter *painter, const QRectF &rect) Block *attached = Node::ValueToPtr(ghost->GetData(TimelineViewGhostItem::kAttachedBlock)); - if (attached && Config::Current()[QStringLiteral("ShowClipWhileDragging")].toBool()) { + if (attached && OLIVE_CONFIG("ShowClipWhileDragging").toBool()) { int adj_track = ghost->GetAdjustedTrack().index(); qreal track_top = GetTrackY(adj_track); qreal track_height = GetTrackHeight(adj_track); diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 5af1f030a..8e0a657b5 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -26,12 +26,14 @@ #include #include "common/qtutils.h" +#include "common/range.h" #include "core.h" #include "dialog/markerproperties/markerpropertiesdialog.h" #include "node/project/serializer/serializer.h" #include "widget/colorlabelmenu/colorlabelmenu.h" #include "widget/menu/menushared.h" #include "widget/timebased/timebasedwidget.h" +#include "widget/timelinewidget/undo/timelineundoworkarea.h" namespace olive { @@ -42,7 +44,10 @@ SeekableWidget::SeekableWidget(QWidget* parent) : timeline_points_(nullptr), dragging_(false), ignore_next_focus_out_(false), - selection_manager_(this) + selection_manager_(this), + resize_item_(nullptr), + marker_top_(0), + marker_bottom_(0) { QFontMetrics fm = fontMetrics(); @@ -53,8 +58,9 @@ SeekableWidget::SeekableWidget(QWidget* parent) : setContextMenuPolicy(Qt::CustomContextMenu); setFocusPolicy(Qt::ClickFocus); + setMouseTracking(true); - selection_manager_.SetSnapMask(TimeBasedWidget::kSnapAll & ~TimeBasedWidget::kSnapToMarkers); + selection_manager_.SetSnapMask(TimeBasedWidget::kSnapAll); } void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) @@ -159,7 +165,17 @@ bool SeekableWidget::PasteMarkers(bool insert, rational insert_time) void SeekableWidget::mousePressEvent(QMouseEvent *event) { - if (TimelineMarker *initial = selection_manager_.MousePress(event)) { + if (resize_item_) { + // Handle selection, even though we won't be using it for dragging + if (!(event->modifiers() & Qt::ShiftModifier)) { + selection_manager_.ClearSelection(); + } + if (TimelineMarker *m = dynamic_cast(resize_item_)) { + selection_manager_.Select(m); + } + dragging_ = true; + resize_start_ = mapToScene(event->pos()); + } else if (TimelineMarker *initial = selection_manager_.MousePress(event)) { selection_manager_.DragStart(initial, event); } else if (!selection_manager_.GetObjectAtPoint(event->pos()) && event->button() == Qt::LeftButton) { SeekToScenePoint(mapToScene(event->pos()).x()); @@ -174,7 +190,15 @@ void SeekableWidget::mouseMoveEvent(QMouseEvent *event) if (selection_manager_.IsDragging()) { selection_manager_.DragMove(event); } else if (dragging_) { - SeekToScenePoint(mapToScene(event->pos()).x()); + QPointF scene = mapToScene(event->pos()); + if (resize_item_) { + DragResizeHandle(scene); + } else { + SeekToScenePoint(scene.x()); + } + } else if (timeline_points_) { + // Look for resize points + setCursor(FindResizeHandle(event) ? Qt::SizeHorCursor : Qt::ArrowCursor); } } @@ -190,6 +214,11 @@ void SeekableWidget::mouseReleaseEvent(QMouseEvent *event) GetSnapService()->HideSnaps(); } + if (resize_item_) { + CommitResizeHandle(); + resize_item_ = nullptr; + } + dragging_ = false; } @@ -326,9 +355,12 @@ void SeekableWidget::DrawTimelinePoints(QPainter* p, int marker_bottom) } QRect marker_rect = marker->Draw(p, QPoint(marker_left, marker_bottom), GetScale(), selection_manager_.IsSelected(marker)); + marker_top_ = marker_rect.top(); selection_manager_.DeclareDrawnObject(marker, marker_rect); } } + + marker_bottom_ = marker_bottom; } void SeekableWidget::DrawPlayhead(QPainter *p, int x, int y) @@ -384,4 +416,115 @@ bool SeekableWidget::ShowContextMenu(const QPoint &p) } } +bool SeekableWidget::FindResizeHandle(QMouseEvent *event) +{ + resize_item_ = nullptr; + resize_mode_ = kResizeNone; + + QPointF scene = mapToScene(event->pos()); + const int border = 10; + rational min = SceneToTimeNoGrid(scene.x() - border); + rational max = SceneToTimeNoGrid(scene.x() + border); + + // Test for workarea + if (timeline_points_->workarea()->in() >= min && timeline_points_->workarea()->in() < max) { + resize_mode_ = kResizeIn; + } else if (timeline_points_->workarea()->out() >= min && timeline_points_->workarea()->out() < max) { + resize_mode_ = kResizeOut; + } + + if (resize_mode_ != kResizeNone) { + resize_item_ = timeline_points_->workarea(); + resize_item_range_ = timeline_points_->workarea()->range(); + resize_snap_mask_ = TimeBasedWidget::kSnapAll & ~TimeBasedWidget::kSnapToWorkarea; + } else if (event->pos().y() >= marker_top_ && event->pos().y() < marker_bottom_) { + // Check for markers + for (auto it=timeline_points_->markers()->cbegin(); it!=timeline_points_->markers()->cend(); it++) { + TimelineMarker *m = *it; + if (m->time_range().in() != m->time_range().out()) { + if (m->time_range().in() >= min && m->time_range().in() < max) { + resize_mode_ = kResizeIn; + } else if (m->time_range().out() >= min && m->time_range().out() < max) { + resize_mode_ = kResizeOut; + } + + if (resize_mode_ != kResizeNone) { + resize_item_ = m; + resize_item_range_ = m->time_range(); + resize_snap_mask_ = TimeBasedWidget::kSnapAll; + break; + } + } + } + } + + return resize_item_; +} + +void SeekableWidget::DragResizeHandle(const QPointF &scene) +{ + qreal diff = scene.x() - resize_start_.x(); + + rational proposed_time; + + if (resize_mode_ == kResizeIn) { + proposed_time = qMax(rational(0), qMin(resize_item_range_.out(), resize_item_range_.in() + SceneToTimeNoGrid(diff))); + } else { + proposed_time = qMax(resize_item_range_.in(), resize_item_range_.out() + SceneToTimeNoGrid(diff)); + } + + rational presnap_time = proposed_time; + + if (Core::instance()->snapping() && GetSnapService()) { + rational movement; + + GetSnapService()->SnapPoint({proposed_time}, &movement, resize_snap_mask_); + + proposed_time += movement; + } + + TimeRange new_range = resize_item_range_; + if (resize_mode_ == kResizeIn) { + // Markers should not have the same time as anything else + // NOTE: This code is largely duplicated from TimeBasedViewSelectionManager::DragMove. Not ideal, + // but I'm not sure if there's a good way to re-use that code + if (TimelineMarker *marker = dynamic_cast(resize_item_)) { + if (marker->has_sibling_at_time(proposed_time)) { + proposed_time = presnap_time; + + if (GetSnapService()) { + GetSnapService()->HideSnaps(); + } + } + + while (marker->has_sibling_at_time(proposed_time)) { + proposed_time += rational(1, 1000); + } + } + + new_range.set_in(proposed_time); + } else { + new_range.set_out(proposed_time); + } + + if (TimelineMarker *marker = dynamic_cast(resize_item_)) { + marker->set_time(new_range); + } else if (TimelineWorkArea *workarea = dynamic_cast(resize_item_)) { + workarea->set_range(new_range); + } +} + +void SeekableWidget::CommitResizeHandle() +{ + MultiUndoCommand *command = new MultiUndoCommand(); + + if (TimelineMarker *marker = dynamic_cast(resize_item_)) { + command->add_child(new MarkerChangeTimeCommand(marker, marker->time_range(), resize_item_range_)); + } else if (TimelineWorkArea *workarea = dynamic_cast(resize_item_)) { + command->add_child(new WorkareaSetRangeCommand(workarea, workarea->range(), resize_item_range_)); + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); +} + } diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index 1bc8e61de..e151bbc4d 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -60,6 +60,11 @@ public: void SeekToScenePoint(qreal scene); + const std::vector &GetSelectedMarkers() const + { + return selection_manager_.GetSelectedObjects(); + } + virtual void SelectionManagerSelectEvent(void *obj) override; virtual void SelectionManagerDeselectEvent(void *obj) override; @@ -95,6 +100,18 @@ protected slots: virtual bool ShowContextMenu(const QPoint &p); private: + enum ResizeMode { + kResizeNone, + kResizeIn, + kResizeOut + }; + + bool FindResizeHandle(QMouseEvent *event); + + void DragResizeHandle(const QPointF &scene_pos); + + void CommitResizeHandle(); + TimelinePoints* timeline_points_; int text_height_; @@ -107,6 +124,15 @@ private: TimeBasedViewSelectionManager selection_manager_; + QObject *resize_item_; + ResizeMode resize_mode_; + TimeRange resize_item_range_; + QPointF resize_start_; + uint32_t resize_snap_mask_; + + int marker_top_; + int marker_bottom_; + private slots: void SetMarkerColor(int c); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index f8b25247a..f638bc67c 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -98,6 +98,11 @@ ViewerWidget::ViewerWidget(QWidget *parent) : connect(display_widget_, &ViewerDisplayWidget::HandDragMoved, sizer_, &ViewerSizer::HandDragMove); sizer_->SetWidget(display_widget_); + // Make the display widget the first tabbable widget. While the viewer display cannot actually + // be interacted with by tabbing, it prevents the actual first tabbable widget (the playhead + // slider in `controls_`) from getting auto-focused any time the panel is maximized (with `) + display_widget_->setFocusPolicy(Qt::TabFocus); + // Create waveform view when audio is connected and video isn't waveform_view_ = new AudioWaveformView(); ConnectTimelineView(waveform_view_, true); @@ -141,6 +146,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) : connect(Core::instance(), &Core::ColorPickerEnabled, this, &ViewerWidget::SetSignalCursorColorEnabled); connect(this, &ViewerWidget::CursorColor, Core::instance(), &Core::ColorPickerColorEmitted); + connect(AudioManager::instance(), &AudioManager::OutputParamsChanged, this, &ViewerWidget::UpdateAudioProcessor); } ViewerWidget::~ViewerWidget() @@ -210,8 +216,7 @@ void ViewerWidget::ConnectNodeEvent(ViewerOutput *n) last_length_ = 0; LengthChangedSlot(n->GetLength()); - AudioParams ap = n->GetAudioParams(); - packed_processor_.Open(ap); + UpdateAudioProcessor(); ColorManager* color_manager = n->project()->color_manager(); @@ -245,7 +250,7 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n) disconnect(n->video_frame_cache(), &FrameHashCache::Shifted, this, &ViewerWidget::ViewerShiftedRange); disconnect(n, &ViewerOutput::TextureInputChanged, this, &ViewerWidget::UpdateStack); - packed_processor_.Close(); + CloseAudioProcessor(); SetDisplayImage(QVariant()); @@ -270,6 +275,7 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n) void ViewerWidget::ConnectedNodeChangeEvent(ViewerOutput *n) { auto_cacher_.SetViewerNode(n); + display_widget_->SetSubtitleTracks(dynamic_cast(n)); } void ViewerWidget::ScaleChangedEvent(const double &s) @@ -456,13 +462,33 @@ void ViewerWidget::DisarmRecording() record_armed_ = false; } +void ViewerWidget::UpdateAudioProcessor() +{ + if (GetConnectedNode()) { + audio_processor_.Close(); + + AudioParams ap = GetConnectedNode()->GetAudioParams(); + AudioParams packed(OLIVE_CONFIG("AudioOutputSampleRate").toInt(), + OLIVE_CONFIG("AudioOutputChannelLayout").toULongLong(), + static_cast(OLIVE_CONFIG("AudioOutputSampleFormat").toInt())); + + audio_processor_.Open(ap, packed, (playback_speed_ == 0) ? 1 : std::abs(playback_speed_)); + } +} + +void ViewerWidget::CloseAudioProcessor() +{ + audio_processor_.Close(); +} + void ViewerWidget::QueueNextAudioBuffer() { rational queue_end = audio_playback_queue_time_ + (kAudioPlaybackInterval * playback_speed_); // Clamp queue end by zero and the audio length queue_end = clamp(queue_end, rational(0), GetConnectedNode()->GetAudioLength()); - if (queue_end <= audio_playback_queue_time_) { + if ((playback_speed_ > 0 && queue_end <= audio_playback_queue_time_) + || (playback_speed_ < 0 && queue_end >= audio_playback_queue_time_)) { // This will queue nothing, so stop the loop here if (prequeuing_audio_) { DecrementPrequeuedAudio(); @@ -485,31 +511,31 @@ void ViewerWidget::ReceivedAudioBufferForPlayback() audio_playback_queue_.pop_front(); if (watcher->HasResult()) { - SampleBufferPtr samples = watcher->Get().value(); - if (samples) { + SampleBuffer samples = watcher->Get().value(); + if (samples.is_allocated()) { // If the samples must be reversed, reverse them now if (playback_speed_ < 0) { - samples->reverse(); + samples.reverse(); } // Convert to packed data for audio output - QByteArray pack = packed_processor_.Convert(samples); - - // If the tempo must be adjusted, adjust now - if (tempo_processor_.IsOpen()) { - tempo_processor_.Push(pack); - pack = tempo_processor_.Pull(); - } + AudioProcessor::Buffer buf; + int r = audio_processor_.Convert(samples.to_raw_ptrs().data(), samples.sample_count(), &buf); // TempoProcessor may have emptied the array - if (!pack.isEmpty()) { - if (prequeuing_audio_) { - // Add to prequeued audio buffer - prequeued_audio_.append(pack); - } else { - // Push directly to audio manager - AudioManager::instance()->PushToOutput(GetConnectedNode()->GetAudioParams(), pack); + if (r >= 0) { + if (!buf.empty()) { + const QByteArray &pack = buf.at(0); + if (prequeuing_audio_) { + // Add to prequeued audio buffer + prequeued_audio_.append(pack); + } else { + // Push directly to audio manager + AudioManager::instance()->PushToOutput(audio_processor_.to(), pack); + } } + } else { + qCritical() << "Failed to process audio for playback:" << r; } } } @@ -530,8 +556,9 @@ void ViewerWidget::ReceivedAudioBufferForScrubbing() RenderTicketWatcher *watcher = static_cast(sender()); if (watcher->HasResult()) { - if (SampleBufferPtr samples = watcher->Get().value()) { - if (samples->audio_params().channel_count() > 0) { + SampleBuffer samples = watcher->Get().value(); + if (samples.is_allocated()) { + if (samples.audio_params().channel_count() > 0) { /* Fade code const int kFadeSz = qMin(200, samples->sample_count()/4); for (int i=0; itransform_volume_for_sample(samples->sample_count() - i - 1, amt); }*/ - QByteArray packed = packed_processor_.Convert(samples); - AudioManager::instance()->ClearBufferedOutput(); - AudioManager::instance()->PushToOutput(samples->audio_params(), packed); - AudioMonitor::PushBytesOnAll(packed); + AudioProcessor::Buffer buf; + int r = audio_processor_.Convert(samples.to_raw_ptrs().data(), samples.sample_count(), &buf); + + if (r >= 0) { + if (!buf.empty()) { + QString error; + const QByteArray &packed = buf.at(0); + AudioManager::instance()->ClearBufferedOutput(); + if (!AudioManager::instance()->PushToOutput(audio_processor_.to(), packed, &error)) { + Core::instance()->ShowStatusBarMessage(tr("Audio scrubbing failed: %1").arg(error)); + } + AudioMonitor::PushSampleBufferOnAll(samples); + } + } else { + qCritical() << "Failed to process audio for scrubbing:" << r; + } } } } @@ -672,12 +711,10 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) AudioParams ap = GetConnectedNode()->GetAudioParams(); if (ap.is_valid()) { - AudioManager::instance()->SetOutputNotifyInterval(ap.time_to_bytes(kAudioPlaybackInterval)); - connect(AudioManager::instance(), &AudioManager::OutputNotify, this, &ViewerWidget::QueueNextAudioBuffer); + UpdateAudioProcessor(); - if (std::abs(playback_speed_) > 1) { - tempo_processor_.Open(GetConnectedNode()->GetAudioParams(), std::abs(playback_speed_)); - } + AudioManager::instance()->SetOutputNotifyInterval(audio_processor_.to().time_to_bytes(kAudioPlaybackInterval)); + connect(AudioManager::instance(), &AudioManager::OutputNotify, this, &ViewerWidget::QueueNextAudioBuffer); static const int prequeue_count = 2; prequeuing_audio_ = prequeue_count; // Queue two buffers ahead of time @@ -721,9 +758,7 @@ void ViewerWidget::PauseInternal() disconnect(AudioManager::instance(), &AudioManager::OutputNotify, this, &ViewerWidget::QueueNextAudioBuffer); qDeleteAll(audio_playback_queue_); audio_playback_queue_.clear(); - if (tempo_processor_.IsOpen()) { - tempo_processor_.Close(); - } + UpdateAudioProcessor(); foreach (ViewerWidget* viewer, instances_) { viewer->auto_cacher_.SetAudioPaused(false); @@ -741,7 +776,7 @@ void ViewerWidget::PauseInternal() void ViewerWidget::PushScrubbedAudio() { - if (!IsPlaying() && GetConnectedNode() && Config::Current()[QStringLiteral("AudioScrubbing")].toBool()) { + if (!IsPlaying() && GetConnectedNode() && OLIVE_CONFIG("AudioScrubbing").toBool()) { // Get audio src device from renderer const AudioParams& params = GetConnectedNode()->audio_playback_cache()->GetParameters(); @@ -852,7 +887,11 @@ void ViewerWidget::FinishPlayPreprocess() // Start audio waveform playback if (!prequeued_audio_.isEmpty()) { - AudioManager::instance()->PushToOutput(GetConnectedNode()->GetAudioParams(), prequeued_audio_); + QString error; + if (!AudioManager::instance()->PushToOutput(audio_processor_.to(), prequeued_audio_, &error)) { + QMessageBox::critical(this, tr("Audio Error"), tr("Failed to start audio: %1\n\n" + "Please check your audio preferences and try again.").arg(error)); + } prequeued_audio_.clear(); AudioMonitor::StartWaveformOnAll(&GetConnectedNode()->audio_playback_cache()->visual(), @@ -1129,9 +1168,9 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) { QAction *stop_playback_on_last_frame = menu.addAction(tr("Stop Playback On Last Frame")); stop_playback_on_last_frame->setCheckable(true); - stop_playback_on_last_frame->setChecked(Config::Current()[QStringLiteral("StopPlaybackOnLastFrame")].toBool()); + stop_playback_on_last_frame->setChecked(OLIVE_CONFIG("StopPlaybackOnLastFrame").toBool()); connect(stop_playback_on_last_frame, &QAction::triggered, this, [](bool e){ - Config::Current()[QStringLiteral("StopPlaybackOnLastFrame")] = e; + OLIVE_CONFIG("StopPlaybackOnLastFrame") = e; }); menu.addSeparator(); @@ -1152,6 +1191,13 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) connect(show_fps_action, &QAction::triggered, display_widget_, &ViewerDisplayWidget::SetShowFPS); } + if (context_menu_widget_ == display_widget_) { + QAction* show_subtitles_action = menu.addAction(tr("Show Subtitles")); + show_subtitles_action->setCheckable(true); + show_subtitles_action->setChecked(display_widget_->GetShowSubtitles()); + connect(show_subtitles_action, &QAction::triggered, display_widget_, &ViewerDisplayWidget::SetShowSubtitles); + } + menu.exec(static_cast(sender())->mapToGlobal(pos)); } @@ -1180,7 +1226,7 @@ void ViewerWidget::Play(bool in_to_out_only) recording_filename_ = audio_path.filePath(QStringLiteral("%1.%2").arg( QDateTime::currentDateTime().toString("yyyy-MM-dd hh-mm-ss"), - ExportFormat::GetExtension(static_cast(Config::Current()[QStringLiteral("AudioRecordingFormat")].toInt()))) + ExportFormat::GetExtension(static_cast(OLIVE_CONFIG("AudioRecordingFormat").toInt()))) ); AudioParams ap(OLIVE_CONFIG("AudioRecordingSampleRate").toInt(), OLIVE_CONFIG("AudioRecordingChannelLayout").toULongLong(), static_cast(OLIVE_CONFIG("AudioRecordingSampleFormat").toInt())); @@ -1190,12 +1236,13 @@ void ViewerWidget::Play(bool in_to_out_only) encode_param.SetFilename(recording_filename_); encode_param.set_audio_bit_rate(OLIVE_CONFIG("AudioRecordingBitRate").toInt() * 1000); - if (AudioManager::instance()->StartRecording(encode_param)) { + QString error; + if (AudioManager::instance()->StartRecording(encode_param, &error)) { recording_ = true; controls_->SetPauseButtonRecordingState(true); recording_callback_->EnableRecordingOverlay(TimelineCoordinate(recording_range_.in(), recording_track_)); } else { - QMessageBox::critical(this, tr("Audio Recording"), tr("Failed to start audio recording")); + QMessageBox::critical(this, tr("Audio Recording"), tr("Failed to start audio recording: %1").arg(error)); return; } } @@ -1304,7 +1351,7 @@ void ViewerWidget::PlaybackTimerUpdate() // If we're stopping playback on the last frame rather than after it, subtract our max time // by one timebase unit - if (Config::Current()[QStringLiteral("StopPlaybackOnLastFrame")].toBool()) { + if (OLIVE_CONFIG("StopPlaybackOnLastFrame").toBool()) { max_time = qMax(min_time, max_time - timebase()); } @@ -1329,7 +1376,7 @@ void ViewerWidget::PlaybackTimerUpdate() // or restart playback end_of_line = true; - if (Config::Current()[QStringLiteral("Loop")].toBool() && !recording_) { + if (OLIVE_CONFIG("Loop").toBool() && !recording_) { // If we're looping, jump to the other side of the workarea and continue time_to_set = (tripped_time == min_time) ? max_time : min_time; @@ -1433,11 +1480,7 @@ void ViewerWidget::UpdateRendererVideoParameters() void ViewerWidget::UpdateRendererAudioParameters() { - packed_processor_.Close(); - - AudioParams ap = GetConnectedNode()->GetAudioParams(); - - packed_processor_.Open(ap); + UpdateAudioProcessor(); } void ViewerWidget::SetZoomFromMenu(QAction *action) diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 4bf043af1..ba7f7be56 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -28,8 +28,7 @@ #include #include -#include "audio/packedprocessor.h" -#include "audio/tempoprocessor.h" +#include "audio/audioprocessor.h" #include "audiowaveformview.h" #include "common/rational.h" #include "node/output/viewer/viewer.h" @@ -212,6 +211,8 @@ private: void DisarmRecording(); + void CloseAudioProcessor(); + QStackedWidget* stack_; ViewerSizer* sizer_; @@ -255,8 +256,7 @@ private: std::list audio_playback_queue_; rational audio_playback_queue_time_; - PackedProcessor packed_processor_; - TempoProcessor tempo_processor_; + AudioProcessor audio_processor_; QByteArray prequeued_audio_; static const rational kAudioPlaybackInterval; @@ -318,6 +318,8 @@ private slots: void ForceRequeueFromCurrentTime(); + void UpdateAudioProcessor(); + }; } diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 4a41a4c43..2ceb202e3 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -37,8 +37,10 @@ #include "common/define.h" #include "common/functiontimer.h" #include "common/html.h" +#include "common/qtutils.h" #include "config/config.h" #include "core.h" +#include "node/block/subtitle/subtitle.h" #include "node/gizmo/path.h" #include "node/gizmo/point.h" #include "node/gizmo/polygon.h" @@ -59,6 +61,8 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) : gizmos_(nullptr), current_gizmo_(nullptr), gizmo_drag_started_(false), + show_subtitles_(true), + subtitle_tracks_(nullptr), hand_dragging_(false), deinterlace_(false), show_fps_(false), @@ -189,6 +193,21 @@ void ViewerDisplayWidget::SetTime(const rational &time) } } +void ViewerDisplayWidget::SetSubtitleTracks(Sequence *list) +{ + if (subtitle_tracks_) { + disconnect(subtitle_tracks_, &Sequence::SubtitlesChanged, this, &ViewerDisplayWidget::SubtitlesChanged); + } + + subtitle_tracks_ = list; + + if (subtitle_tracks_) { + connect(subtitle_tracks_, &Sequence::SubtitlesChanged, this, &ViewerDisplayWidget::SubtitlesChanged); + } + + update(); +} + QPointF ViewerDisplayWidget::TransformViewerSpaceToBufferSpace(const QPointF &pos) { /* @@ -428,7 +447,7 @@ void ViewerDisplayWidget::OnPaint() // Draw texture through color transform int device_width = width() * devicePixelRatioF(); int device_height = height() * devicePixelRatioF(); - VideoParams::Format device_format = static_cast(Config::Current()["OfflinePixelFormat"].toInt()); + VideoParams::Format device_format = static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()); VideoParams device_params(device_width, device_height, device_format, VideoParams::kInternalChannelCount); if (push_mode_ == kPushBlank) { @@ -437,8 +456,8 @@ void ViewerDisplayWidget::OnPaint() } ShaderJob job; - job.InsertValue(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, combined_matrix_flipped_)); - job.InsertValue(QStringLiteral("ove_cropmatrix"), NodeValue(NodeValue::kMatrix, crop_matrix_)); + job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, combined_matrix_flipped_)); + job.Insert(QStringLiteral("ove_cropmatrix"), NodeValue(NodeValue::kMatrix, crop_matrix_)); renderer()->Blit(blank_shader_, job, device_params, false); } else if (color_service()) { @@ -477,18 +496,23 @@ void ViewerDisplayWidget::OnPaint() } ShaderJob job; - job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, QVector2D(texture_to_draw->width(), texture_to_draw->height()))); - job.InsertValue(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture_to_draw))); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, QVector2D(texture_to_draw->width(), texture_to_draw->height()))); + job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture_to_draw))); renderer()->BlitToTexture(deinterlace_shader_, job, deinterlace_texture_.get()); texture_to_draw = deinterlace_texture_; } - renderer()->BlitColorManaged(color_service(), texture_to_draw, - Config::Current()[QStringLiteral("ReassocLinToNonLin")].toBool() ? Renderer::kAlphaAssociated : Renderer::kAlphaNone, - device_params, false, - combined_matrix_flipped_, crop_matrix_); + ColorTransformJob ctj; + ctj.SetColorProcessor(color_service()); + ctj.SetInputTexture(texture_to_draw); + ctj.SetInputAlphaAssociation(OLIVE_CONFIG("ReassocLinToNonLin").toBool() ? kAlphaAssociated : kAlphaNone); + ctj.SetClearDestinationEnabled(false); + ctj.SetTransformMatrix(combined_matrix_flipped_); + ctj.SetCropMatrix(crop_matrix_); + + renderer()->BlitColorManaged(ctj, device_params); } } @@ -505,7 +529,9 @@ void ViewerDisplayWidget::OnPaint() gizmos_->UpdateGizmoPositions(gizmo_db_, NodeTraverser::GenerateGlobals(gizmo_params_, range)); foreach (NodeGizmo *gizmo, gizmos_->GetGizmos()) { - gizmo->Draw(&p); + if (gizmo->IsVisible()) { + gizmo->Draw(&p); + } } } @@ -577,6 +603,53 @@ void ViewerDisplayWidget::OnPaint() } } } + + // Extraordinarily basic subtitle renderer. Hoping to swap this out with libass at some point. + if (show_subtitles_ && subtitle_tracks_) { + const QVector &subtitle_tracklist = subtitle_tracks_->track_list(Track::kSubtitle)->GetTracks(); + + if (!subtitle_tracklist.empty()) { + QPainter p(inner_widget()); + + QTransform transform = GenerateWorldTransform(); + QRect bounding_box = transform.mapRect(rect()); + + bounding_box.adjust(bounding_box.width()/10, bounding_box.height()/10, -bounding_box.width()/10, -bounding_box.height()/10); + + QFont f = p.font(); + int font_sz = bounding_box.height() / 18; + f.setStyleHint(QFont::SansSerif); + f.setFamily(f.defaultFamily()); + f.setPointSize(font_sz); + f.setWeight(QFont::Bold); + p.setFont(f); + p.setPen(Qt::white); + + QPainterPath path; + + int text_line = 1; + + for (int j=subtitle_tracklist.size()-1; j>=0; j--) { + Track *sub_track = subtitle_tracklist.at(j); + if (!sub_track->IsMuted()) { + if (SubtitleBlock *sub = dynamic_cast(sub_track->BlockAtTime(time_))) { + // Split into lines + QStringList list = QtUtils::WordWrapString(sub->GetText(), p.fontMetrics(), bounding_box.width()); + + for (int i=list.size()-1; i>=0; i--) { + int w = QtUtils::QFontMetricsWidth(p.fontMetrics(), list.at(i)); + path.addText(bounding_box.x() + bounding_box.width()/2 - w/2, bounding_box.y() + bounding_box.height() - p.fontMetrics().height() * text_line + p.fontMetrics().ascent(), p.font(), list.at(i)); + text_line++; + } + } + } + } + + p.setPen(QPen(Qt::black, font_sz / 16)); + p.setBrush(Qt::white); + p.drawPath(path); + } + } } void ViewerDisplayWidget::OnDestroy() @@ -613,12 +686,12 @@ QPointF ViewerDisplayWidget::GetTexturePosition(const double &x, const double &y y / gizmo_params_.height()); } -void ViewerDisplayWidget::DrawTextWithCrudeShadow(QPainter *painter, const QRect &rect, const QString &text) +void ViewerDisplayWidget::DrawTextWithCrudeShadow(QPainter *painter, const QRect &rect, const QString &text, const QTextOption &opt) { painter->setPen(Qt::black); - painter->drawText(rect.adjusted(1, 1, 0, 0), text); + painter->drawText(rect.adjusted(1, 1, 0, 0), text, opt); painter->setPen(Qt::white); - painter->drawText(rect, text); + painter->drawText(rect, text, opt); } rational ViewerDisplayWidget::GetGizmoTime() @@ -677,21 +750,23 @@ NodeGizmo *ViewerDisplayWidget::TryGizmoPress(const NodeValueRow &row, const QPo { for (auto it=gizmos_->GetGizmos().crbegin(); it!=gizmos_->GetGizmos().crend(); it++) { NodeGizmo *gizmo = *it; - if (PointGizmo *point = dynamic_cast(gizmo)) { - if (point->GetClickingRect(GenerateGizmoTransform()).contains(p)) { - return point; + if (gizmo->IsVisible()) { + if (PointGizmo *point = dynamic_cast(gizmo)) { + if (point->GetClickingRect(GenerateGizmoTransform()).contains(p)) { + return point; + } + } else if (PolygonGizmo *poly = dynamic_cast(gizmo)) { + if (poly->GetPolygon().containsPoint(p, Qt::OddEvenFill)) { + return poly; + } + } else if (PathGizmo *path = dynamic_cast(gizmo)) { + if (path->GetPath().contains(p)) { + return path; + } + } else if (ScreenGizmo *screen = dynamic_cast(gizmo)) { + // NOTE: Perhaps this should limit to the actual visible screen space? We'll see. + return screen; } - } else if (PolygonGizmo *poly = dynamic_cast(gizmo)) { - if (poly->GetPolygon().containsPoint(p, Qt::OddEvenFill)) { - return poly; - } - } else if (PathGizmo *path = dynamic_cast(gizmo)) { - if (path->GetPath().contains(p)) { - return path; - } - } else if (ScreenGizmo *screen = dynamic_cast(gizmo)) { - // NOTE: Perhaps this should limit to the actual visible screen space? We'll see. - return screen; } } @@ -802,4 +877,11 @@ void ViewerDisplayWidget::TextEditChanged() gizmo->UpdateInputHtml(html, GetGizmoTime()); } +void ViewerDisplayWidget::SubtitlesChanged(const TimeRange &r) +{ + if (time_ >= r.in() && time_ < r.out()) { + update(); + } +} + } diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index 718fd3070..1ccc5b3d1 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -26,6 +26,7 @@ #include "node/color/colormanager/colormanager.h" #include "node/node.h" +#include "node/output/track/tracklist.h" #include "render/color.h" #include "tool/tool.h" #include "viewerplaybacktimer.h" @@ -72,6 +73,7 @@ public: void SetGizmos(Node* node); void SetVideoParams(const VideoParams ¶ms); void SetTime(const rational& time); + void SetSubtitleTracks(Sequence *list); void SetShowWidgetBackground(bool e) { @@ -97,6 +99,9 @@ public: return show_fps_; } + bool GetShowSubtitles() const { return show_subtitles_; } + void SetShowSubtitles(bool e) { show_subtitles_ = e; update(); } + void IncrementSkippedFrames(); void IncrementFrameCount() @@ -239,7 +244,7 @@ private: QPointF GetTexturePosition(const QSize& size); QPointF GetTexturePosition(const double& x, const double& y); - static void DrawTextWithCrudeShadow(QPainter* painter, const QRect& rect, const QString& text); + static void DrawTextWithCrudeShadow(QPainter* painter, const QRect& rect, const QString& text, const QTextOption &opt = QTextOption()); rational GetGizmoTime(); @@ -312,6 +317,9 @@ private: NodeGizmo *current_gizmo_; bool gizmo_drag_started_; + bool show_subtitles_; + Sequence *subtitle_tracks_; + rational time_; /** @@ -365,6 +373,8 @@ private slots: void TextEditChanged(); + void SubtitlesChanged(const TimeRange &r); + }; } diff --git a/app/window/mainwindow/mainmenu.cpp b/app/window/mainwindow/mainmenu.cpp index c4ba08d5e..dec12a79e 100644 --- a/app/window/mainwindow/mainmenu.cpp +++ b/app/window/mainwindow/mainmenu.cpp @@ -381,7 +381,7 @@ void MainMenu::ToolsMenuAboutToShow() void MainMenu::PlaybackMenuAboutToShow() { - playback_loop_item_->setChecked(Config::Current()["Loop"].toBool()); + playback_loop_item_->setChecked(OLIVE_CONFIG("Loop").toBool()); } void MainMenu::SequenceMenuAboutToShow() @@ -506,7 +506,7 @@ void MainMenu::PlayInToOutTriggered() void MainMenu::LoopTriggered(bool enabled) { - Config::Current()["Loop"] = enabled; + OLIVE_CONFIG("Loop") = enabled; } void MainMenu::NextFrameTriggered() diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 6e02ffc07..1bf868ffd 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -92,15 +92,12 @@ MainWindow::MainWindow(QWidget *parent) : scope_panel_ = new ScopePanel(this); // Make node-related connections - connect(node_panel_, &NodePanel::NodesSelected, param_panel_, &ParamPanel::SelectNodes); - connect(node_panel_, &NodePanel::NodesDeselected, param_panel_, &ParamPanel::DeselectNodes); + connect(node_panel_, &NodePanel::NodeSelectionChangedWithContexts, param_panel_, &ParamPanel::SetSelectedNodes); connect(node_panel_, &NodePanel::NodeGroupOpened, this, &MainWindow::NodePanelGroupOpenedOrClosed); connect(node_panel_, &NodePanel::NodeGroupClosed, this, &MainWindow::NodePanelGroupOpenedOrClosed); - connect(param_panel_, &ParamPanel::RequestSelectNode, this, [this](const QVector& target){ - node_panel_->Select(target, true); - }); connect(param_panel_, &ParamPanel::FocusedNodeChanged, sequence_viewer_panel_, &ViewerPanel::SetGizmos); connect(param_panel_, &ParamPanel::FocusedNodeChanged, curve_panel_, &CurvePanel::SetNode); + connect(param_panel_, &ParamPanel::SelectedNodesChanged, node_panel_, &NodePanel::Select); // Connect time signals together connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, param_panel_, &ParamPanel::SetTime); @@ -473,7 +470,7 @@ void MainWindow::TimelinePanelSelectionChanged(const QVector &blocks) void MainWindow::ShowWelcomeDialog() { - if (Config::Current()[QStringLiteral("ShowWelcomeDialog")].toBool()) { + if (OLIVE_CONFIG("ShowWelcomeDialog").toBool()) { AboutDialog ad(true, this); ad.exec(); }