Merge branch 'master' into ocio_node
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "audio/packedprocessor.h"
|
||||
#include "config/config.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -68,6 +69,19 @@ int OutputCallback(const void *input, void *output, unsigned long frameCount, co
|
||||
return paContinue;
|
||||
}
|
||||
|
||||
int InputCallback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
|
||||
{
|
||||
FFmpegEncoder *f = static_cast<FFmpegEncoder*>(userData);
|
||||
|
||||
SampleBufferPtr s = SampleBuffer::Create();
|
||||
s->set_sample_count(frameCount);
|
||||
s->set_audio_params(f->params().audio_params());
|
||||
|
||||
f->WriteAudioData(f->params().audio_params(), false, reinterpret_cast<const uint8_t**>(&input), frameCount);
|
||||
|
||||
return paContinue;
|
||||
}
|
||||
|
||||
void AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &samples)
|
||||
{
|
||||
if (output_device_ == paNoDevice) {
|
||||
@@ -79,13 +93,7 @@ void AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &sam
|
||||
|
||||
CloseOutputStream();
|
||||
|
||||
PaStreamParameters p;
|
||||
|
||||
p.channelCount = output_params_.channel_count();
|
||||
p.device = output_device_;
|
||||
p.hostApiSpecificStreamInfo = nullptr;
|
||||
p.sampleFormat = GetPortAudioSampleFormat(output_params_.format());
|
||||
p.suggestedLatency = Pa_GetDeviceInfo(output_device_)->defaultLowOutputLatency;
|
||||
PaStreamParameters p = GetPortAudioParams(params, output_device_);
|
||||
|
||||
Pa_OpenStream(&output_stream_, nullptr, &p, output_params_.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, OutputCallback, output_buffer_);
|
||||
|
||||
@@ -176,6 +184,52 @@ void AudioManager::HardReset()
|
||||
Pa_Initialize();
|
||||
}
|
||||
|
||||
bool AudioManager::StartRecording(const QString &filename, const AudioParams ¶ms)
|
||||
{
|
||||
if (input_device_ == paNoDevice) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EncodingParams encode_param;
|
||||
encode_param.EnableAudio(params, ExportCodec::kCodecMP3);
|
||||
encode_param.SetFilename(filename);
|
||||
|
||||
input_encoder_ = new FFmpegEncoder(encode_param);
|
||||
if (!input_encoder_->Open()) {
|
||||
qCritical() << "Failed to open encoder for recording";
|
||||
return false;
|
||||
}
|
||||
|
||||
PaStreamParameters p = GetPortAudioParams(params, input_device_);
|
||||
|
||||
if (Pa_OpenStream(&input_stream_, &p, nullptr, params.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, InputCallback, input_encoder_) == paNoError) {
|
||||
if (Pa_StartStream(input_stream_) == paNoError) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
StopRecording();
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioManager::StopRecording()
|
||||
{
|
||||
if (input_stream_) {
|
||||
if (Pa_IsStreamActive(input_stream_)) {
|
||||
Pa_StopStream(input_stream_);
|
||||
}
|
||||
Pa_CloseStream(input_stream_);
|
||||
|
||||
input_stream_ = nullptr;
|
||||
}
|
||||
|
||||
if (input_encoder_) {
|
||||
input_encoder_->Close();
|
||||
delete input_encoder_;
|
||||
input_encoder_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
PaDeviceIndex AudioManager::FindConfigDeviceByName(bool is_output_device)
|
||||
{
|
||||
QString entry = is_output_device ? QStringLiteral("AudioOutput") : QStringLiteral("AudioInput");
|
||||
@@ -199,8 +253,23 @@ PaDeviceIndex AudioManager::FindDeviceByName(const QString &s, bool is_output_de
|
||||
return is_output_device ? Pa_GetDefaultOutputDevice() : Pa_GetDefaultInputDevice();
|
||||
}
|
||||
|
||||
PaStreamParameters AudioManager::GetPortAudioParams(const AudioParams ¶ms, PaDeviceIndex device)
|
||||
{
|
||||
PaStreamParameters p;
|
||||
|
||||
p.channelCount = params.channel_count();
|
||||
p.device = device;
|
||||
p.hostApiSpecificStreamInfo = nullptr;
|
||||
p.sampleFormat = GetPortAudioSampleFormat(params.format());
|
||||
p.suggestedLatency = Pa_GetDeviceInfo(device)->defaultLowOutputLatency;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
AudioManager::AudioManager() :
|
||||
output_stream_(nullptr)
|
||||
output_stream_(nullptr),
|
||||
input_stream_(nullptr),
|
||||
input_encoder_(nullptr)
|
||||
{
|
||||
#ifdef PA_HAS_JACK
|
||||
// PortAudio doesn't do a strcpy, so we need a const char that's readily accessible (i.e. not
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "audiovisualwaveform.h"
|
||||
#include "common/define.h"
|
||||
#include "codec/ffmpeg/ffmpegencoder.h"
|
||||
#include "render/audioparams.h"
|
||||
#include "render/audioplaybackcache.h"
|
||||
#include "render/previewaudiodevice.h"
|
||||
@@ -73,9 +74,15 @@ public:
|
||||
|
||||
void HardReset();
|
||||
|
||||
bool StartRecording(const QString &filename, const AudioParams ¶ms);
|
||||
|
||||
void StopRecording();
|
||||
|
||||
static PaDeviceIndex FindConfigDeviceByName(bool is_output_device);
|
||||
static PaDeviceIndex FindDeviceByName(const QString &s, bool is_output_device);
|
||||
|
||||
static PaStreamParameters GetPortAudioParams(const AudioParams &p, PaDeviceIndex device);
|
||||
|
||||
signals:
|
||||
void OutputNotify();
|
||||
|
||||
@@ -96,6 +103,8 @@ private:
|
||||
PreviewAudioDevice *output_buffer_;
|
||||
|
||||
PaDeviceIndex input_device_;
|
||||
PaStream *input_stream_;
|
||||
FFmpegEncoder *input_encoder_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -236,11 +236,6 @@ fail:
|
||||
|
||||
bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio)
|
||||
{
|
||||
if (!InitializeResampleContext(audio)) {
|
||||
qCritical() << "Failed to initialize resample context";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = true;
|
||||
|
||||
// Create input buffer
|
||||
@@ -258,6 +253,25 @@ bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio)
|
||||
}
|
||||
}
|
||||
|
||||
result = WriteAudioData(audio->audio_params(), true, const_cast<const uint8_t**>(input_data), input_sample_count);
|
||||
|
||||
if (input_data) {
|
||||
av_freep(&input_data[0]);
|
||||
av_freep(&input_data);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::WriteAudioData(const AudioParams &audio_params, bool planar, const uint8_t **input_data, int input_sample_count)
|
||||
{
|
||||
if (!InitializeResampleContext(audio_params, planar)) {
|
||||
qCritical() << "Failed to initialize resample context";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = true;
|
||||
|
||||
// Create output buffer
|
||||
int output_sample_count = input_sample_count ? swr_get_out_samples(audio_resample_ctx_, input_sample_count) : 102400;
|
||||
uint8_t** output_data = nullptr;
|
||||
@@ -308,11 +322,6 @@ bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio)
|
||||
av_freep(&output_data);
|
||||
}
|
||||
|
||||
if (input_data) {
|
||||
av_freep(&input_data[0]);
|
||||
av_freep(&input_data);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -774,7 +783,7 @@ void FFmpegEncoder::FlushCodecCtx(AVCodecContext *codec_ctx, AVStream* stream)
|
||||
av_packet_free(&pkt);
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::InitializeResampleContext(SampleBufferPtr audio)
|
||||
bool FFmpegEncoder::InitializeResampleContext(const AudioParams &audio, bool planar)
|
||||
{
|
||||
if (audio_resample_ctx_) {
|
||||
return true;
|
||||
@@ -785,9 +794,9 @@ bool FFmpegEncoder::InitializeResampleContext(SampleBufferPtr audio)
|
||||
static_cast<int64_t>(audio_codec_ctx_->channel_layout),
|
||||
audio_codec_ctx_->sample_fmt,
|
||||
audio_codec_ctx_->sample_rate,
|
||||
static_cast<int64_t>(audio->audio_params().channel_layout()),
|
||||
FFmpegUtils::GetFFmpegSampleFormat(audio->audio_params().format(), true),
|
||||
audio->audio_params().sample_rate(),
|
||||
static_cast<int64_t>(audio.channel_layout()),
|
||||
FFmpegUtils::GetFFmpegSampleFormat(audio.format(), planar),
|
||||
audio.sample_rate(),
|
||||
0,
|
||||
nullptr);
|
||||
if (!audio_resample_ctx_) {
|
||||
|
||||
@@ -47,6 +47,8 @@ public:
|
||||
|
||||
virtual bool WriteAudio(olive::SampleBufferPtr audio) override;
|
||||
|
||||
bool WriteAudioData(const AudioParams &audio_params, bool planar, const uint8_t **data, int input_sample_count);
|
||||
|
||||
virtual bool WriteSubtitle(const SubtitleBlock *sub_block) override;
|
||||
|
||||
virtual void Close() override;
|
||||
@@ -76,7 +78,7 @@ private:
|
||||
void FlushEncoders();
|
||||
void FlushCodecCtx(AVCodecContext* codec_ctx, AVStream *stream);
|
||||
|
||||
bool InitializeResampleContext(SampleBufferPtr audio);
|
||||
bool InitializeResampleContext(const AudioParams &audio, bool planar);
|
||||
|
||||
static const AVCodec *GetEncoder(ExportCodec::Codec c);
|
||||
|
||||
|
||||
+87
-253
@@ -1,5 +1,22 @@
|
||||
//Copyright 2015 Adam Quintero
|
||||
//This program is distributed under the terms of the GNU General Public License.
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "rational.h"
|
||||
|
||||
@@ -39,9 +56,9 @@ rational rational::fromString(const QString &str, bool* ok)
|
||||
|
||||
switch (elements.size()) {
|
||||
case 1:
|
||||
return rational(elements.first().toLongLong(ok));
|
||||
return rational(elements.first().toInt(ok));
|
||||
case 2:
|
||||
return rational(elements.at(0).toLongLong(ok), elements.at(1).toLongLong(ok));
|
||||
return rational(elements.at(0).toInt(ok), elements.at(1).toInt(ok));
|
||||
default:
|
||||
// Returns NaN with ok set to false
|
||||
if (ok) {
|
||||
@@ -51,63 +68,12 @@ rational rational::fromString(const QString &str, bool* ok)
|
||||
}
|
||||
}
|
||||
|
||||
//Function: ensures denom >= 0
|
||||
|
||||
void rational::fix_signs()
|
||||
{
|
||||
// Normalize so that denominator is always positive and only numerator is positive
|
||||
if (denom_ < 0) {
|
||||
denom_ = -denom_;
|
||||
numer_ = -numer_;
|
||||
} else if (denom_ == intType(0)) {
|
||||
// Normalize to 0/0 (aka NaN) if denominator is zero
|
||||
numer_ = intType(0);
|
||||
} else if (numer_ == intType(0)) {
|
||||
// Normalize to 0/1 if numerator is zero
|
||||
denom_ = intType(1);
|
||||
}
|
||||
}
|
||||
|
||||
//Function: ensures lowest form
|
||||
|
||||
void rational::reduce()
|
||||
{
|
||||
if (!isNull()) {
|
||||
// Euclidean often fails if numbers are negative, we abs it and re-neg it later if necessary
|
||||
bool neg = numer_ < 0;
|
||||
|
||||
numer_ = qAbs(numer_);
|
||||
|
||||
intType d = gcd(numer_, denom_);
|
||||
|
||||
if (d > 1) {
|
||||
numer_ /= d;
|
||||
denom_ /= d;
|
||||
}
|
||||
|
||||
if (neg) {
|
||||
numer_ = -numer_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Function: finds greatest common denominator
|
||||
|
||||
intType rational::gcd(const intType &x, const intType &y)
|
||||
{
|
||||
if (y == 0) {
|
||||
return x;
|
||||
} else {
|
||||
return gcd(y, x % y);
|
||||
}
|
||||
}
|
||||
|
||||
//Function: convert to double
|
||||
|
||||
double rational::toDouble() const
|
||||
{
|
||||
if (denom_ != 0) {
|
||||
return static_cast<double>(numer_) / static_cast<double>(denom_);
|
||||
if (r_.den != 0) {
|
||||
return av_q2d(r_);
|
||||
} else {
|
||||
return qSNaN();
|
||||
}
|
||||
@@ -115,12 +81,7 @@ double rational::toDouble() const
|
||||
|
||||
AVRational rational::toAVRational() const
|
||||
{
|
||||
AVRational r;
|
||||
|
||||
r.num = static_cast<int>(numer_);
|
||||
r.den = static_cast<int>(denom_);
|
||||
|
||||
return r;
|
||||
return r_;
|
||||
}
|
||||
|
||||
#ifdef USE_OTIO
|
||||
@@ -128,7 +89,7 @@ opentime::RationalTime rational::toRationalTime(double framerate) const
|
||||
{
|
||||
// Is this the best way of doing this?
|
||||
// Olive can store rationals as 0/0 which causes errors in OTIO
|
||||
opentime::RationalTime time = opentime::RationalTime(numer_, denom_ == 0 ? 1 : denom_);
|
||||
opentime::RationalTime time = opentime::RationalTime(r_.num, r_.den == 0 ? 1 : r_.den);
|
||||
return time.rescaled_to(framerate);
|
||||
}
|
||||
#endif
|
||||
@@ -143,64 +104,54 @@ rational rational::flipped() const
|
||||
void rational::flip()
|
||||
{
|
||||
if (!isNull()) {
|
||||
std::swap(denom_, numer_);
|
||||
std::swap(r_.den, r_.num);
|
||||
FixSigns();
|
||||
}
|
||||
}
|
||||
|
||||
bool rational::isNull() const
|
||||
{
|
||||
return numerator() == 0;
|
||||
}
|
||||
|
||||
bool rational::isNaN() const
|
||||
{
|
||||
return denominator() == 0;
|
||||
}
|
||||
|
||||
const intType &rational::numerator() const
|
||||
{
|
||||
return numer_;
|
||||
}
|
||||
|
||||
const intType &rational::denominator() const
|
||||
{
|
||||
return denom_;
|
||||
}
|
||||
|
||||
QString rational::toString() const
|
||||
{
|
||||
return QStringLiteral("%1/%2").arg(QString::number(numer_), QString::number(denom_));
|
||||
return QStringLiteral("%1/%2").arg(QString::number(r_.num), QString::number(r_.den));
|
||||
}
|
||||
|
||||
void rational::FixSigns()
|
||||
{
|
||||
if (r_.den < 0) {
|
||||
// Normalize so that denominator is always positive
|
||||
r_.den = -r_.den;
|
||||
r_.num = -r_.num;
|
||||
} else if (r_.den == 0) {
|
||||
// Normalize to 0/0 (aka NaN) if denominator is zero
|
||||
r_.num = 0;
|
||||
} else if (r_.num == 0) {
|
||||
// Normalize to 0/1 if numerator is zero
|
||||
r_.den = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void rational::Reduce()
|
||||
{
|
||||
av_reduce(&r_.num, &r_.den, r_.num, r_.den, INT_MAX);
|
||||
}
|
||||
|
||||
//Assignment Operators
|
||||
|
||||
const rational& rational::operator=(const rational &rhs)
|
||||
{
|
||||
if (this != &rhs) {
|
||||
numer_ = rhs.numer_;
|
||||
denom_ = rhs.denom_;
|
||||
}
|
||||
|
||||
r_ = rhs.r_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const rational& rational::operator+=(const rational &rhs)
|
||||
{
|
||||
Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX);
|
||||
|
||||
if (!isNaN()) {
|
||||
if (rhs.isNaN()) {
|
||||
// Set to NaN
|
||||
denom_ = 0;
|
||||
fix_signs();
|
||||
} else if (!rhs.isNull()) {
|
||||
if (isNull()) {
|
||||
numer_ = rhs.numer_;
|
||||
denom_ = rhs.denom_;
|
||||
*this = NaN;
|
||||
} else {
|
||||
numer_ = (numer_ * rhs.denom_) + (rhs.numer_ * denom_);
|
||||
denom_ = denom_ * rhs.denom_;
|
||||
fix_signs();
|
||||
reduce();
|
||||
}
|
||||
r_ = av_add_q(r_, rhs.r_);
|
||||
FixSigns();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,39 +160,14 @@ const rational& rational::operator+=(const rational &rhs)
|
||||
|
||||
const rational& rational::operator-=(const rational &rhs)
|
||||
{
|
||||
Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX);
|
||||
|
||||
if (!isNaN()) {
|
||||
if (rhs.isNaN()) {
|
||||
// Set to NaN
|
||||
denom_ = 0;
|
||||
fix_signs();
|
||||
} else if (!rhs.isNull()) {
|
||||
if (isNull()) {
|
||||
numer_ = -rhs.numer_;
|
||||
denom_ = rhs.denom_;
|
||||
*this = NaN;
|
||||
} else {
|
||||
numer_ = (numer_ * rhs.denom_) - (rhs.numer_ * denom_);
|
||||
denom_ = denom_ * rhs.denom_;
|
||||
fix_signs();
|
||||
reduce();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
const rational& rational::operator/=(const rational &rhs)
|
||||
{
|
||||
if (!isNaN()) {
|
||||
if (rhs.isNaN()) {
|
||||
// Set to NaN
|
||||
denom_ = 0;
|
||||
fix_signs();
|
||||
} else {
|
||||
numer_ = numer_ * rhs.denom_;
|
||||
denom_ = denom_ * rhs.numer_;
|
||||
fix_signs();
|
||||
reduce();
|
||||
r_ = av_sub_q(r_, rhs.r_);
|
||||
FixSigns();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,15 +176,30 @@ const rational& rational::operator/=(const rational &rhs)
|
||||
|
||||
const rational& rational::operator*=(const rational &rhs)
|
||||
{
|
||||
Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX);
|
||||
|
||||
if (!isNaN()) {
|
||||
if (rhs.isNaN()) {
|
||||
denom_ = 0;
|
||||
fix_signs();
|
||||
*this = NaN;
|
||||
} else {
|
||||
numer_ = numer_ * rhs.numer_;
|
||||
denom_ = denom_ * rhs.denom_;
|
||||
fix_signs();
|
||||
reduce();
|
||||
r_ = av_mul_q(r_, rhs.r_);
|
||||
FixSigns();
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
const rational& rational::operator/=(const rational &rhs)
|
||||
{
|
||||
Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX);
|
||||
|
||||
if (!isNaN()) {
|
||||
if (rhs.isNaN()) {
|
||||
*this = NaN;
|
||||
} else {
|
||||
r_ = av_div_q(r_, rhs.r_);
|
||||
FixSigns();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,87 +240,29 @@ rational rational::operator*(const rational &rhs) const
|
||||
|
||||
bool rational::operator<(const rational &rhs) const
|
||||
{
|
||||
if (isNaN() || rhs.isNaN()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isNull() && rhs.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rhs == RATIONAL_MAX
|
||||
|| *this == RATIONAL_MIN) {
|
||||
// We will always either be LESS THAN (true) or EQUAL (false)
|
||||
return (*this != rhs);
|
||||
}
|
||||
|
||||
if (*this == RATIONAL_MAX
|
||||
|| rhs == RATIONAL_MIN) {
|
||||
// We will always be GREATER THAN (false) or EQUAL (false)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isNull() && rhs.isNull()) {
|
||||
return (numer_ * denom_ < intType(0));
|
||||
}
|
||||
|
||||
if (isNull() && !rhs.isNull()) {
|
||||
return !(rhs.numer_ * rhs.denom_ < intType(0));
|
||||
}
|
||||
|
||||
return ((numer_ * rhs.denom_) < (denom_ * rhs.numer_));
|
||||
return av_cmp_q(r_, rhs.r_) == -1;
|
||||
}
|
||||
|
||||
bool rational::operator<=(const rational &rhs) const
|
||||
{
|
||||
if (isNaN() || rhs.isNaN()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isNull() && rhs.isNull()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (rhs == RATIONAL_MAX
|
||||
|| *this == RATIONAL_MIN) {
|
||||
// We will always either be LESS THAN (true) or EQUAL (true)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (*this == RATIONAL_MAX
|
||||
|| rhs == RATIONAL_MIN) {
|
||||
// We will always be GREATER THAN (false) or EQUAL (true)
|
||||
return rhs == *this;
|
||||
}
|
||||
|
||||
if (!isNull() && rhs.isNull()) {
|
||||
return (numer_ * denom_ < intType(0));
|
||||
}
|
||||
|
||||
if (isNull() && !rhs.isNull()) {
|
||||
return !(rhs.numer_ * rhs.denom_ < intType(0));
|
||||
}
|
||||
|
||||
return ((numer_ * rhs.denom_) <= (denom_ * rhs.numer_));
|
||||
int cmp = av_cmp_q(r_, rhs.r_);
|
||||
return cmp == 0 || cmp == -1;
|
||||
}
|
||||
|
||||
bool rational::operator>(const rational &rhs) const
|
||||
{
|
||||
return rhs < *this;
|
||||
return av_cmp_q(r_, rhs.r_) == 1;
|
||||
}
|
||||
|
||||
bool rational::operator>=(const rational &rhs) const
|
||||
{
|
||||
return rhs <= *this;
|
||||
int cmp = av_cmp_q(r_, rhs.r_);
|
||||
return cmp == 0 || cmp == 1;
|
||||
}
|
||||
|
||||
bool rational::operator==(const rational &rhs) const
|
||||
{
|
||||
if (isNaN() || rhs.isNaN()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (numer_ == rhs.numer_ && denom_ == rhs.denom_);
|
||||
return av_cmp_q(r_, rhs.r_) == 0;
|
||||
}
|
||||
|
||||
bool rational::operator!=(const rational &rhs) const
|
||||
@@ -387,55 +270,6 @@ bool rational::operator!=(const rational &rhs) const
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
const rational& rational::operator+() const
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
rational rational::operator-() const
|
||||
{
|
||||
return rational(numer_, -denom_);
|
||||
}
|
||||
|
||||
bool rational::operator!() const
|
||||
{
|
||||
return !numer_;
|
||||
}
|
||||
|
||||
//IO
|
||||
|
||||
std::ostream& operator<<(std::ostream &out, const rational &value)
|
||||
{
|
||||
out << value.numer_;
|
||||
|
||||
if (value.denom_ != 1) {
|
||||
out << '/' << value.denom_;
|
||||
return out;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::istream& operator>>(std::istream &in, rational &value)
|
||||
{
|
||||
in >> value.numer_;
|
||||
value.denom_ = 1;
|
||||
|
||||
char ch;
|
||||
in.get(ch);
|
||||
|
||||
if(!in.eof()) {
|
||||
if(ch == '/') {
|
||||
in >> value.denom_;
|
||||
value.fix_signs();
|
||||
value.reduce();
|
||||
} else {
|
||||
in.putback(ch);
|
||||
}
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
||||
uint qHash(const rational &r, uint seed)
|
||||
{
|
||||
return ::qHash(r.toDouble(), seed);
|
||||
|
||||
+60
-55
@@ -1,60 +1,67 @@
|
||||
//Copyright 2015 Adam Quintero
|
||||
//This program is distributed under the terms of the GNU General Public License.
|
||||
/***
|
||||
|
||||
// Adapted by MattKC for the Olive Video Editor (2019)
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef RATIONAL_H
|
||||
#define RATIONAL_H
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/rational.h>
|
||||
}
|
||||
|
||||
#include <iostream>
|
||||
#include <QDebug>
|
||||
#include <QMetaType>
|
||||
|
||||
#ifdef USE_OTIO
|
||||
#include <opentime/rationalTime.h>
|
||||
#endif
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMetaType>
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
}
|
||||
|
||||
#include "common/define.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
typedef int64_t intType;
|
||||
/*
|
||||
* Zero Handling
|
||||
* 0/0 = 0
|
||||
* 0/non-zero = 0
|
||||
* non-zero/0 = 0
|
||||
*/
|
||||
class rational
|
||||
{
|
||||
public:
|
||||
//constructors
|
||||
rational(const intType &numerator = 0) :
|
||||
numer_(numerator),
|
||||
denom_(1)
|
||||
rational(const int &numerator = 0)
|
||||
{
|
||||
r_.num = numerator;
|
||||
r_.den = 1;
|
||||
}
|
||||
|
||||
rational(const intType &numerator, const intType &denominator) :
|
||||
numer_(numerator),
|
||||
denom_(denominator)
|
||||
rational(const int &numerator, const int &denominator)
|
||||
{
|
||||
fix_signs();
|
||||
reduce();
|
||||
r_.num = numerator;
|
||||
r_.den = denominator;
|
||||
|
||||
FixSigns();
|
||||
Reduce();
|
||||
}
|
||||
|
||||
rational(const rational &rhs) = default;
|
||||
|
||||
rational(const AVRational& r) :
|
||||
numer_(r.num),
|
||||
denom_(r.den)
|
||||
rational(const AVRational& r)
|
||||
{
|
||||
fix_signs();
|
||||
reduce();
|
||||
r_ = r;
|
||||
|
||||
FixSigns();
|
||||
}
|
||||
|
||||
static rational fromDouble(const double& flt, bool *ok = nullptr);
|
||||
@@ -84,9 +91,9 @@ public:
|
||||
bool operator!=(const rational &rhs) const;
|
||||
|
||||
//Unary operators
|
||||
const rational& operator+() const;
|
||||
rational operator-() const;
|
||||
bool operator!() const;
|
||||
const rational& operator+() const { return *this; }
|
||||
rational operator-() const { return rational(r_.num, -r_.den); }
|
||||
bool operator!() const { return !r_.num; }
|
||||
|
||||
//Function: convert to double
|
||||
double toDouble() const;
|
||||
@@ -100,7 +107,7 @@ public:
|
||||
return fromDouble(t.to_seconds());
|
||||
}
|
||||
|
||||
// Convert Olive ratioanls to opentime rationals with the given framerate (defaults to 24)
|
||||
// Convert Olive rationals to opentime rationals with the given framerate (defaults to 24)
|
||||
opentime::RationalTime toRationalTime(double framerate = 24) const;
|
||||
#endif
|
||||
|
||||
@@ -111,35 +118,33 @@ public:
|
||||
// Returns whether the rational is valid but equal to zero or not
|
||||
//
|
||||
// A NaN is always a null, but a null is not always a NaN
|
||||
bool isNull() const;
|
||||
bool isNull() const { return r_.num == 0; }
|
||||
|
||||
// Returns whether this rational is not a valid number
|
||||
bool isNaN() const;
|
||||
// Returns whether this rational is not a valid number (denominator == 0)
|
||||
bool isNaN() const { return r_.den == 0; }
|
||||
|
||||
//IO
|
||||
friend std::ostream& operator<<(std::ostream &out, const rational &value);
|
||||
friend std::istream& operator>>(std::istream &in, rational &value);
|
||||
|
||||
const intType& numerator() const;
|
||||
const intType& denominator() const;
|
||||
const int& numerator() const { return r_.num; }
|
||||
const int& denominator() const { return r_.den; }
|
||||
|
||||
QString toString() const;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream &out, const rational &value)
|
||||
{
|
||||
out << value.r_.num << '/' << value.r_.den;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private:
|
||||
//numerator and denominator
|
||||
intType numer_;
|
||||
intType denom_;
|
||||
void FixSigns();
|
||||
void Reduce();
|
||||
|
||||
AVRational r_;
|
||||
|
||||
//Function: ensures denom >= 0
|
||||
void fix_signs();
|
||||
//Function: ensures lowest form
|
||||
void reduce();
|
||||
//Function: finds greatest common denominator
|
||||
static intType gcd(const intType &x, const intType &y);
|
||||
};
|
||||
|
||||
#define RATIONAL_MIN rational(INT64_MIN, 1)
|
||||
#define RATIONAL_MAX rational(INT64_MAX, 1)
|
||||
#define RATIONAL_MIN rational(INT_MIN)
|
||||
#define RATIONAL_MAX rational(INT_MAX)
|
||||
|
||||
uint qHash(const rational& r, uint seed = 0);
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ const rational &TimeRange::out() const
|
||||
|
||||
const rational &TimeRange::length() const
|
||||
{
|
||||
Q_ASSERT(!length_.isNaN());
|
||||
return length_;
|
||||
}
|
||||
|
||||
@@ -173,8 +174,12 @@ void TimeRange::normalize()
|
||||
}
|
||||
|
||||
// Calculate length
|
||||
if (out_ == RATIONAL_MIN || out_ == RATIONAL_MAX || in_ == RATIONAL_MIN || in_ == RATIONAL_MAX) {
|
||||
length_ = rational::NaN;
|
||||
} else {
|
||||
length_ = out_ - in_;
|
||||
}
|
||||
}
|
||||
|
||||
void TimeRangeList::insert(const TimeRangeList &list_to_add)
|
||||
{
|
||||
|
||||
@@ -135,6 +135,8 @@ void Config::SetDefaults()
|
||||
// Online/offline settings
|
||||
SetEntryInternal(QStringLiteral("OnlinePixelFormat"), NodeValue::kInt, VideoParams::kFormatFloat32);
|
||||
SetEntryInternal(QStringLiteral("OfflinePixelFormat"), NodeValue::kInt, VideoParams::kFormatFloat16);
|
||||
|
||||
SetEntryInternal(QStringLiteral("MarkerColor"), NodeValue::kInt, ColorCoding::kLime);
|
||||
}
|
||||
|
||||
void Config::Load()
|
||||
|
||||
+3
-3
@@ -245,14 +245,14 @@ UndoStack *Core::undo_stack()
|
||||
return &undo_stack_;
|
||||
}
|
||||
|
||||
void Core::ImportFiles(const QStringList &urls, ProjectViewModel* model, Folder* parent)
|
||||
void Core::ImportFiles(const QStringList &urls, Folder* parent)
|
||||
{
|
||||
if (urls.isEmpty()) {
|
||||
QMessageBox::critical(main_window_, tr("Import error"), tr("Nothing to import"));
|
||||
return;
|
||||
}
|
||||
|
||||
ProjectImportTask* pim = new ProjectImportTask(model, parent, urls);
|
||||
ProjectImportTask* pim = new ProjectImportTask(parent, urls);
|
||||
|
||||
if (!pim->GetFileCount()) {
|
||||
// No files to import
|
||||
@@ -364,7 +364,7 @@ void Core::DialogImportShow()
|
||||
// Get the selected folder in this panel
|
||||
Folder* folder = active_project_panel->GetSelectedFolder();
|
||||
|
||||
ImportFiles(files, active_project_panel->model(), folder);
|
||||
ImportFiles(files, folder);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -182,7 +182,7 @@ public:
|
||||
*
|
||||
* @param urls
|
||||
*/
|
||||
void ImportFiles(const QStringList& urls, ProjectViewModel *model, Folder *parent);
|
||||
void ImportFiles(const QStringList& urls, Folder *parent);
|
||||
|
||||
/**
|
||||
* @brief Get the currently active tool
|
||||
|
||||
@@ -24,6 +24,7 @@ add_subdirectory(export)
|
||||
add_subdirectory(footageproperties)
|
||||
add_subdirectory(footagerelink)
|
||||
add_subdirectory(keyframeproperties)
|
||||
add_subdirectory(markerproperties)
|
||||
if(OpenTimelineIO_FOUND)
|
||||
add_subdirectory(otioproperties)
|
||||
endif()
|
||||
|
||||
@@ -24,6 +24,8 @@ set(OLIVE_SOURCES
|
||||
dialog/export/exportadvancedvideodialog.h
|
||||
dialog/export/exportaudiotab.cpp
|
||||
dialog/export/exportaudiotab.h
|
||||
dialog/export/exportformatcombobox.cpp
|
||||
dialog/export/exportformatcombobox.h
|
||||
dialog/export/exportsubtitlestab.cpp
|
||||
dialog/export/exportsubtitlestab.h
|
||||
dialog/export/exportvideotab.cpp
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "task/taskmanager.h"
|
||||
#include "ui/icons/icons.h"
|
||||
#include "widget/timeruler/timeruler.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -117,7 +118,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
|
||||
row++;
|
||||
|
||||
preferences_layout->addWidget(new QLabel(tr("Format:")), row, 0);
|
||||
format_combobox_ = new QComboBox();
|
||||
format_combobox_ = new ExportFormatComboBox();
|
||||
preferences_layout->addWidget(format_combobox_, row, 1, 1, 3);
|
||||
|
||||
row++;
|
||||
@@ -192,33 +193,11 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
|
||||
// Set default filename
|
||||
SetDefaultFilename();
|
||||
|
||||
// Populate combobox formats
|
||||
for (int i=0; i<ExportFormat::kFormatCount; i++) {
|
||||
QString format_name = ExportFormat::GetName(static_cast<ExportFormat::Format>(i));
|
||||
|
||||
bool inserted = false;
|
||||
|
||||
for (int j=0; j<format_combobox_->count(); j++) {
|
||||
if (format_combobox_->itemText(j) > format_name) {
|
||||
format_combobox_->insertItem(j, format_name, i);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inserted) {
|
||||
format_combobox_->addItem(format_name, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Set defaults
|
||||
previously_selected_format_ = ExportFormat::kFormatMPEG4;
|
||||
SetCurrentFormat(ExportFormat::kFormatMPEG4);
|
||||
connect(format_combobox_,
|
||||
static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
this,
|
||||
&ExportDialog::FormatChanged);
|
||||
FormatChanged(format_combobox_->currentIndex());
|
||||
format_combobox_->SetFormat(ExportFormat::kFormatMPEG4);
|
||||
connect(format_combobox_, &ExportFormatComboBox::FormatChanged, this, &ExportDialog::FormatChanged);
|
||||
FormatChanged(format_combobox_->GetFormat());
|
||||
|
||||
VideoParams vp = viewer_node_->GetVideoParams();
|
||||
AudioParams ap = viewer_node_->GetAudioParams();
|
||||
@@ -272,11 +251,6 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
|
||||
preview_viewer_->SetColorTransform(video_tab_->CurrentOCIOColorSpace());
|
||||
}
|
||||
|
||||
ExportFormat::Format ExportDialog::GetSelectedFormat() const
|
||||
{
|
||||
return static_cast<ExportFormat::Format>(format_combobox_->currentData().toInt());
|
||||
}
|
||||
|
||||
rational ExportDialog::GetSelectedTimebase() const
|
||||
{
|
||||
return video_tab_->GetSelectedFrameRate().flipped();
|
||||
@@ -292,7 +266,7 @@ void ExportDialog::StartExport()
|
||||
|
||||
// Validate if the entered filename contains the correct extension (the extension is necessary
|
||||
// for both FFmpeg and OIIO to determine the output format)
|
||||
QString necessary_ext = QStringLiteral(".%1").arg(ExportFormat::GetExtension(GetSelectedFormat()));
|
||||
QString necessary_ext = QStringLiteral(".%1").arg(ExportFormat::GetExtension(format_combobox_->GetFormat()));
|
||||
QString proposed_filename = filename_edit_->text().trimmed();
|
||||
|
||||
// If it doesn't, see if the user wants to append it automatically. If not, we don't abort the export.
|
||||
@@ -427,7 +401,7 @@ void ExportDialog::AddPreferencesTab(QWidget *inner_widget, const QString &title
|
||||
|
||||
void ExportDialog::BrowseFilename()
|
||||
{
|
||||
ExportFormat::Format f = GetSelectedFormat();
|
||||
ExportFormat::Format f = format_combobox_->GetFormat();
|
||||
|
||||
QString browsed_fn = QFileDialog::getSaveFileName(this,
|
||||
"",
|
||||
@@ -443,11 +417,10 @@ void ExportDialog::BrowseFilename()
|
||||
}
|
||||
}
|
||||
|
||||
void ExportDialog::FormatChanged(int index)
|
||||
void ExportDialog::FormatChanged(ExportFormat::Format current_format)
|
||||
{
|
||||
QString current_filename = filename_edit_->text().trimmed();
|
||||
QString previously_selected_ext = ExportFormat::GetExtension(previously_selected_format_);
|
||||
ExportFormat::Format current_format = static_cast<ExportFormat::Format>(format_combobox_->itemData(index).toInt());
|
||||
QString currently_selected_ext = ExportFormat::GetExtension(current_format);
|
||||
|
||||
// If the previous extension was added, remove it
|
||||
@@ -545,7 +518,7 @@ ExportParams ExportDialog::GenerateParams() const
|
||||
AudioParams::kInternalFormat);
|
||||
|
||||
ExportParams params;
|
||||
params.set_encoder(Encoder::GetTypeFromFormat(GetSelectedFormat()));
|
||||
params.set_encoder(Encoder::GetTypeFromFormat(format_combobox_->GetFormat()));
|
||||
params.SetFilename(filename_edit_->text().trimmed());
|
||||
params.SetExportLength(viewer_node_->GetLength());
|
||||
|
||||
@@ -593,16 +566,6 @@ ExportParams ExportDialog::GenerateParams() const
|
||||
return params;
|
||||
}
|
||||
|
||||
void ExportDialog::SetCurrentFormat(ExportFormat::Format format)
|
||||
{
|
||||
for (int i=0; i<format_combobox_->count(); i++) {
|
||||
if (format_combobox_->itemData(i).toInt() == format) {
|
||||
format_combobox_->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rational ExportDialog::GetExportLength() const
|
||||
{
|
||||
if (range_combobox_->currentIndex() == kRangeInToOut) {
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
#include "codec/exportcodec.h"
|
||||
#include "codec/exportformat.h"
|
||||
#include "dialog/export/exportformatcombobox.h"
|
||||
#include "exportaudiotab.h"
|
||||
#include "exportsubtitlestab.h"
|
||||
#include "exportvideotab.h"
|
||||
@@ -43,8 +44,6 @@ class ExportDialog : public QDialog
|
||||
public:
|
||||
ExportDialog(ViewerOutput* viewer_node, QWidget* parent = nullptr);
|
||||
|
||||
ExportFormat::Format GetSelectedFormat() const;
|
||||
|
||||
rational GetSelectedTimebase() const;
|
||||
|
||||
protected:
|
||||
@@ -58,8 +57,6 @@ private:
|
||||
|
||||
ExportParams GenerateParams() const;
|
||||
|
||||
void SetCurrentFormat(ExportFormat::Format format);
|
||||
|
||||
ViewerOutput* viewer_node_;
|
||||
|
||||
ExportFormat::Format previously_selected_format_;
|
||||
@@ -82,7 +79,7 @@ private:
|
||||
|
||||
ViewerWidget* preview_viewer_;
|
||||
QLineEdit* filename_edit_;
|
||||
QComboBox* format_combobox_;
|
||||
ExportFormatComboBox* format_combobox_;
|
||||
|
||||
ExportVideoTab* video_tab_;
|
||||
ExportAudioTab* audio_tab_;
|
||||
@@ -98,7 +95,7 @@ private:
|
||||
private slots:
|
||||
void BrowseFilename();
|
||||
|
||||
void FormatChanged(int index);
|
||||
void FormatChanged(ExportFormat::Format current_format);
|
||||
|
||||
void ResolutionChanged();
|
||||
|
||||
|
||||
@@ -37,8 +37,6 @@ class ExportAudioTab : public QWidget
|
||||
public:
|
||||
ExportAudioTab(QWidget* parent = nullptr);
|
||||
|
||||
int SetFormat(ExportFormat::Format format);
|
||||
|
||||
QComboBox* codec_combobox() const
|
||||
{
|
||||
return codec_combobox_;
|
||||
@@ -59,6 +57,9 @@ public:
|
||||
return bit_rate_slider_;
|
||||
}
|
||||
|
||||
public slots:
|
||||
int SetFormat(ExportFormat::Format format);
|
||||
|
||||
private:
|
||||
QComboBox* codec_combobox_;
|
||||
SampleRateComboBox* sample_rate_combobox_;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "exportformatcombobox.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
ExportFormatComboBox::ExportFormatComboBox(Mode mode, QWidget *parent) :
|
||||
QComboBox(parent)
|
||||
{
|
||||
// Populate combobox formats
|
||||
for (int i=0; i<ExportFormat::kFormatCount; i++) {
|
||||
ExportFormat::Format f = static_cast<ExportFormat::Format>(i);
|
||||
|
||||
switch (mode) {
|
||||
case kShowAllFormats:
|
||||
break;
|
||||
case kShowAudioOnly:
|
||||
if (!ExportFormat::GetVideoCodecs(f).isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case kShowVideoOnly:
|
||||
if (!ExportFormat::GetAudioCodecs(f).isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
QString format_name = ExportFormat::GetName(f);
|
||||
|
||||
bool inserted = false;
|
||||
|
||||
// Sort formats alphabetically
|
||||
for (int j=0; j<count(); j++) {
|
||||
if (itemText(j) > format_name) {
|
||||
insertItem(j, format_name, i);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inserted) {
|
||||
addItem(format_name, i);
|
||||
}
|
||||
}
|
||||
|
||||
connect(this, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ExportFormatComboBox::HandleIndexChange);
|
||||
}
|
||||
|
||||
void ExportFormatComboBox::SetFormat(ExportFormat::Format fmt)
|
||||
{
|
||||
for (int i=0; i<count(); i++) {
|
||||
if (itemData(i).toInt() == fmt) {
|
||||
setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExportFormatComboBox::HandleIndexChange(int index)
|
||||
{
|
||||
emit FormatChanged(static_cast<ExportFormat::Format>(itemData(index).toInt()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef EXPORTFORMATCOMBOBOX_H
|
||||
#define EXPORTFORMATCOMBOBOX_H
|
||||
|
||||
#include <QComboBox>
|
||||
|
||||
#include "codec/exportformat.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class ExportFormatComboBox : public QComboBox
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Mode {
|
||||
kShowAllFormats,
|
||||
kShowAudioOnly,
|
||||
kShowVideoOnly
|
||||
};
|
||||
|
||||
ExportFormatComboBox(Mode mode, QWidget *parent = nullptr);
|
||||
ExportFormatComboBox(QWidget *parent = nullptr) :
|
||||
ExportFormatComboBox(kShowAllFormats, parent)
|
||||
{}
|
||||
|
||||
ExportFormat::Format GetFormat() const
|
||||
{
|
||||
return static_cast<ExportFormat::Format>(currentData().toInt());
|
||||
}
|
||||
|
||||
signals:
|
||||
void FormatChanged(ExportFormat::Format fmt);
|
||||
|
||||
public slots:
|
||||
void SetFormat(ExportFormat::Format fmt);
|
||||
|
||||
private slots:
|
||||
void HandleIndexChange(int index);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // EXPORTFORMATCOMBOBOX_H
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
KeyframePropertiesDialog::KeyframePropertiesDialog(const QVector<NodeKeyframe*> &keys, const rational &timebase, QWidget *parent) :
|
||||
KeyframePropertiesDialog::KeyframePropertiesDialog(const std::vector<NodeKeyframe*> &keys, const rational &timebase, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
keys_(keys),
|
||||
timebase_(timebase)
|
||||
@@ -91,7 +91,7 @@ KeyframePropertiesDialog::KeyframePropertiesDialog(const QVector<NodeKeyframe*>
|
||||
bool all_same_bezier_out_x = true;
|
||||
bool all_same_bezier_out_y = true;
|
||||
|
||||
for (int i=0;i<keys_.size();i++) {
|
||||
for (size_t i=0;i<keys_.size();i++) {
|
||||
if (i > 0) {
|
||||
NodeKeyframe* prev_key = keys_.at(i-1);
|
||||
NodeKeyframe* this_key = keys_.at(i);
|
||||
@@ -126,7 +126,7 @@ KeyframePropertiesDialog::KeyframePropertiesDialog(const QVector<NodeKeyframe*>
|
||||
|
||||
// Determine if any keyframes are on the same track (in which case we can't set the time)
|
||||
if (can_set_time) {
|
||||
for (int j=0;j<keys_.size();j++) {
|
||||
for (size_t j=0;j<keys_.size();j++) {
|
||||
if (i != j
|
||||
&& keys_.at(j)->track() == keys_.at(i)->track()) {
|
||||
can_set_time = false;
|
||||
@@ -147,7 +147,7 @@ KeyframePropertiesDialog::KeyframePropertiesDialog(const QVector<NodeKeyframe*>
|
||||
}
|
||||
|
||||
if (all_same_time) {
|
||||
time_slider_->SetValue(keys_.first()->time());
|
||||
time_slider_->SetValue(keys_.front()->time());
|
||||
} else {
|
||||
time_slider_->SetTristate();
|
||||
}
|
||||
@@ -169,7 +169,7 @@ KeyframePropertiesDialog::KeyframePropertiesDialog(const QVector<NodeKeyframe*>
|
||||
if (all_same_type) {
|
||||
// If all keyframes are the same type, set it here
|
||||
for (int i=0;i<type_select_->count();i++) {
|
||||
if (type_select_->itemData(i).toInt() == keys_.first()->type()) {
|
||||
if (type_select_->itemData(i).toInt() == keys_.front()->type()) {
|
||||
type_select_->setCurrentIndex(i);
|
||||
|
||||
// Ensure UI updates for this index
|
||||
@@ -179,10 +179,10 @@ KeyframePropertiesDialog::KeyframePropertiesDialog(const QVector<NodeKeyframe*>
|
||||
}
|
||||
}
|
||||
|
||||
SetUpBezierSlider(bezier_in_x_slider_, all_same_bezier_in_x, keys_.first()->bezier_control_in().x());
|
||||
SetUpBezierSlider(bezier_in_y_slider_, all_same_bezier_in_y, keys_.first()->bezier_control_in().y());
|
||||
SetUpBezierSlider(bezier_out_x_slider_, all_same_bezier_out_x, keys_.first()->bezier_control_out().x());
|
||||
SetUpBezierSlider(bezier_out_y_slider_, all_same_bezier_out_y, keys_.first()->bezier_control_out().y());
|
||||
SetUpBezierSlider(bezier_in_x_slider_, all_same_bezier_in_x, keys_.front()->bezier_control_in().x());
|
||||
SetUpBezierSlider(bezier_in_y_slider_, all_same_bezier_in_y, keys_.front()->bezier_control_in().y());
|
||||
SetUpBezierSlider(bezier_out_x_slider_, all_same_bezier_out_x, keys_.front()->bezier_control_out().x());
|
||||
SetUpBezierSlider(bezier_out_y_slider_, all_same_bezier_out_y, keys_.front()->bezier_control_out().y());
|
||||
|
||||
row++;
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class KeyframePropertiesDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
KeyframePropertiesDialog(const QVector<NodeKeyframe*>& keys, const rational& timebase, QWidget* parent = nullptr);
|
||||
KeyframePropertiesDialog(const std::vector<NodeKeyframe*>& keys, const rational& timebase, QWidget* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
virtual void accept() override;
|
||||
@@ -43,7 +43,7 @@ public slots:
|
||||
private:
|
||||
void SetUpBezierSlider(FloatSlider *slider, bool all_same, double value);
|
||||
|
||||
const QVector<NodeKeyframe*>& keys_;
|
||||
const std::vector<NodeKeyframe*>& keys_;
|
||||
|
||||
rational timebase_;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
widget/snapservice/snapservice.cpp
|
||||
widget/snapservice/snapservice.h
|
||||
dialog/markerproperties/markerpropertiesdialog.h
|
||||
dialog/markerproperties/markerpropertiesdialog.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,152 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "markerpropertiesdialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super QDialog
|
||||
|
||||
MarkerPropertiesDialog::MarkerPropertiesDialog(const std::vector<TimelineMarker *> &markers, const rational &timebase, QWidget *parent) :
|
||||
super(parent),
|
||||
markers_(markers)
|
||||
{
|
||||
QGridLayout *layout = new QGridLayout(this);
|
||||
|
||||
int row = 0;
|
||||
|
||||
QGroupBox *time_group = new QGroupBox(tr("Time"));
|
||||
QGridLayout *time_layout = new QGridLayout(time_group);
|
||||
|
||||
{
|
||||
int time_row = 0;
|
||||
|
||||
time_layout->addWidget(new QLabel(tr("In:")), time_row, 0);
|
||||
|
||||
in_slider_ = new RationalSlider();
|
||||
time_layout->addWidget(in_slider_, time_row, 1);
|
||||
|
||||
time_row++;
|
||||
|
||||
time_layout->addWidget(new QLabel(tr("Out:")), time_row, 0);
|
||||
|
||||
out_slider_ = new RationalSlider();
|
||||
time_layout->addWidget(out_slider_, time_row, 1);
|
||||
}
|
||||
|
||||
if (markers.size() == 1) {
|
||||
in_slider_->SetValue(markers.front()->time_range().in());
|
||||
in_slider_->SetDisplayType(RationalSlider::kTime);
|
||||
in_slider_->SetTimebase(timebase);
|
||||
out_slider_->SetValue(markers.front()->time_range().out());
|
||||
out_slider_->SetDisplayType(RationalSlider::kTime);
|
||||
out_slider_->SetTimebase(timebase);
|
||||
} else {
|
||||
// Markers cannot be on the same time, so we disable setting time if multiple markers are selected
|
||||
in_slider_->setEnabled(false);
|
||||
in_slider_->SetTristate();
|
||||
out_slider_->setEnabled(false);
|
||||
out_slider_->SetTristate();
|
||||
}
|
||||
|
||||
layout->addWidget(time_group, row, 0, 1, 2);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Color:")), row, 0);
|
||||
|
||||
color_menu_ = new ColorCodingComboBox();
|
||||
layout->addWidget(color_menu_, row, 1);
|
||||
|
||||
color_menu_->SetColor(markers.front()->color());
|
||||
for (size_t i=1; i<markers.size(); i++) {
|
||||
if (markers.at(i)->color() != color_menu_->GetSelectedColor()) {
|
||||
color_menu_->SetColor(-1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Name:")), row, 0);
|
||||
|
||||
label_edit_ = new LineEditWithFocusSignal();
|
||||
connect(label_edit_, &LineEditWithFocusSignal::Focused, this, [this]{
|
||||
label_edit_->setPlaceholderText(QString());
|
||||
});
|
||||
layout->addWidget(label_edit_, row, 1);
|
||||
|
||||
// Determine what the startup label text should be
|
||||
label_edit_->setText(markers.front()->name());
|
||||
for (size_t i=1; i<markers.size(); i++) {
|
||||
if (markers.at(i)->name() != label_edit_->text()) {
|
||||
label_edit_->clear();
|
||||
label_edit_->setPlaceholderText(tr("(multiple)"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
row++;
|
||||
|
||||
QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &MarkerPropertiesDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &MarkerPropertiesDialog::reject);
|
||||
layout->addWidget(buttons, row, 0, 1, 2);
|
||||
}
|
||||
|
||||
void MarkerPropertiesDialog::accept()
|
||||
{
|
||||
if (in_slider_->isEnabled() && in_slider_->GetValue() > out_slider_->GetValue()) {
|
||||
QMessageBox::critical(this, tr("Invalid Values"), tr("In point must be less than or equal to out point."));
|
||||
return;
|
||||
}
|
||||
|
||||
MultiUndoCommand *command = new MultiUndoCommand();
|
||||
|
||||
int color = color_menu_->GetSelectedColor();
|
||||
|
||||
foreach (TimelineMarker *m, markers_) {
|
||||
if (color != -1) {
|
||||
command->add_child(new MarkerChangeColorCommand(m, color));
|
||||
}
|
||||
|
||||
if (label_edit_->placeholderText().isEmpty()) {
|
||||
command->add_child(new MarkerChangeNameCommand(m, label_edit_->text()));
|
||||
}
|
||||
}
|
||||
|
||||
if (markers_.size() == 1) {
|
||||
command->add_child(new MarkerChangeTimeCommand(markers_.front(), TimeRange(in_slider_->GetValue(), out_slider_->GetValue())));
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
|
||||
super::accept();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef MARKERPROPERTIESDIALOG_H
|
||||
#define MARKERPROPERTIESDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLineEdit>
|
||||
|
||||
#include "timeline/timelinemarker.h"
|
||||
#include "widget/colorlabelmenu/colorcodingcombobox.h"
|
||||
#include "widget/slider/rationalslider.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class LineEditWithFocusSignal : public QLineEdit
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
LineEditWithFocusSignal(QWidget *parent = nullptr) :
|
||||
QLineEdit(parent)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void focusInEvent(QFocusEvent *e) override
|
||||
{
|
||||
QLineEdit::focusInEvent(e);
|
||||
emit Focused();
|
||||
}
|
||||
|
||||
signals:
|
||||
void Focused();
|
||||
|
||||
};
|
||||
|
||||
class MarkerPropertiesDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MarkerPropertiesDialog(const std::vector<TimelineMarker*> &markers, const rational &timebase, QWidget *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
virtual void accept() override;
|
||||
|
||||
private:
|
||||
std::vector<TimelineMarker*> markers_;
|
||||
|
||||
LineEditWithFocusSignal *label_edit_;
|
||||
|
||||
ColorCodingComboBox *color_menu_;
|
||||
|
||||
RationalSlider *in_slider_;
|
||||
|
||||
RationalSlider *out_slider_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // MARKERPROPERTIESDIALOG_H
|
||||
@@ -82,6 +82,22 @@ PreferencesAppearanceTab::PreferencesAppearanceTab()
|
||||
appearance_layout->addWidget(color_group, row, 0, 1, 2);
|
||||
}
|
||||
|
||||
row++;
|
||||
{
|
||||
QGroupBox* marker_group = new QGroupBox();
|
||||
marker_group->setTitle(tr("Miscellaneous"));
|
||||
|
||||
QGridLayout* marker_layout = new QGridLayout(marker_group);
|
||||
|
||||
marker_layout->addWidget(new QLabel("Default Marker Color"), 0, 0);
|
||||
|
||||
marker_btn_ = new ColorCodingComboBox();
|
||||
marker_btn_->SetColor(Config::Current()[QStringLiteral("MarkerColor")].toInt());
|
||||
marker_layout->addWidget(marker_btn_, 0, 1);
|
||||
|
||||
appearance_layout->addWidget(marker_group, row, 0, 1, 2);
|
||||
}
|
||||
|
||||
layout->addStretch();
|
||||
}
|
||||
|
||||
@@ -99,6 +115,8 @@ void PreferencesAppearanceTab::Accept(MultiUndoCommand *command)
|
||||
for (int i=0; i<color_btns_.size(); i++) {
|
||||
Config::Current()[QStringLiteral("CatColor%1").arg(i)] = color_btns_.at(i)->GetSelectedColor();
|
||||
}
|
||||
|
||||
Config::Current()[QStringLiteral("MarkerColor")] = marker_btn_->GetSelectedColor();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ private:
|
||||
|
||||
QVector<ColorCodingComboBox*> color_btns_;
|
||||
|
||||
ColorCodingComboBox* marker_btn_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
#include "audio/audiomanager.h"
|
||||
#include "config/config.h"
|
||||
#include "dialog/export/exportaudiotab.h"
|
||||
#include "dialog/export/exportformatcombobox.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -87,12 +89,24 @@ PreferencesAudioTab::PreferencesAudioTab()
|
||||
|
||||
row++;
|
||||
|
||||
input_layout->addWidget(new QLabel(tr("Recording Mode:"), this), row, 0);
|
||||
QGroupBox *recording_group = new QGroupBox(tr("Recording"));
|
||||
input_layout->addWidget(recording_group, row, 0, 1, 2);
|
||||
|
||||
recording_combobox_ = new QComboBox();
|
||||
recording_combobox_->addItem(tr("Mono"));
|
||||
recording_combobox_->addItem(tr("Stereo"));
|
||||
input_layout->addWidget(recording_combobox_, row, 1);
|
||||
QVBoxLayout *recording_layout = new QVBoxLayout(recording_group);
|
||||
|
||||
QHBoxLayout *fmt_layout = new QHBoxLayout();
|
||||
recording_layout->addLayout(fmt_layout);
|
||||
|
||||
fmt_layout->addWidget(new QLabel(tr("Format:")));
|
||||
|
||||
ExportFormatComboBox *fmt_combo = new ExportFormatComboBox(ExportFormatComboBox::kShowAudioOnly);
|
||||
fmt_combo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
fmt_layout->addWidget(fmt_combo);
|
||||
|
||||
ExportAudioTab *audio_recording_options = new ExportAudioTab();
|
||||
recording_layout->addWidget(audio_recording_options);
|
||||
|
||||
connect(fmt_combo, &ExportFormatComboBox::FormatChanged, audio_recording_options, &ExportAudioTab::SetFormat);
|
||||
}
|
||||
|
||||
QHBoxLayout* refresh_layout = new QHBoxLayout();
|
||||
|
||||
@@ -48,8 +48,6 @@ set(OLIVE_SOURCES
|
||||
node/keyframe.h
|
||||
node/node.cpp
|
||||
node/node.h
|
||||
node/nodecopypaste.cpp
|
||||
node/nodecopypaste.h
|
||||
node/param.cpp
|
||||
node/param.h
|
||||
node/splitvalue.h
|
||||
|
||||
@@ -27,6 +27,8 @@ namespace olive {
|
||||
const QString PanNode::kSamplesInput = QStringLiteral("samples_in");
|
||||
const QString PanNode::kPanningInput = QStringLiteral("panning_in");
|
||||
|
||||
#define super Node
|
||||
|
||||
PanNode::PanNode()
|
||||
{
|
||||
AddInput(kSamplesInput, NodeValue::kSamples, InputFlags(kInputFlagNotKeyframable));
|
||||
@@ -35,6 +37,9 @@ PanNode::PanNode()
|
||||
SetInputProperty(kPanningInput, QStringLiteral("min"), -1.0);
|
||||
SetInputProperty(kPanningInput, QStringLiteral("max"), 1.0);
|
||||
SetInputProperty(kPanningInput, QStringLiteral("view"), FloatSlider::kPercentage);
|
||||
|
||||
SetFlags(kAudioEffect);
|
||||
SetEffectInput(kSamplesInput);
|
||||
}
|
||||
|
||||
Node *PanNode::copy() const
|
||||
@@ -115,6 +120,8 @@ void PanNode::ProcessSamples(const NodeValueRow &values, const SampleBufferPtr i
|
||||
|
||||
void PanNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kSamplesInput, tr("Samples"));
|
||||
SetInputName(kPanningInput, tr("Pan"));
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ namespace olive {
|
||||
const QString VolumeNode::kSamplesInput = QStringLiteral("samples_in");
|
||||
const QString VolumeNode::kVolumeInput = QStringLiteral("volume_in");
|
||||
|
||||
#define super MathNodeBase
|
||||
|
||||
VolumeNode::VolumeNode()
|
||||
{
|
||||
AddInput(kSamplesInput, NodeValue::kSamples, InputFlags(kInputFlagNotKeyframable));
|
||||
@@ -34,6 +36,9 @@ VolumeNode::VolumeNode()
|
||||
AddInput(kVolumeInput, NodeValue::kFloat, 1.0);
|
||||
SetInputProperty(kVolumeInput, QStringLiteral("min"), 0.0);
|
||||
SetInputProperty(kVolumeInput, QStringLiteral("view"), FloatSlider::kDecibel);
|
||||
|
||||
SetFlags(kAudioEffect);
|
||||
SetEffectInput(kSamplesInput);
|
||||
}
|
||||
|
||||
Node *VolumeNode::copy() const
|
||||
@@ -80,6 +85,8 @@ void VolumeNode::ProcessSamples(const NodeValueRow &values, const SampleBufferPt
|
||||
|
||||
void VolumeNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kSamplesInput, tr("Samples"));
|
||||
SetInputName(kVolumeInput, tr("Volume"));
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ namespace olive {
|
||||
#define super Node
|
||||
|
||||
const QString Block::kLengthInput = QStringLiteral("length_in");
|
||||
const QString Block::kEnabledInput = QStringLiteral("enabled_in");
|
||||
|
||||
Block::Block() :
|
||||
previous_(nullptr),
|
||||
@@ -46,8 +45,6 @@ Block::Block() :
|
||||
SetInputProperty(kLengthInput, QStringLiteral("viewlock"), true);
|
||||
IgnoreHashingFrom(kLengthInput);
|
||||
|
||||
AddInput(kEnabledInput, NodeValue::kBoolean, true, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
|
||||
|
||||
SetFlags(kDontShowInParamView);
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,6 @@ public:
|
||||
virtual void InvalidateCache(const TimeRange& range, const QString& from, int element = -1, InvalidateCacheOptions options = InvalidateCacheOptions()) override;
|
||||
|
||||
static const QString kLengthInput;
|
||||
static const QString kEnabledInput;
|
||||
|
||||
public slots:
|
||||
|
||||
|
||||
@@ -57,6 +57,8 @@ ClipBlock::ClipBlock() :
|
||||
|
||||
PrependInput(kBufferIn, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable));
|
||||
SetValueHintForInput(kBufferIn, ValueHint(NodeValue::kBuffer));
|
||||
|
||||
SetEffectInput(kBufferIn);
|
||||
}
|
||||
|
||||
Node *ClipBlock::copy() const
|
||||
@@ -206,10 +208,22 @@ void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int
|
||||
|
||||
// Find connected viewer node
|
||||
auto viewers = FindInputNodesConnectedToInput<ViewerOutput>(NodeInput(this, kBufferIn));
|
||||
if (viewers.isEmpty()) {
|
||||
connected_viewer_ = nullptr;
|
||||
} else {
|
||||
connected_viewer_ = viewers.first();
|
||||
ViewerOutput *new_connected_viewer = viewers.isEmpty() ? nullptr : viewers.first();
|
||||
|
||||
if (new_connected_viewer != connected_viewer_) {
|
||||
if (connected_viewer_) {
|
||||
disconnect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerAdded, this, &ClipBlock::PreviewChanged);
|
||||
disconnect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerRemoved, this, &ClipBlock::PreviewChanged);
|
||||
disconnect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerModified, this, &ClipBlock::PreviewChanged);
|
||||
}
|
||||
|
||||
connected_viewer_ = new_connected_viewer;
|
||||
|
||||
if (connected_viewer_) {
|
||||
connect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerAdded, this, &ClipBlock::PreviewChanged);
|
||||
connect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerRemoved, this, &ClipBlock::PreviewChanged);
|
||||
connect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerModified, this, &ClipBlock::PreviewChanged);
|
||||
}
|
||||
}
|
||||
|
||||
super::InvalidateCache(adj, from, element, options);
|
||||
|
||||
@@ -34,6 +34,8 @@ const QString ColorManager::kConfigFilenameIn = QStringLiteral("config");
|
||||
const QString ColorManager::kDefaultColorspaceIn = QStringLiteral("default_input");
|
||||
const QString ColorManager::kReferenceSpaceIn = QStringLiteral("reference_space");
|
||||
|
||||
#define super Node
|
||||
|
||||
OCIO::ConstConfigRcPtr ColorManager::default_config_ = nullptr;
|
||||
|
||||
ColorManager::ColorManager() :
|
||||
@@ -253,6 +255,8 @@ void ColorManager::GetDefaultLumaCoefs(double *rgb) const
|
||||
|
||||
void ColorManager::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kConfigFilenameIn, tr("Configuration"));
|
||||
SetInputName(kDefaultColorspaceIn, tr("Default Input"));
|
||||
SetInputName(kReferenceSpaceIn, tr("Reference Space"));
|
||||
|
||||
@@ -33,6 +33,8 @@ const QString CornerPinDistortNode::kBottomRightInput = QStringLiteral("bottom_r
|
||||
const QString CornerPinDistortNode::kBottomLeftInput = QStringLiteral("bottom_left_in");
|
||||
const QString CornerPinDistortNode::kPerspectiveInput = QStringLiteral("perspective_in");
|
||||
|
||||
#define super Node
|
||||
|
||||
CornerPinDistortNode::CornerPinDistortNode()
|
||||
{
|
||||
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
|
||||
@@ -48,10 +50,15 @@ CornerPinDistortNode::CornerPinDistortNode()
|
||||
gizmo_resize_handle_[1] = AddDraggableGizmo<PointGizmo>({NodeKeyframeTrackReference(NodeInput(this, kTopRightInput), 0), NodeKeyframeTrackReference(NodeInput(this, kTopRightInput), 1)});
|
||||
gizmo_resize_handle_[2] = AddDraggableGizmo<PointGizmo>({NodeKeyframeTrackReference(NodeInput(this, kBottomRightInput), 0), NodeKeyframeTrackReference(NodeInput(this, kBottomRightInput), 1)});
|
||||
gizmo_resize_handle_[3] = AddDraggableGizmo<PointGizmo>({NodeKeyframeTrackReference(NodeInput(this, kBottomLeftInput), 0), NodeKeyframeTrackReference(NodeInput(this, kBottomLeftInput), 1)});
|
||||
|
||||
SetFlags(kVideoEffect);
|
||||
SetEffectInput(kTextureInput);
|
||||
}
|
||||
|
||||
void CornerPinDistortNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kTextureInput, tr("Texture"));
|
||||
SetInputName(kPerspectiveInput, tr("Perspective"));
|
||||
SetInputName(kTopLeftInput, tr("Top Left"));
|
||||
|
||||
@@ -33,6 +33,8 @@ const QString CropDistortNode::kRightInput = QStringLiteral("right_in");
|
||||
const QString CropDistortNode::kBottomInput = QStringLiteral("bottom_in");
|
||||
const QString CropDistortNode::kFeatherInput = QStringLiteral("feather_in");
|
||||
|
||||
#define super Node
|
||||
|
||||
CropDistortNode::CropDistortNode()
|
||||
{
|
||||
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
|
||||
@@ -56,10 +58,15 @@ CropDistortNode::CropDistortNode()
|
||||
point_gizmo_[kGizmoScaleBottomRight] = AddDraggableGizmo<PointGizmo>({kRightInput, kBottomInput});
|
||||
point_gizmo_[kGizmoScaleCenterLeft] = AddDraggableGizmo<PointGizmo>({kLeftInput});
|
||||
point_gizmo_[kGizmoScaleCenterRight] = AddDraggableGizmo<PointGizmo>({kRightInput});
|
||||
|
||||
SetFlags(kVideoEffect);
|
||||
SetEffectInput(kTextureInput);
|
||||
}
|
||||
|
||||
void CropDistortNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kTextureInput, tr("Texture"));
|
||||
SetInputName(kLeftInput, tr("Left"));
|
||||
SetInputName(kTopInput, tr("Top"));
|
||||
|
||||
@@ -26,6 +26,8 @@ const QString FlipDistortNode::kTextureInput = QStringLiteral("tex_in");
|
||||
const QString FlipDistortNode::kHorizontalInput = QStringLiteral("horiz_in");
|
||||
const QString FlipDistortNode::kVerticalInput = QStringLiteral("vert_in");
|
||||
|
||||
#define super Node
|
||||
|
||||
FlipDistortNode::FlipDistortNode()
|
||||
{
|
||||
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
|
||||
@@ -33,6 +35,9 @@ FlipDistortNode::FlipDistortNode()
|
||||
AddInput(kHorizontalInput, NodeValue::kBoolean, false);
|
||||
|
||||
AddInput(kVerticalInput, NodeValue::kBoolean, false);
|
||||
|
||||
SetFlags(kVideoEffect);
|
||||
SetEffectInput(kTextureInput);
|
||||
}
|
||||
|
||||
Node* FlipDistortNode::copy() const
|
||||
@@ -62,6 +67,8 @@ QString FlipDistortNode::Description() const
|
||||
|
||||
void FlipDistortNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kTextureInput, tr("Input"));
|
||||
SetInputName(kHorizontalInput, tr("Horizontal"));
|
||||
SetInputName(kVerticalInput, tr("Vertical"));
|
||||
|
||||
@@ -32,7 +32,7 @@ const QString TransformDistortNode::kTextureInput = QStringLiteral("tex_in");
|
||||
const QString TransformDistortNode::kAutoscaleInput = QStringLiteral("autoscale_in");
|
||||
const QString TransformDistortNode::kInterpolationInput = QStringLiteral("interpolation_in");
|
||||
|
||||
#define super Node
|
||||
#define super MatrixGenerator
|
||||
|
||||
TransformDistortNode::TransformDistortNode()
|
||||
{
|
||||
@@ -64,11 +64,14 @@ TransformDistortNode::TransformDistortNode()
|
||||
point_gizmo_[i]->AddInput(NodeKeyframeTrackReference(NodeInput(this, kScaleInput), 1));
|
||||
point_gizmo_[i]->SetDragValueBehavior(PointGizmo::kAbsolute);
|
||||
}
|
||||
|
||||
SetFlags(kVideoEffect);
|
||||
SetEffectInput(kTextureInput);
|
||||
}
|
||||
|
||||
void TransformDistortNode::Retranslate()
|
||||
{
|
||||
MatrixGenerator::Retranslate();
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kAutoscaleInput, tr("Auto-Scale"));
|
||||
SetInputName(kTextureInput, tr("Texture"));
|
||||
@@ -150,7 +153,7 @@ void TransformDistortNode::Hash(QCryptographicHash &hash, const NodeGlobals &glo
|
||||
}
|
||||
}
|
||||
|
||||
Node::Hash(out, GetValueHintForInput(kTextureInput), hash, globals, video_params);
|
||||
super::Hash(out, GetValueHintForInput(kTextureInput), hash, globals, video_params);
|
||||
}
|
||||
|
||||
void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, double y, const rational &time)
|
||||
|
||||
@@ -24,6 +24,9 @@ OpacityEffect::OpacityEffect()
|
||||
SetInputProperty(kValueInput, QStringLiteral("view"), FloatSlider::kPercentage);
|
||||
SetInputProperty(kValueInput, QStringLiteral("min"), 0.0);
|
||||
SetInputProperty(kValueInput, QStringLiteral("max"), 1.0);
|
||||
|
||||
SetFlags(kVideoEffect);
|
||||
SetEffectInput(kTextureInput);
|
||||
}
|
||||
|
||||
void OpacityEffect::Retranslate()
|
||||
|
||||
@@ -89,7 +89,7 @@ void NodeFactory::Destroy()
|
||||
library_.clear();
|
||||
}
|
||||
|
||||
Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item, Node::CategoryID restrict_to)
|
||||
Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item, Node::CategoryID restrict_to, uint64_t restrict_flags)
|
||||
{
|
||||
Menu* menu = new Menu(parent);
|
||||
menu->setToolTipsVisible(true);
|
||||
@@ -102,6 +102,10 @@ Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item, Node::Cate
|
||||
continue;
|
||||
}
|
||||
|
||||
if (restrict_flags && !(n->GetFlags() & restrict_flags)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hidden_.contains(i)) {
|
||||
// Skip this node
|
||||
continue;
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ public:
|
||||
|
||||
static void Destroy();
|
||||
|
||||
static Menu* CreateMenu(QWidget *parent, bool create_none_item = false, Node::CategoryID restrict_to = Node::kCategoryUnknown);
|
||||
static Menu* CreateMenu(QWidget *parent, bool create_none_item = false, Node::CategoryID restrict_to = Node::kCategoryUnknown, uint64_t restrict_flags = 0);
|
||||
|
||||
static Node* CreateFromMenuAction(QAction* action);
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ const QString BlurFilterNode::kHorizInput = QStringLiteral("horiz_in");
|
||||
const QString BlurFilterNode::kVertInput = QStringLiteral("vert_in");
|
||||
const QString BlurFilterNode::kRepeatEdgePixelsInput = QStringLiteral("repeat_edge_pixels_in");
|
||||
|
||||
#define super Node
|
||||
|
||||
BlurFilterNode::BlurFilterNode()
|
||||
{
|
||||
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
|
||||
@@ -43,6 +45,9 @@ BlurFilterNode::BlurFilterNode()
|
||||
AddInput(kVertInput, NodeValue::kBoolean, true);
|
||||
|
||||
AddInput(kRepeatEdgePixelsInput, NodeValue::kBoolean, true);
|
||||
|
||||
SetFlags(kVideoEffect);
|
||||
SetEffectInput(kTextureInput);
|
||||
}
|
||||
|
||||
Node *BlurFilterNode::copy() const
|
||||
@@ -72,6 +77,8 @@ QString BlurFilterNode::Description() const
|
||||
|
||||
void BlurFilterNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kTextureInput, tr("Input"));
|
||||
SetInputName(kMethodInput, tr("Method"));
|
||||
SetComboBoxStrings(kMethodInput, { tr("Box"), tr("Gaussian") });
|
||||
|
||||
@@ -26,6 +26,8 @@ const QString MosaicFilterNode::kTextureInput = QStringLiteral("tex_in");
|
||||
const QString MosaicFilterNode::kHorizInput = QStringLiteral("horiz_in");
|
||||
const QString MosaicFilterNode::kVertInput = QStringLiteral("vert_in");
|
||||
|
||||
#define super Node
|
||||
|
||||
MosaicFilterNode::MosaicFilterNode()
|
||||
{
|
||||
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
|
||||
@@ -35,10 +37,15 @@ MosaicFilterNode::MosaicFilterNode()
|
||||
|
||||
AddInput(kVertInput, NodeValue::kFloat, 18.0);
|
||||
SetInputProperty(kVertInput, QStringLiteral("min"), 1.0);
|
||||
|
||||
SetFlags(kVideoEffect);
|
||||
SetEffectInput(kTextureInput);
|
||||
}
|
||||
|
||||
void MosaicFilterNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kTextureInput, tr("Texture"));
|
||||
SetInputName(kHorizInput, tr("Horizontal"));
|
||||
SetInputName(kVertInput, tr("Vertical"));
|
||||
|
||||
@@ -31,6 +31,8 @@ const QString StrokeFilterNode::kRadiusInput = QStringLiteral("radius_in");
|
||||
const QString StrokeFilterNode::kOpacityInput = QStringLiteral("opacity_in");
|
||||
const QString StrokeFilterNode::kInnerInput = QStringLiteral("inner_in");
|
||||
|
||||
#define super Node
|
||||
|
||||
StrokeFilterNode::StrokeFilterNode()
|
||||
{
|
||||
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
|
||||
@@ -46,6 +48,9 @@ StrokeFilterNode::StrokeFilterNode()
|
||||
SetInputProperty(kOpacityInput, QStringLiteral("max"), 1.0f);
|
||||
|
||||
AddInput(kInnerInput, NodeValue::kBoolean, false);
|
||||
|
||||
SetFlags(kVideoEffect);
|
||||
SetEffectInput(kTextureInput);
|
||||
}
|
||||
|
||||
Node *StrokeFilterNode::copy() const
|
||||
@@ -75,6 +80,8 @@ QString StrokeFilterNode::Description() const
|
||||
|
||||
void StrokeFilterNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kTextureInput, tr("Input"));
|
||||
SetInputName(kColorInput, tr("Color"));
|
||||
SetInputName(kRadiusInput, tr("Radius"));
|
||||
|
||||
@@ -33,6 +33,8 @@ const QString MatrixGenerator::kScaleInput = QStringLiteral("scale_in");
|
||||
const QString MatrixGenerator::kUniformScaleInput = QStringLiteral("uniform_scale_in");
|
||||
const QString MatrixGenerator::kAnchorInput = QStringLiteral("anchor_in");
|
||||
|
||||
#define super Node
|
||||
|
||||
MatrixGenerator::MatrixGenerator()
|
||||
{
|
||||
AddInput(kPositionInput, NodeValue::kVec2, QVector2D(0.0, 0.0));
|
||||
@@ -81,6 +83,8 @@ QString MatrixGenerator::Description() const
|
||||
|
||||
void MatrixGenerator::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kPositionInput, tr("Position"));
|
||||
SetInputName(kRotationInput, tr("Rotation"));
|
||||
SetInputName(kScaleInput, tr("Scale"));
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace olive {
|
||||
const QString NoiseGeneratorNode::kColorInput = QStringLiteral("color_in");
|
||||
const QString NoiseGeneratorNode::kStrengthInput = QStringLiteral("strength_in");
|
||||
|
||||
#define super Node
|
||||
|
||||
NoiseGeneratorNode::NoiseGeneratorNode()
|
||||
{
|
||||
AddInput(kStrengthInput, NodeValue::kFloat, 20);
|
||||
@@ -59,6 +61,8 @@ QString NoiseGeneratorNode::Description() const
|
||||
|
||||
void NoiseGeneratorNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kStrengthInput, tr("Strength"));
|
||||
SetInputName(kColorInput, tr("Color"));
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ namespace olive {
|
||||
const QString PolygonGenerator::kPointsInput = QStringLiteral("points_in");
|
||||
const QString PolygonGenerator::kColorInput = QStringLiteral("color_in");
|
||||
|
||||
#define super Node
|
||||
|
||||
PolygonGenerator::PolygonGenerator()
|
||||
{
|
||||
AddInput(kPointsInput, NodeValue::kBezier, QVector2D(0, 0), InputFlags(kInputFlagArray));
|
||||
@@ -86,6 +88,8 @@ QString PolygonGenerator::Description() const
|
||||
|
||||
void PolygonGenerator::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kPointsInput, tr("Points"));
|
||||
SetInputName(kColorInput, tr("Color"));
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ namespace olive {
|
||||
|
||||
const QString SolidGenerator::kColorInput = QStringLiteral("color_in");
|
||||
|
||||
#define super Node
|
||||
|
||||
SolidGenerator::SolidGenerator()
|
||||
{
|
||||
// Default to a color that isn't black
|
||||
@@ -59,6 +61,8 @@ QString SolidGenerator::Description() const
|
||||
|
||||
void SolidGenerator::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kColorInput, tr("Color"));
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ const QString TextGeneratorV1::kVAlignInput = QStringLiteral("valign_in");
|
||||
const QString TextGeneratorV1::kFontInput = QStringLiteral("font_in");
|
||||
const QString TextGeneratorV1::kFontSizeInput = QStringLiteral("font_size_in");
|
||||
|
||||
#define super Node
|
||||
|
||||
TextGeneratorV1::TextGeneratorV1()
|
||||
{
|
||||
AddInput(kTextInput, NodeValue::kText, tr("Sample Text"));
|
||||
@@ -75,6 +77,8 @@ QString TextGeneratorV1::Description() const
|
||||
|
||||
void TextGeneratorV1::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kTextInput, tr("Text"));
|
||||
SetInputName(kHtmlInput, tr("Enable HTML"));
|
||||
SetInputName(kFontInput, tr("Font"));
|
||||
|
||||
@@ -53,6 +53,8 @@ QString NodeGroup::Description() const
|
||||
|
||||
void NodeGroup::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
for (auto it=GetContextPositions().cbegin(); it!=GetContextPositions().cend(); it++) {
|
||||
it.key()->Retranslate();
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ ValueNode::ValueNode()
|
||||
|
||||
void ValueNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kTypeInput, QStringLiteral("Type"));
|
||||
SetInputName(kValueInput, QStringLiteral("Value"));
|
||||
|
||||
|
||||
@@ -195,4 +195,10 @@ NodeKeyframe::BezierType NodeKeyframe::get_opposing_bezier_type(NodeKeyframe::Be
|
||||
}
|
||||
}
|
||||
|
||||
bool NodeKeyframe::has_sibling_at_time(const rational &t) const
|
||||
{
|
||||
NodeKeyframe *k = parent()->GetKeyframeAtTimeOnTrack(input(), t, track(), element());
|
||||
return k && k != this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <QVariant>
|
||||
|
||||
#include "common/rational.h"
|
||||
#include "common/timerange.h"
|
||||
#include "node/param.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -86,6 +87,14 @@ public:
|
||||
const rational& time() const;
|
||||
void set_time(const rational& time);
|
||||
|
||||
/**
|
||||
* @brief Dummy function for TimeBasedViewSelectionManager compatibility
|
||||
*
|
||||
* FIXME: Once we upgrade to C++17, we won't need this because we'll be able to check types in
|
||||
* TimeBasedViewSelectionManager's template functions
|
||||
*/
|
||||
TimeRange time_range() const { return TimeRange(time_, time_); }
|
||||
|
||||
/**
|
||||
* @brief The value of this keyframe (i.e. the value to use at this keyframe's time)
|
||||
*/
|
||||
@@ -167,6 +176,8 @@ public:
|
||||
next_ = keyframe;
|
||||
}
|
||||
|
||||
bool has_sibling_at_time(const rational &t) const;
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Signal emitted when this keyframe's time is changed
|
||||
|
||||
@@ -25,7 +25,10 @@ const QString ColorDifferenceKeyNode::kShadowsInput = QStringLiteral("shadows_in
|
||||
const QString ColorDifferenceKeyNode::kHighlightsInput = QStringLiteral("highlights_in");
|
||||
const QString ColorDifferenceKeyNode::kMaskOnlyInput = QStringLiteral("mask_only_in");
|
||||
|
||||
ColorDifferenceKeyNode::ColorDifferenceKeyNode() {
|
||||
#define super Node
|
||||
|
||||
ColorDifferenceKeyNode::ColorDifferenceKeyNode()
|
||||
{
|
||||
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
|
||||
|
||||
AddInput(kGarbageMatteInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
|
||||
@@ -41,6 +44,9 @@ ColorDifferenceKeyNode::ColorDifferenceKeyNode() {
|
||||
SetInputProperty(kShadowsInput, QStringLiteral("min"), 0.0);
|
||||
|
||||
AddInput(kMaskOnlyInput, NodeValue::kBoolean, false);
|
||||
|
||||
SetFlags(kVideoEffect);
|
||||
SetEffectInput(kTextureInput);
|
||||
}
|
||||
|
||||
Node *ColorDifferenceKeyNode::copy() const
|
||||
@@ -70,6 +76,8 @@ QString ColorDifferenceKeyNode::Description() const
|
||||
|
||||
void ColorDifferenceKeyNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kTextureInput, tr("Input"));
|
||||
SetInputName(kGarbageMatteInput, tr("Garbage Matte"));
|
||||
SetInputName(kCoreMatteInput, tr("Core Matte"));
|
||||
|
||||
@@ -24,6 +24,8 @@ const QString DespillNode::kColorInput = QStringLiteral("color_in");
|
||||
const QString DespillNode::kMethodInput = QStringLiteral("method_in");
|
||||
const QString DespillNode::kPreserveLuminanceInput = QStringLiteral("preserve_luminance_input");
|
||||
|
||||
#define super Node
|
||||
|
||||
DespillNode::DespillNode()
|
||||
{
|
||||
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
|
||||
@@ -33,6 +35,9 @@ DespillNode::DespillNode()
|
||||
AddInput(kMethodInput, NodeValue::kCombo, 0);
|
||||
|
||||
AddInput(kPreserveLuminanceInput, NodeValue::kBoolean, false);
|
||||
|
||||
SetFlags(kVideoEffect);
|
||||
SetEffectInput(kTextureInput);
|
||||
}
|
||||
|
||||
Node* DespillNode::copy() const
|
||||
@@ -62,12 +67,13 @@ QString DespillNode::Description() const
|
||||
|
||||
void DespillNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kTextureInput, tr("Input"));
|
||||
|
||||
SetInputName(kColorInput, tr("Key Color"));
|
||||
SetComboBoxStrings(kColorInput, {tr("Green"), tr("Blue")});
|
||||
|
||||
|
||||
SetInputName(kMethodInput, tr("Method"));
|
||||
SetComboBoxStrings(kMethodInput, {tr("Average"), tr("Double Red Average"), tr("Double Average"), tr("Limit")});
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ const QString MathNode::kParamAIn = QStringLiteral("param_a_in");
|
||||
const QString MathNode::kParamBIn = QStringLiteral("param_b_in");
|
||||
const QString MathNode::kParamCIn = QStringLiteral("param_c_in");
|
||||
|
||||
#define super MathNodeBase
|
||||
|
||||
MathNode::MathNode()
|
||||
{
|
||||
AddInput(kMethodIn, NodeValue::kCombo, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
|
||||
@@ -67,7 +69,7 @@ QString MathNode::Description() const
|
||||
|
||||
void MathNode::Retranslate()
|
||||
{
|
||||
Node::Retranslate();
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kMethodIn, tr("Method"));
|
||||
SetInputName(kParamAIn, tr("Value"));
|
||||
|
||||
@@ -27,6 +27,8 @@ namespace olive {
|
||||
const QString MergeNode::kBaseIn = QStringLiteral("base_in");
|
||||
const QString MergeNode::kBlendIn = QStringLiteral("blend_in");
|
||||
|
||||
#define super Node
|
||||
|
||||
MergeNode::MergeNode()
|
||||
{
|
||||
AddInput(kBaseIn, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
|
||||
@@ -63,6 +65,8 @@ QString MergeNode::Description() const
|
||||
|
||||
void MergeNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kBaseIn, tr("Base"));
|
||||
|
||||
SetInputName(kBlendIn, tr("Blend"));
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace olive {
|
||||
const QString TrigonometryNode::kMethodIn = QStringLiteral("method_in");
|
||||
const QString TrigonometryNode::kXIn = QStringLiteral("x_in");
|
||||
|
||||
#define super Node
|
||||
|
||||
TrigonometryNode::TrigonometryNode()
|
||||
{
|
||||
AddInput(kMethodIn, NodeValue::kCombo, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
|
||||
@@ -59,6 +61,8 @@ QString TrigonometryNode::Description() const
|
||||
|
||||
void TrigonometryNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
QStringList strings = {tr("Sine"),
|
||||
tr("Cosine"),
|
||||
tr("Tangent"),
|
||||
|
||||
+51
-11
@@ -42,14 +42,18 @@ namespace olive {
|
||||
|
||||
#define super QObject
|
||||
|
||||
const QString Node::kEnabledInput = QStringLiteral("enabled_in");
|
||||
|
||||
Node::Node() :
|
||||
can_be_deleted_(true),
|
||||
override_color_(-1),
|
||||
folder_(nullptr),
|
||||
operation_stack_(0),
|
||||
cache_result_(false),
|
||||
flags_(kNone)
|
||||
flags_(kNone),
|
||||
effect_element_(-1)
|
||||
{
|
||||
AddInput(kEnabledInput, NodeValue::kBoolean, true);
|
||||
}
|
||||
|
||||
Node::~Node()
|
||||
@@ -78,16 +82,7 @@ NodeGraph *Node::parent() const
|
||||
|
||||
Project *Node::project() const
|
||||
{
|
||||
QObject *t = this->parent();
|
||||
|
||||
while (t) {
|
||||
if (Project *p = dynamic_cast<Project*>(t)) {
|
||||
return p;
|
||||
}
|
||||
t = t->parent();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return Project::GetProjectFromObject(this);
|
||||
}
|
||||
|
||||
QString Node::ShortName() const
|
||||
@@ -103,6 +98,7 @@ QString Node::Description() const
|
||||
|
||||
void Node::Retranslate()
|
||||
{
|
||||
SetInputName(kEnabledInput, tr("Enabled"));
|
||||
}
|
||||
|
||||
QIcon Node::icon() const
|
||||
@@ -1997,6 +1993,50 @@ void Node::SetValueAtTime(const NodeInput &input, const rational &time, const QV
|
||||
}
|
||||
}
|
||||
|
||||
void FindPathInternal(std::list<Node *> &vec, Node *to, int &path_index)
|
||||
{
|
||||
Node *from = vec.back();
|
||||
|
||||
for (auto it=from->input_connections().cbegin(); it!=from->input_connections().cend(); it++) {
|
||||
vec.push_back(it->second);
|
||||
if (it->second == to) {
|
||||
// Found a path, determine if it's the one we want
|
||||
if (path_index == 0) {
|
||||
// It is!
|
||||
break;
|
||||
} else {
|
||||
path_index--;
|
||||
}
|
||||
}
|
||||
|
||||
// Recurse to see if we can find it here
|
||||
FindPathInternal(vec, to, path_index);
|
||||
if (vec.back() == to) {
|
||||
// Found through recursion
|
||||
break;
|
||||
} else {
|
||||
// Must not be available through this path
|
||||
vec.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::list<Node *> Node::FindPath(Node *from, Node *to, int path_index)
|
||||
{
|
||||
std::list<Node *> v;
|
||||
|
||||
v.push_back(from);
|
||||
|
||||
FindPathInternal(v, to, path_index);
|
||||
|
||||
if (v.size() == 1) {
|
||||
// Failed to find path, return empty list
|
||||
v.pop_back();
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
Project *Node::ArrayInsertCommand::GetRelevantProject() const
|
||||
{
|
||||
return node_->project();
|
||||
|
||||
+21
-1
@@ -95,7 +95,9 @@ public:
|
||||
|
||||
enum Flag {
|
||||
kNone = 0,
|
||||
kDontShowInParamView = 0x1
|
||||
kDontShowInParamView = 0x1,
|
||||
kVideoEffect = 0x2,
|
||||
kAudioEffect = 0x4
|
||||
};
|
||||
|
||||
Node();
|
||||
@@ -539,6 +541,11 @@ public:
|
||||
|
||||
int InputArraySize(const QString& id) const;
|
||||
|
||||
NodeInput GetEffectInput()
|
||||
{
|
||||
return effect_input_.isEmpty() ? NodeInput() : NodeInput(this, effect_input_, effect_element_);
|
||||
}
|
||||
|
||||
class ValueHint {
|
||||
public:
|
||||
explicit ValueHint(const QVector<NodeValue::Type> &types = QVector<NodeValue::Type>(), int index = -1, const QString &tag = QString()) :
|
||||
@@ -949,6 +956,10 @@ public:
|
||||
|
||||
static void SetValueAtTime(const NodeInput &input, const rational &time, const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key);
|
||||
|
||||
static std::list<Node*> FindPath(Node *from, Node *to, int path_index = 0);
|
||||
|
||||
static const QString kEnabledInput;
|
||||
|
||||
protected:
|
||||
virtual void Hash(QCryptographicHash& hash, const NodeGlobals &globals, const VideoParams& video_params) const;
|
||||
|
||||
@@ -1027,6 +1038,12 @@ protected:
|
||||
|
||||
virtual void childEvent(QChildEvent *event) override;
|
||||
|
||||
void SetEffectInput(const QString &input, int element = -1)
|
||||
{
|
||||
effect_input_ = input;
|
||||
effect_element_ = element;
|
||||
}
|
||||
|
||||
void SetToolTip(const QString& s)
|
||||
{
|
||||
tooltip_ = s;
|
||||
@@ -1342,6 +1359,9 @@ private:
|
||||
|
||||
QVector<NodeGizmo*> gizmos_;
|
||||
|
||||
QString effect_input_;
|
||||
int effect_element_;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time
|
||||
|
||||
@@ -1,91 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "nodecopypaste.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "core.h"
|
||||
#include "node/factory.h"
|
||||
#include "widget/nodeview/nodeviewundo.h"
|
||||
#include "window/mainwindow/mainwindow.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
void NodeCopyPasteService::CopyNodesToClipboard(QVector<Node *> nodes, void *userdata)
|
||||
{
|
||||
QString copy_str;
|
||||
|
||||
QXmlStreamWriter writer(©_str);
|
||||
|
||||
// For any groups, add children
|
||||
for (int i=0; i<nodes.size(); i++) {
|
||||
// If this is a group, add the child nodes too
|
||||
if (NodeGroup *g = dynamic_cast<NodeGroup*>(nodes.at(i))) {
|
||||
for (auto it=g->GetContextPositions().cbegin(); it!=g->GetContextPositions().cend(); it++) {
|
||||
if (!nodes.contains(it.key())) {
|
||||
nodes.append(it.key());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProjectSerializer::SaveData data(nodes.first()->project(), QString(), nodes);
|
||||
|
||||
CopyNodesToClipboardCallback(nodes, &data, userdata);
|
||||
|
||||
ProjectSerializer::Save(&writer, data);
|
||||
|
||||
Core::CopyStringToClipboard(copy_str);
|
||||
}
|
||||
|
||||
void NodeCopyPasteService::PasteNodesFromClipboard(void *userdata)
|
||||
{
|
||||
QString clipboard = Core::PasteStringFromClipboard();
|
||||
if (clipboard.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QXmlStreamReader reader(clipboard);
|
||||
|
||||
Project temp;
|
||||
ProjectSerializer::Result res = ProjectSerializer::Load(&temp, &reader);
|
||||
|
||||
if (res.code() != ProjectSerializer::kSuccess) {
|
||||
return;
|
||||
}
|
||||
|
||||
QVector<Node*> pasted_nodes;
|
||||
foreach (Node *n, temp.nodes()) {
|
||||
if (!temp.default_nodes().contains(n)) {
|
||||
// Move nodes out of Project
|
||||
n->setParent(nullptr);
|
||||
pasted_nodes.append(n);
|
||||
}
|
||||
}
|
||||
|
||||
if (pasted_nodes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PasteNodesToClipboardCallback(pasted_nodes, res.GetLoadData(), userdata);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,52 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef NODECOPYPASTEWIDGET_H
|
||||
#define NODECOPYPASTEWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QUndoCommand>
|
||||
|
||||
#include "node/node.h"
|
||||
#include "node/project/project.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "node/project/serializer/serializer.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class NodeCopyPasteService
|
||||
{
|
||||
public:
|
||||
NodeCopyPasteService() = default;
|
||||
|
||||
protected:
|
||||
void CopyNodesToClipboard(QVector<Node *> nodes, void* userdata = nullptr);
|
||||
|
||||
void PasteNodesFromClipboard(void* userdata = nullptr);
|
||||
|
||||
virtual void CopyNodesToClipboardCallback(const QVector<Node*> &nodes, ProjectSerializer::SaveData *data, void *userdata){}
|
||||
|
||||
virtual void PasteNodesToClipboardCallback(const QVector<Node*> &nodes, const ProjectSerializer::LoadData &load_data, void *userdata){}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // NODECOPYPASTEWIDGET_H
|
||||
@@ -277,7 +277,7 @@ void Track::InputValueChangedEvent(const QString &input, int element)
|
||||
|
||||
void Track::Retranslate()
|
||||
{
|
||||
Node::Retranslate();
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kBlockInput, tr("Blocks"));
|
||||
SetInputName(kMutedInput, tr("Muted"));
|
||||
|
||||
@@ -68,6 +68,8 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream
|
||||
}
|
||||
|
||||
SetFlags(kDontShowInParamView);
|
||||
|
||||
timeline_points_ = new TimelinePoints(this);
|
||||
}
|
||||
|
||||
Node *ViewerOutput::copy() const
|
||||
|
||||
@@ -137,7 +137,7 @@ public:
|
||||
|
||||
TimelinePoints* GetTimelinePoints()
|
||||
{
|
||||
return &timeline_points_;
|
||||
return timeline_points_;
|
||||
}
|
||||
|
||||
QVector<Track::Reference> GetEnabledStreamsAsReferences() const;
|
||||
@@ -252,7 +252,7 @@ private:
|
||||
|
||||
AudioParams cached_audio_params_;
|
||||
|
||||
TimelinePoints timeline_points_;
|
||||
TimelinePoints *timeline_points_;
|
||||
|
||||
bool video_cache_enabled_;
|
||||
bool audio_cache_enabled_;
|
||||
|
||||
@@ -169,6 +169,20 @@ void Project::RegenerateUuid()
|
||||
uuid_ = QUuid::createUuid();
|
||||
}
|
||||
|
||||
Project *Project::GetProjectFromObject(const QObject *o)
|
||||
{
|
||||
QObject *t = o->parent();
|
||||
|
||||
while (t) {
|
||||
if (Project *p = dynamic_cast<Project*>(t)) {
|
||||
return p;
|
||||
}
|
||||
t = t->parent();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Project::ColorManagerValueChanged(const NodeInput &input, const TimeRange &range)
|
||||
{
|
||||
Q_UNUSED(input)
|
||||
|
||||
@@ -109,6 +109,14 @@ public:
|
||||
saved_url_ = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find project parent from object
|
||||
*
|
||||
* If an object is expected to be a child of a project, this function will traverse its parent
|
||||
* tree until it finds it.
|
||||
*/
|
||||
static Project *GetProjectFromObject(const QObject *o);
|
||||
|
||||
signals:
|
||||
void NameChanged();
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace olive {
|
||||
const QString ProjectSettingsNode::kCacheSetting = QStringLiteral("cache_setting");
|
||||
const QString ProjectSettingsNode::kCachePath = QStringLiteral("cache_path");
|
||||
|
||||
#define super Node
|
||||
|
||||
ProjectSettingsNode::ProjectSettingsNode()
|
||||
{
|
||||
AddInput(kCacheSetting, NodeValue::kCombo, 0, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
|
||||
@@ -36,6 +38,8 @@ ProjectSettingsNode::ProjectSettingsNode()
|
||||
|
||||
void ProjectSettingsNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kCacheSetting, tr("Disk Cache Location"));
|
||||
SetInputName(kCachePath, tr("Disk Cache Path"));
|
||||
SetComboBoxStrings(kCacheSetting, {tr("Use Default Location"), tr("Store Alongside Project"), tr("Use Custom Location")});
|
||||
|
||||
@@ -405,7 +405,7 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action
|
||||
}
|
||||
|
||||
// Trigger an import
|
||||
Core::instance()->ImportFiles(urls, this, static_cast<Folder*>(drop_item));
|
||||
Core::instance()->ImportFiles(urls, static_cast<Folder*>(drop_item));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -55,14 +55,14 @@ void ProjectSerializer::Destroy()
|
||||
instances_.clear();
|
||||
}
|
||||
|
||||
ProjectSerializer::Result ProjectSerializer::Load(Project *project, const QString &filename)
|
||||
ProjectSerializer::Result ProjectSerializer::Load(Project *project, const QString &filename, const QString &type)
|
||||
{
|
||||
QFile project_file(filename);
|
||||
|
||||
if (project_file.open(QFile::ReadOnly | QFile::Text)) {
|
||||
QXmlStreamReader reader(&project_file);
|
||||
|
||||
Result inner_result = Load(project, &reader);
|
||||
Result inner_result = Load(project, &reader, type);
|
||||
|
||||
project_file.close();
|
||||
|
||||
@@ -82,25 +82,162 @@ ProjectSerializer::Result ProjectSerializer::Load(Project *project, const QStrin
|
||||
}
|
||||
}
|
||||
|
||||
ProjectSerializer::Result ProjectSerializer::Load(Project *project, QXmlStreamReader *reader)
|
||||
ProjectSerializer::Result ProjectSerializer::Load(Project *project, QXmlStreamReader *reader, const QString &type)
|
||||
{
|
||||
// Determine project version
|
||||
uint version = 0;
|
||||
Result res = kUnknownVersion;
|
||||
|
||||
while (!version && XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("olive") || reader->name() == QStringLiteral("project")) {
|
||||
while(!version && XMLReadNextStartElement(reader)) {
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("olive")
|
||||
|| reader->name() == QStringLiteral("project")) { // 0.1 projects only
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("version")) {
|
||||
version = reader->readElementText().toUInt();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("url")) {
|
||||
project->SetSavedURL(reader->readElementText());
|
||||
|
||||
// HACK for 0.1 projects
|
||||
if (version == 190219) {
|
||||
res = LoadWithSerializerVersion(version, project, reader);
|
||||
}
|
||||
} else if (reader->name() == type) {
|
||||
// Found our data
|
||||
res = LoadWithSerializerVersion(version, project, reader);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ProjectSerializer::Result ProjectSerializer::Paste(const QString &type)
|
||||
{
|
||||
QString clipboard = Core::PasteStringFromClipboard();
|
||||
if (clipboard.isEmpty()) {
|
||||
return kNoData;
|
||||
}
|
||||
|
||||
QXmlStreamReader reader(clipboard);
|
||||
|
||||
Project temp;
|
||||
ProjectSerializer::Result res = ProjectSerializer::Load(&temp, &reader, type);
|
||||
|
||||
if (res.code() != ProjectSerializer::kSuccess) {
|
||||
return res;
|
||||
}
|
||||
|
||||
QVector<Node*> pasted_nodes;
|
||||
foreach (Node *n, temp.nodes()) {
|
||||
if (!temp.default_nodes().contains(n)) {
|
||||
// Move nodes out of Project
|
||||
n->setParent(nullptr);
|
||||
pasted_nodes.append(n);
|
||||
}
|
||||
}
|
||||
|
||||
res.SetLoadedNodes(pasted_nodes);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ProjectSerializer::Result ProjectSerializer::Save(const SaveData &data, const QString &type)
|
||||
{
|
||||
QString temp_save = FileFunctions::GetSafeTemporaryFilename(data.GetFilename());
|
||||
|
||||
QFile project_file(temp_save);
|
||||
|
||||
if (project_file.open(QFile::WriteOnly | QFile::Text)) {
|
||||
QXmlStreamWriter writer(&project_file);
|
||||
|
||||
Result inner_result = Save(&writer, data, type);
|
||||
|
||||
project_file.close();
|
||||
|
||||
if (inner_result != kSuccess) {
|
||||
return inner_result;
|
||||
}
|
||||
|
||||
// Save was successful, we can now rewrite the original file
|
||||
if (FileFunctions::RenameFileAllowOverwrite(temp_save, data.GetFilename())) {
|
||||
return kSuccess;
|
||||
} else {
|
||||
Result r(kOverwriteError);
|
||||
r.SetDetails(temp_save);
|
||||
return r;
|
||||
}
|
||||
} else {
|
||||
Result r(kFileError);
|
||||
r.SetDetails(temp_save);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
ProjectSerializer::Result ProjectSerializer::Save(QXmlStreamWriter *writer, const SaveData &data, const QString &type)
|
||||
{
|
||||
writer->setAutoFormatting(true);
|
||||
|
||||
writer->writeStartDocument();
|
||||
|
||||
writer->writeStartElement("olive");
|
||||
|
||||
// By default, save as last serializer which, assuming the instances are ordered correctly,
|
||||
// will be the newest file format. But we may allow saving as older versions later on.
|
||||
ProjectSerializer *serializer = instances_.last();
|
||||
|
||||
// Version is stored in YYMMDD from whenever the project format was last changed
|
||||
// Allows easy integer math for checking project versions.
|
||||
writer->writeTextElement(QStringLiteral("version"), QString::number(serializer->Version()));
|
||||
|
||||
if (!data.GetFilename().isEmpty()) {
|
||||
writer->writeTextElement("url", data.GetFilename());
|
||||
}
|
||||
|
||||
writer->writeStartElement(type);
|
||||
|
||||
serializer->Save(writer, data, nullptr);
|
||||
|
||||
writer->writeEndElement(); // [type]
|
||||
|
||||
writer->writeEndElement(); // olive
|
||||
|
||||
writer->writeEndDocument();
|
||||
|
||||
if (writer->hasError()) {
|
||||
return kXmlError;
|
||||
}
|
||||
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
ProjectSerializer::Result ProjectSerializer::Copy(const SaveData &data, const QString &type)
|
||||
{
|
||||
QString copy_str;
|
||||
QXmlStreamWriter writer(©_str);
|
||||
|
||||
ProjectSerializer::Result res = ProjectSerializer::Save(&writer, data, type);
|
||||
|
||||
if (res == kSuccess) {
|
||||
Core::CopyStringToClipboard(copy_str);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool ProjectSerializer::IsCancelled() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ProjectSerializer::Result ProjectSerializer::LoadWithSerializerVersion(uint version, Project *project, QXmlStreamReader *reader)
|
||||
{
|
||||
// Failed to find version in file
|
||||
if (version == 0) {
|
||||
return kUnknownVersion;
|
||||
@@ -135,78 +272,21 @@ ProjectSerializer::Result ProjectSerializer::Load(Project *project, QXmlStreamRe
|
||||
}
|
||||
}
|
||||
|
||||
ProjectSerializer::Result ProjectSerializer::Save(const SaveData &data)
|
||||
void ProjectSerializer::SaveData::SetOnlySerializeNodesAndResolveGroups(QVector<Node *> nodes)
|
||||
{
|
||||
QString temp_save = FileFunctions::GetSafeTemporaryFilename(data.GetFilename());
|
||||
|
||||
QFile project_file(temp_save);
|
||||
|
||||
if (project_file.open(QFile::WriteOnly | QFile::Text)) {
|
||||
QXmlStreamWriter writer(&project_file);
|
||||
|
||||
Result inner_result = Save(&writer, data);
|
||||
|
||||
project_file.close();
|
||||
|
||||
if (inner_result != kSuccess) {
|
||||
return inner_result;
|
||||
// For any groups, add children
|
||||
for (int i=0; i<nodes.size(); i++) {
|
||||
// If this is a group, add the child nodes too
|
||||
if (NodeGroup *g = dynamic_cast<NodeGroup*>(nodes.at(i))) {
|
||||
for (auto it=g->GetContextPositions().cbegin(); it!=g->GetContextPositions().cend(); it++) {
|
||||
if (!nodes.contains(it.key())) {
|
||||
nodes.append(it.key());
|
||||
}
|
||||
|
||||
// Save was successful, we can now rewrite the original file
|
||||
if (FileFunctions::RenameFileAllowOverwrite(temp_save, data.GetFilename())) {
|
||||
return kSuccess;
|
||||
} else {
|
||||
Result r(kOverwriteError);
|
||||
r.SetDetails(temp_save);
|
||||
return r;
|
||||
}
|
||||
} else {
|
||||
Result r(kFileError);
|
||||
r.SetDetails(temp_save);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
ProjectSerializer::Result ProjectSerializer::Save(QXmlStreamWriter *writer, const SaveData &data)
|
||||
{
|
||||
writer->setAutoFormatting(true);
|
||||
|
||||
writer->writeStartDocument();
|
||||
|
||||
writer->writeStartElement("olive");
|
||||
|
||||
// By default, save as last serializer which, assuming the instances are ordered correctly,
|
||||
// will be the newest file format. But we may allow saving as older versions later on.
|
||||
ProjectSerializer *serializer = instances_.last();
|
||||
|
||||
// Version is stored in YYMMDD from whenever the project format was last changed
|
||||
// Allows easy integer math for checking project versions.
|
||||
writer->writeTextElement(QStringLiteral("version"), QString::number(serializer->Version()));
|
||||
|
||||
if (!data.GetFilename().isEmpty()) {
|
||||
writer->writeTextElement("url", data.GetFilename());
|
||||
}
|
||||
|
||||
writer->writeStartElement(QStringLiteral("project"));
|
||||
|
||||
serializer->Save(writer, data, nullptr);
|
||||
|
||||
writer->writeEndElement(); // project
|
||||
|
||||
writer->writeEndElement(); // olive
|
||||
|
||||
writer->writeEndDocument();
|
||||
|
||||
if (writer->hasError()) {
|
||||
return kXmlError;
|
||||
}
|
||||
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
bool ProjectSerializer::IsCancelled() const
|
||||
{
|
||||
return false;
|
||||
SetOnlySerializeNodes(nodes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -50,7 +50,8 @@ public:
|
||||
kUnknownVersion,
|
||||
kFileError,
|
||||
kXmlError,
|
||||
kOverwriteError
|
||||
kOverwriteError,
|
||||
kNoData
|
||||
};
|
||||
|
||||
using SerializedProperties = QHash<Node*, QMap<QString, QString> >;
|
||||
@@ -62,6 +63,8 @@ public:
|
||||
|
||||
SerializedProperties properties;
|
||||
|
||||
std::vector<TimelineMarker*> markers;
|
||||
|
||||
};
|
||||
|
||||
class Result
|
||||
@@ -84,6 +87,10 @@ public:
|
||||
|
||||
void SetLoadData(const LoadData &p) { load_data_ = p; }
|
||||
|
||||
const QVector<Node*> &GetLoadedNodes() const { return loaded_nodes_; }
|
||||
|
||||
void SetLoadedNodes(const QVector<Node*> &n) { loaded_nodes_ = n; }
|
||||
|
||||
private:
|
||||
ResultCode code_;
|
||||
|
||||
@@ -91,17 +98,17 @@ public:
|
||||
|
||||
LoadData load_data_;
|
||||
|
||||
QVector<Node*> loaded_nodes_;
|
||||
|
||||
};
|
||||
|
||||
class SaveData
|
||||
{
|
||||
public:
|
||||
SaveData(Project *project, const QString &filename, const QVector<Node*> &only = QVector<Node*>(), const SerializedProperties &p = SerializedProperties())
|
||||
SaveData(Project *project, const QString &filename = QString())
|
||||
{
|
||||
project_ = project;
|
||||
filename_ = filename;
|
||||
only_serialize_nodes_ = only;
|
||||
properties_ = p;
|
||||
}
|
||||
|
||||
Project *GetProject() const
|
||||
@@ -115,11 +122,13 @@ public:
|
||||
}
|
||||
|
||||
const QVector<Node*> &GetOnlySerializeNodes() const { return only_serialize_nodes_; }
|
||||
|
||||
void SetOnlySerializeNodes(const QVector<Node*> &only) { only_serialize_nodes_ = only; }
|
||||
void SetOnlySerializeNodesAndResolveGroups(QVector<Node*> only);
|
||||
|
||||
const std::vector<TimelineMarker*> &GetOnlySerializeMarkers() const { return only_serialize_markers_; }
|
||||
void SetOnlySerializeMarkers(const std::vector<TimelineMarker*> &only) { only_serialize_markers_ = only; }
|
||||
|
||||
const SerializedProperties &GetProperties() const { return properties_; }
|
||||
|
||||
void SetProperties(const SerializedProperties &p) { properties_ = p; }
|
||||
|
||||
private:
|
||||
@@ -131,17 +140,21 @@ public:
|
||||
|
||||
SerializedProperties properties_;
|
||||
|
||||
std::vector<TimelineMarker*> only_serialize_markers_;
|
||||
|
||||
};
|
||||
|
||||
static void Initialize();
|
||||
|
||||
static void Destroy();
|
||||
|
||||
static Result Load(Project *project, const QString &filename);
|
||||
static Result Load(Project *project, QXmlStreamReader *read_device);
|
||||
static Result Load(Project *project, const QString &filename, const QString &type);
|
||||
static Result Load(Project *project, QXmlStreamReader *read_device, const QString &type);
|
||||
static Result Paste(const QString &type);
|
||||
|
||||
static Result Save(const SaveData &data);
|
||||
static Result Save(QXmlStreamWriter *write_device, const SaveData &data);
|
||||
static Result Save(const SaveData &data, const QString &type);
|
||||
static Result Save(QXmlStreamWriter *write_device, const SaveData &data, const QString &type);
|
||||
static Result Copy(const SaveData &data, const QString &type);
|
||||
|
||||
protected:
|
||||
virtual LoadData Load(Project *project, QXmlStreamReader *reader, void *reserved) const = 0;
|
||||
@@ -153,6 +166,8 @@ protected:
|
||||
bool IsCancelled() const;
|
||||
|
||||
private:
|
||||
static Result LoadWithSerializerVersion(uint version, Project *project, QXmlStreamReader *reader);
|
||||
|
||||
static QVector<ProjectSerializer*> instances_;
|
||||
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "serializer210528.h"
|
||||
|
||||
#include "config/config.h"
|
||||
#include "node/factory.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -28,10 +29,6 @@ ProjectSerializer210528::LoadData ProjectSerializer210528::Load(Project *project
|
||||
{
|
||||
XMLNodeData xml_node_data;
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("url")) {
|
||||
project->SetSavedURL(reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("project")) {
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("layout")) {
|
||||
|
||||
@@ -154,10 +151,6 @@ ProjectSerializer210528::LoadData ProjectSerializer210528::Load(Project *project
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
// Make connections
|
||||
PostConnect(xml_node_data);
|
||||
@@ -612,7 +605,7 @@ void ProjectSerializer210528::LoadMarkerList(QXmlStreamReader *reader, TimelineM
|
||||
}
|
||||
}
|
||||
|
||||
markers->AddMarker(TimeRange(in, out), name);
|
||||
new TimelineMarker(Config::Current()[QStringLiteral("MarkerColor")].toInt(), TimeRange(in, out), name, markers);
|
||||
}
|
||||
|
||||
reader->skipCurrentElement();
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "serializer210907.h"
|
||||
|
||||
#include "config/config.h"
|
||||
#include "node/factory.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -28,10 +29,6 @@ ProjectSerializer210907::LoadData ProjectSerializer210907::Load(Project *project
|
||||
{
|
||||
XMLNodeData xml_node_data;
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("url")) {
|
||||
project->SetSavedURL(reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("project")) {
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("layout")) {
|
||||
|
||||
@@ -154,10 +151,6 @@ ProjectSerializer210907::LoadData ProjectSerializer210907::Load(Project *project
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
// Make connections
|
||||
PostConnect(xml_node_data);
|
||||
@@ -604,7 +597,7 @@ void ProjectSerializer210907::LoadMarkerList(QXmlStreamReader *reader, TimelineM
|
||||
}
|
||||
}
|
||||
|
||||
markers->AddMarker(TimeRange(in, out), name);
|
||||
new TimelineMarker(Config::Current()[QStringLiteral("MarkerColor")].toInt(), TimeRange(in, out), name, markers);
|
||||
}
|
||||
|
||||
reader->skipCurrentElement();
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "serializer211228.h"
|
||||
|
||||
#include "config/config.h"
|
||||
#include "node/factory.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -30,10 +31,6 @@ ProjectSerializer211228::LoadData ProjectSerializer211228::Load(Project *project
|
||||
QMap<quintptr, QMap<quintptr, Node::Position> > positions;
|
||||
XMLNodeData xml_node_data;
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("url")) {
|
||||
project->SetSavedURL(reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("project")) {
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("layout")) {
|
||||
|
||||
@@ -180,10 +177,6 @@ ProjectSerializer211228::LoadData ProjectSerializer211228::Load(Project *project
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve positions
|
||||
for (auto it=positions.cbegin(); it!=positions.cend(); it++) {
|
||||
@@ -654,7 +647,7 @@ void ProjectSerializer211228::LoadMarkerList(QXmlStreamReader *reader, TimelineM
|
||||
}
|
||||
}
|
||||
|
||||
markers->AddMarker(TimeRange(in, out), name);
|
||||
new TimelineMarker(Config::Current()[QStringLiteral("MarkerColor")].toInt(), TimeRange(in, out), name, markers);
|
||||
}
|
||||
|
||||
reader->skipCurrentElement();
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "serializer220403.h"
|
||||
|
||||
#include "config/config.h"
|
||||
#include "node/factory.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -30,10 +31,8 @@ ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project
|
||||
QMap<quintptr, QMap<quintptr, Node::Position> > positions;
|
||||
XMLNodeData xml_node_data;
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("url")) {
|
||||
project->SetSavedURL(reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("project")) {
|
||||
LoadData load_data;
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("layout")) {
|
||||
|
||||
@@ -100,6 +99,18 @@ ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project
|
||||
}
|
||||
}
|
||||
|
||||
} else if (reader->name() == QStringLiteral("markers")) {
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("marker")) {
|
||||
TimelineMarker *marker = new TimelineMarker();
|
||||
LoadMarker(reader, marker);
|
||||
load_data.markers.push_back(marker);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
} else if (reader->name() == QStringLiteral("positions")) {
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
@@ -180,10 +191,6 @@ ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve positions
|
||||
for (auto it=positions.cbegin(); it!=positions.cend(); it++) {
|
||||
@@ -201,8 +208,6 @@ ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project
|
||||
// Make connections
|
||||
PostConnect(xml_node_data);
|
||||
|
||||
LoadData load_data;
|
||||
|
||||
// Resolve serialized properties (if any)
|
||||
for (auto it=properties.cbegin(); it!=properties.cend(); it++) {
|
||||
Node *node = xml_node_data.node_ptrs.value(it.key());
|
||||
@@ -218,6 +223,24 @@ void ProjectSerializer220403::Save(QXmlStreamWriter *writer, const SaveData &dat
|
||||
{
|
||||
Project *project = data.GetProject();
|
||||
|
||||
if (!data.GetOnlySerializeMarkers().empty()) {
|
||||
|
||||
writer->writeStartElement(QStringLiteral("markers"));
|
||||
|
||||
for (auto it=data.GetOnlySerializeMarkers().cbegin(); it!=data.GetOnlySerializeMarkers().cend(); it++) {
|
||||
TimelineMarker *marker = *it;
|
||||
|
||||
writer->writeStartElement(QStringLiteral("marker"));
|
||||
|
||||
SaveMarker(writer, marker);
|
||||
|
||||
writer->writeEndElement(); // marker
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // markers
|
||||
|
||||
} else {
|
||||
|
||||
writer->writeTextElement(QStringLiteral("uuid"), data.GetProject()->GetUuid().toString());
|
||||
|
||||
writer->writeStartElement(QStringLiteral("nodes"));
|
||||
@@ -286,6 +309,8 @@ void ProjectSerializer220403::Save(QXmlStreamWriter *writer, const SaveData &dat
|
||||
|
||||
// Save main window project layout
|
||||
project->GetLayoutInfo().toXml(writer);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer220403::LoadNode(Node *node, XMLNodeData &xml_node_data, QXmlStreamReader *reader) const
|
||||
@@ -961,6 +986,36 @@ void ProjectSerializer220403::SaveTimelinePoints(QXmlStreamWriter *writer, Timel
|
||||
writer->writeEndElement(); // markers
|
||||
}
|
||||
|
||||
void ProjectSerializer220403::LoadMarker(QXmlStreamReader *reader, TimelineMarker *marker) const
|
||||
{
|
||||
rational in, out;
|
||||
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (attr.name() == QStringLiteral("name")) {
|
||||
marker->set_name(attr.value().toString());
|
||||
} else if (attr.name() == QStringLiteral("in")) {
|
||||
in = rational::fromString(attr.value().toString());
|
||||
} else if (attr.name() == QStringLiteral("out")) {
|
||||
out = rational::fromString(attr.value().toString());
|
||||
} else if (attr.name() == QStringLiteral("color")) {
|
||||
marker->set_color(attr.value().toInt());
|
||||
}
|
||||
}
|
||||
|
||||
marker->set_time(TimeRange(in, out));
|
||||
|
||||
// This element has no inner text, so just skip it
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
|
||||
void ProjectSerializer220403::SaveMarker(QXmlStreamWriter *writer, TimelineMarker *marker) const
|
||||
{
|
||||
writer->writeAttribute(QStringLiteral("name"), marker->name());
|
||||
writer->writeAttribute(QStringLiteral("in"), marker->time_range().in().toString());
|
||||
writer->writeAttribute(QStringLiteral("out"), marker->time_range().out().toString());
|
||||
writer->writeAttribute(QStringLiteral("color"), QString::number(marker->color()));
|
||||
}
|
||||
|
||||
void ProjectSerializer220403::LoadWorkArea(QXmlStreamReader *reader, TimelineWorkArea *workarea) const
|
||||
{
|
||||
rational range_in = workarea->in();
|
||||
@@ -996,35 +1051,22 @@ void ProjectSerializer220403::LoadMarkerList(QXmlStreamReader *reader, TimelineM
|
||||
{
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("marker")) {
|
||||
QString name;
|
||||
rational in, out;
|
||||
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (attr.name() == QStringLiteral("name")) {
|
||||
name = attr.value().toString();
|
||||
} else if (attr.name() == QStringLiteral("in")) {
|
||||
in = rational::fromString(attr.value().toString());
|
||||
} else if (attr.name() == QStringLiteral("out")) {
|
||||
out = rational::fromString(attr.value().toString());
|
||||
}
|
||||
}
|
||||
|
||||
markers->AddMarker(TimeRange(in, out), name);
|
||||
}
|
||||
|
||||
TimelineMarker *marker = new TimelineMarker(markers);
|
||||
LoadMarker(reader, marker);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectSerializer220403::SaveMarkerList(QXmlStreamWriter *writer, TimelineMarkerList *markers) const
|
||||
{
|
||||
foreach (TimelineMarker* marker, markers->list()) {
|
||||
for (auto it=markers->cbegin(); it!=markers->cend(); it++) {
|
||||
TimelineMarker* marker = *it;
|
||||
|
||||
writer->writeStartElement(QStringLiteral("marker"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("name"), marker->name());
|
||||
|
||||
writer->writeAttribute(QStringLiteral("in"), marker->time().in().toString());
|
||||
writer->writeAttribute(QStringLiteral("out"), marker->time().out().toString());
|
||||
SaveMarker(writer, marker);
|
||||
|
||||
writer->writeEndElement(); // marker
|
||||
}
|
||||
|
||||
@@ -100,6 +100,10 @@ private:
|
||||
|
||||
void SaveTimelinePoints(QXmlStreamWriter *writer, TimelinePoints *points) const;
|
||||
|
||||
void LoadMarker(QXmlStreamReader *reader, TimelineMarker *marker) const;
|
||||
|
||||
void SaveMarker(QXmlStreamWriter *writer, TimelineMarker *marker) const;
|
||||
|
||||
void LoadWorkArea(QXmlStreamReader *reader, TimelineWorkArea *workarea) const;
|
||||
|
||||
void SaveWorkArea(QXmlStreamWriter *writer, TimelineWorkArea *workarea) const;
|
||||
|
||||
@@ -41,6 +41,8 @@ TimeOffsetNode::TimeOffsetNode()
|
||||
|
||||
void TimeOffsetNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kTimeInput, QStringLiteral("Time"));
|
||||
SetInputName(kInputInput, QStringLiteral("Input"));
|
||||
}
|
||||
|
||||
@@ -87,6 +87,8 @@ TimeRange TimeRemapNode::OutputTimeAdjustment(const QString &input, int element,
|
||||
|
||||
void TimeRemapNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kTimeInput, QStringLiteral("Time"));
|
||||
SetInputName(kInputInput, QStringLiteral("Input"));
|
||||
}
|
||||
|
||||
+14
-1
@@ -246,8 +246,18 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const Node::ValueHint
|
||||
|
||||
// Generate row for node
|
||||
NodeValueDatabase database = GenerateDatabase(n, range);
|
||||
NodeValueRow row = GenerateRow(&database, n, range);
|
||||
|
||||
// Check for bypass
|
||||
bool is_enabled;
|
||||
if (!database[Node::kEnabledInput].Has(NodeValue::kBoolean)) {
|
||||
// Fallback if we couldn't find a bool value
|
||||
is_enabled = true;
|
||||
} else {
|
||||
is_enabled = database[Node::kEnabledInput].Get(NodeValue::kBoolean).toBool();
|
||||
}
|
||||
|
||||
if (is_enabled) {
|
||||
NodeValueRow row = GenerateRow(&database, n, range);
|
||||
//qDebug() << "FIXME: Implement pre-process of row";
|
||||
|
||||
// Generate output table
|
||||
@@ -260,6 +270,9 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const Node::ValueHint
|
||||
PostProcessTable(n, hint, range, table);
|
||||
|
||||
return table;
|
||||
} else {
|
||||
return database.Merge();
|
||||
}
|
||||
}
|
||||
|
||||
NodeValueTable NodeTraverser::GenerateBlockTable(const Track *track, const TimeRange &range)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
***/
|
||||
|
||||
#include "sequenceviewer.h"
|
||||
#include "panel/timeline/timeline.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -29,6 +30,12 @@ SequenceViewerPanel::SequenceViewerPanel(QWidget *parent) :
|
||||
Retranslate();
|
||||
}
|
||||
|
||||
void SequenceViewerPanel::StartCapture(const TimeRange &time, const Track::Reference &track)
|
||||
{
|
||||
TimelinePanel *tp = static_cast<TimelinePanel *>(sender());
|
||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->StartCapture(tp->timeline_widget(), time, track);
|
||||
}
|
||||
|
||||
void SequenceViewerPanel::Retranslate()
|
||||
{
|
||||
ViewerPanel::Retranslate();
|
||||
|
||||
@@ -31,6 +31,9 @@ class SequenceViewerPanel : public ViewerPanel
|
||||
public:
|
||||
SequenceViewerPanel(QWidget* parent);
|
||||
|
||||
public slots:
|
||||
void StartCapture(const TimeRange &time, const Track::Reference &track);
|
||||
|
||||
protected:
|
||||
virtual void Retranslate() override;
|
||||
|
||||
|
||||
@@ -211,4 +211,9 @@ void TimeBasedPanel::GoToOut()
|
||||
GetTimeBasedWidget()->GoToOut();
|
||||
}
|
||||
|
||||
void TimeBasedPanel::DeleteSelected()
|
||||
{
|
||||
GetTimeBasedWidget()->DeleteSelected();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -98,6 +98,8 @@ public:
|
||||
|
||||
virtual void GoToOut() override;
|
||||
|
||||
virtual void DeleteSelected() override;
|
||||
|
||||
public slots:
|
||||
void SetTimebase(const rational& timebase);
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ TimelinePanel::TimelinePanel(QWidget *parent) :
|
||||
Retranslate();
|
||||
|
||||
connect(tw, &TimelineWidget::BlockSelectionChanged, this, &TimelinePanel::BlockSelectionChanged);
|
||||
connect(tw, &TimelineWidget::RequestCaptureStart, this, &TimelinePanel::RequestCaptureStart );
|
||||
}
|
||||
|
||||
void TimelinePanel::SplitAtPlayhead()
|
||||
|
||||
@@ -112,6 +112,8 @@ protected:
|
||||
signals:
|
||||
void BlockSelectionChanged(const QVector<Block*>& selected_blocks);
|
||||
|
||||
void RequestCaptureStart(const TimeRange &time, const Track::Reference &track);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "openglrenderer.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QOpenGLExtraFunctions>
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
|
||||
#include "videoparams.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/avutil.h>
|
||||
}
|
||||
|
||||
#include <QtMath>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
@@ -30,9 +30,8 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
ProjectImportTask::ProjectImportTask(ProjectViewModel *model, Folder *folder, const QStringList &filenames) :
|
||||
ProjectImportTask::ProjectImportTask(Folder *folder, const QStringList &filenames) :
|
||||
command_(nullptr),
|
||||
model_(model),
|
||||
folder_(folder)
|
||||
{
|
||||
foreach (const QString& f, filenames) {
|
||||
@@ -117,6 +116,9 @@ void ProjectImportTask::Import(Folder *folder, QFileInfoList import, int &counte
|
||||
|
||||
// Create undoable command that adds the items to the model
|
||||
AddItemToFolder(folder, footage, parent_command);
|
||||
|
||||
// Add to vector
|
||||
imported_footage_.push_back(footage);
|
||||
} else {
|
||||
// Add to list so we can tell the user about it later
|
||||
invalid_files_.append(file_info.absoluteFilePath());
|
||||
@@ -220,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 = model_->project();
|
||||
Project* project = folder->project();
|
||||
|
||||
NodeAddCommand* nac = new NodeAddCommand(project, item);
|
||||
nac->PushToThread(project->thread());
|
||||
|
||||
@@ -34,7 +34,7 @@ class ProjectImportTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ProjectImportTask(ProjectViewModel* model, Folder* folder, const QStringList& filenames);
|
||||
ProjectImportTask(Folder* folder, const QStringList& filenames);
|
||||
|
||||
const int& GetFileCount() const;
|
||||
|
||||
@@ -53,6 +53,8 @@ public:
|
||||
return !invalid_files_.isEmpty();
|
||||
}
|
||||
|
||||
const QVector<Footage*> &GetImportedFootage() const { return imported_footage_; }
|
||||
|
||||
protected:
|
||||
virtual bool Run() override;
|
||||
|
||||
@@ -71,8 +73,6 @@ private:
|
||||
|
||||
MultiUndoCommand* command_;
|
||||
|
||||
ProjectViewModel* model_;
|
||||
|
||||
Folder* folder_;
|
||||
|
||||
QFileInfoList filenames_;
|
||||
@@ -83,6 +83,8 @@ private:
|
||||
|
||||
QList<QString> image_sequence_ignore_files_;
|
||||
|
||||
QVector<Footage*> imported_footage_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ bool ProjectLoadTask::Run()
|
||||
|
||||
project_->set_filename(GetFilename());
|
||||
|
||||
ProjectSerializer::Result result = ProjectSerializer::Load(project_, GetFilename());
|
||||
ProjectSerializer::Result result = ProjectSerializer::Load(project_, GetFilename(), QStringLiteral("project"));
|
||||
|
||||
switch (result.code()) {
|
||||
case ProjectSerializer::kSuccess:
|
||||
@@ -57,6 +57,9 @@ bool ProjectLoadTask::Run()
|
||||
case ProjectSerializer::kXmlError:
|
||||
SetError(tr("Failed to read XML document. File may be corrupt. Error was: %1").arg(result.GetDetails()));
|
||||
break;
|
||||
case ProjectSerializer::kNoData:
|
||||
SetError(tr("Failed to find any data to parse."));
|
||||
break;
|
||||
|
||||
// Errors that should never be thrown by a load
|
||||
case ProjectSerializer::kOverwriteError:
|
||||
|
||||
@@ -42,7 +42,7 @@ bool ProjectSaveTask::Run()
|
||||
|
||||
ProjectSerializer::SaveData data(project_, using_filename);
|
||||
|
||||
ProjectSerializer::Result result = ProjectSerializer::Save(data);
|
||||
ProjectSerializer::Result result = ProjectSerializer::Save(data, QStringLiteral("project"));
|
||||
|
||||
bool success = false;
|
||||
|
||||
@@ -66,6 +66,7 @@ bool ProjectSaveTask::Run()
|
||||
case ProjectSerializer::kProjectTooNew:
|
||||
case ProjectSerializer::kProjectTooOld:
|
||||
case ProjectSerializer::kUnknownVersion:
|
||||
case ProjectSerializer::kNoData:
|
||||
SetError(tr("Unknown error."));
|
||||
break;
|
||||
}
|
||||
|
||||
+265
-24
@@ -20,20 +20,26 @@
|
||||
|
||||
#include "timelinemarker.h"
|
||||
|
||||
#include "common/qtutils.h"
|
||||
#include "common/xmlutils.h"
|
||||
#include "config/config.h"
|
||||
#include "core.h"
|
||||
#include "ui/colorcoding.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
TimelineMarker::TimelineMarker(const TimeRange &time, const QString &name, QObject *parent) :
|
||||
QObject(parent),
|
||||
time_(time),
|
||||
name_(name)
|
||||
TimelineMarker::TimelineMarker(QObject *parent) :
|
||||
color_(Config::Current()[QStringLiteral("MarkerColor")].toInt())
|
||||
{
|
||||
setParent(parent);
|
||||
}
|
||||
|
||||
const TimeRange &TimelineMarker::time() const
|
||||
TimelineMarker::TimelineMarker(int color, const TimeRange &time, const QString &name, QObject *parent) :
|
||||
time_(time),
|
||||
name_(name),
|
||||
color_(color)
|
||||
{
|
||||
return time_;
|
||||
setParent(parent);
|
||||
}
|
||||
|
||||
void TimelineMarker::set_time(const TimeRange &time)
|
||||
@@ -42,9 +48,15 @@ void TimelineMarker::set_time(const TimeRange &time)
|
||||
emit TimeChanged(time_);
|
||||
}
|
||||
|
||||
const QString &TimelineMarker::name() const
|
||||
void TimelineMarker::set_time(const rational &time)
|
||||
{
|
||||
return name_;
|
||||
set_time(TimeRange(time, time + time_.length()));
|
||||
}
|
||||
|
||||
bool TimelineMarker::has_sibling_at_time(const rational &t) const
|
||||
{
|
||||
TimelineMarker *m = static_cast<TimelineMarkerList*>(parent())->GetMarkerAtTime(t);
|
||||
return m && m != this;
|
||||
}
|
||||
|
||||
void TimelineMarker::set_name(const QString &name)
|
||||
@@ -53,36 +65,265 @@ void TimelineMarker::set_name(const QString &name)
|
||||
emit NameChanged(name_);
|
||||
}
|
||||
|
||||
TimelineMarkerList::~TimelineMarkerList()
|
||||
void TimelineMarker::set_color(int c)
|
||||
{
|
||||
qDeleteAll(markers_);
|
||||
color_ = c;
|
||||
emit ColorChanged(color_);
|
||||
}
|
||||
|
||||
TimelineMarker* TimelineMarkerList::AddMarker(const TimeRange &time, const QString &name)
|
||||
int TimelineMarker::GetMarkerHeight(const QFontMetrics &fm)
|
||||
{
|
||||
TimelineMarker* m = new TimelineMarker(time, name);
|
||||
markers_.append(m);
|
||||
emit MarkerAdded(m);
|
||||
return m;
|
||||
return fm.height();
|
||||
}
|
||||
|
||||
void TimelineMarkerList::RemoveMarker(TimelineMarker *marker)
|
||||
QRect TimelineMarker::Draw(QPainter *p, const QPoint &pt, double scale, bool selected)
|
||||
{
|
||||
for (int i=0;i<markers_.size();i++) {
|
||||
TimelineMarker* m = markers_.at(i);
|
||||
QFontMetrics fm = p->fontMetrics();
|
||||
|
||||
if (m == marker) {
|
||||
markers_.removeAt(i);
|
||||
emit MarkerRemoved(m);
|
||||
delete m;
|
||||
int marker_height = GetMarkerHeight(fm);
|
||||
int marker_width = QtUtils::QFontMetricsWidth(fm, QStringLiteral("H"));
|
||||
|
||||
int half_width = marker_width / 2;
|
||||
|
||||
QColor c = ColorCoding::GetColor(color()).toQColor();
|
||||
if (selected) {
|
||||
p->setPen(Qt::white);
|
||||
p->setBrush(c.lighter());
|
||||
} else {
|
||||
p->setPen(Qt::black);
|
||||
p->setBrush(c);
|
||||
}
|
||||
|
||||
int top = pt.y() - marker_height;
|
||||
|
||||
if (time_.out() != time_.in()) {
|
||||
QRect marker_rect(pt.x(), top, time_.length().toDouble() * scale, marker_height);
|
||||
|
||||
p->drawRect(marker_rect);
|
||||
|
||||
if (!name_.isEmpty()) {
|
||||
p->setPen(ColorCoding::GetUISelectorColor(ColorCoding::GetColor(color_)));
|
||||
p->drawText(marker_rect.adjusted(marker_width/4, 0, 0, 0), name_, Qt::AlignLeft | Qt::AlignVCenter);
|
||||
}
|
||||
|
||||
return marker_rect;
|
||||
} else {
|
||||
int half_marker_height = marker_height / 3;
|
||||
int left = pt.x() - half_width;
|
||||
int right = pt.x() + half_width;
|
||||
int center_y = pt.y() - half_marker_height;
|
||||
|
||||
QPoint points[] = {
|
||||
pt,
|
||||
QPoint(left, center_y),
|
||||
QPoint(left, top),
|
||||
QPoint(right, top),
|
||||
QPoint(right, center_y),
|
||||
pt,
|
||||
};
|
||||
|
||||
p->setRenderHint(QPainter::Antialiasing);
|
||||
p->drawPolygon(points, 6);
|
||||
|
||||
return QRect(left, top, marker_width, marker_height);
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineMarkerList::childEvent(QChildEvent *e)
|
||||
{
|
||||
QObject::childEvent(e);
|
||||
|
||||
if (TimelineMarker *marker = dynamic_cast<TimelineMarker *>(e->child())) {
|
||||
if (e->type() == QChildEvent::ChildAdded) {
|
||||
|
||||
connect(marker, &TimelineMarker::TimeChanged, this, &TimelineMarkerList::HandleMarkerTimeChange);
|
||||
connect(marker, &TimelineMarker::TimeChanged, this, &TimelineMarkerList::HandleMarkerModification);
|
||||
connect(marker, &TimelineMarker::NameChanged, this, &TimelineMarkerList::HandleMarkerModification);
|
||||
connect(marker, &TimelineMarker::ColorChanged, this, &TimelineMarkerList::HandleMarkerModification);
|
||||
|
||||
InsertIntoList(marker);
|
||||
|
||||
emit MarkerAdded(marker);
|
||||
|
||||
} else if (e->type() == QChildEvent::ChildRemoved) {
|
||||
|
||||
RemoveFromList(marker);
|
||||
|
||||
disconnect(marker, &TimelineMarker::TimeChanged, this, &TimelineMarkerList::HandleMarkerTimeChange);
|
||||
disconnect(marker, &TimelineMarker::TimeChanged, this, &TimelineMarkerList::HandleMarkerModification);
|
||||
disconnect(marker, &TimelineMarker::NameChanged, this, &TimelineMarkerList::HandleMarkerModification);
|
||||
disconnect(marker, &TimelineMarker::ColorChanged, this, &TimelineMarkerList::HandleMarkerModification);
|
||||
|
||||
emit MarkerRemoved(marker);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineMarkerList::InsertIntoList(TimelineMarker *marker)
|
||||
{
|
||||
// Insertion sort by time to allow some loop optimizations
|
||||
bool found = false;
|
||||
for (auto it=markers_.begin(); it!=markers_.end(); it++) {
|
||||
TimelineMarker *m = *it;
|
||||
|
||||
Q_ASSERT(m->time() != marker->time());
|
||||
|
||||
if (m->time() > marker->time()) {
|
||||
markers_.insert(it, marker);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
markers_.push_back(marker);
|
||||
}
|
||||
}
|
||||
|
||||
const QList<TimelineMarker*> &TimelineMarkerList::list() const
|
||||
bool TimelineMarkerList::RemoveFromList(TimelineMarker *marker)
|
||||
{
|
||||
return markers_;
|
||||
auto it = std::find(markers_.begin(), markers_.end(), marker);
|
||||
|
||||
if (it != markers_.end()) {
|
||||
markers_.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void TimelineMarkerList::HandleMarkerModification()
|
||||
{
|
||||
emit MarkerModified(static_cast<TimelineMarker*>(sender()));
|
||||
}
|
||||
|
||||
void TimelineMarkerList::HandleMarkerTimeChange()
|
||||
{
|
||||
TimelineMarker *m = static_cast<TimelineMarker*>(sender());
|
||||
|
||||
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
|
||||
markers_.erase(it);
|
||||
InsertIntoList(m);
|
||||
}
|
||||
}
|
||||
|
||||
MarkerAddCommand::MarkerAddCommand(TimelineMarkerList *marker_list, const TimeRange &range, const QString &name, int color) :
|
||||
MarkerAddCommand(marker_list, new TimelineMarker(color, range, name, &memory_manager_))
|
||||
{
|
||||
}
|
||||
|
||||
MarkerAddCommand::MarkerAddCommand(TimelineMarkerList *marker_list, TimelineMarker *marker) :
|
||||
marker_list_(marker_list),
|
||||
added_marker_(marker)
|
||||
{
|
||||
added_marker_->setParent(&memory_manager_);
|
||||
}
|
||||
|
||||
Project* MarkerAddCommand::GetRelevantProject() const
|
||||
{
|
||||
return Project::GetProjectFromObject(marker_list_);
|
||||
}
|
||||
|
||||
void MarkerAddCommand::redo()
|
||||
{
|
||||
added_marker_->setParent(marker_list_);
|
||||
}
|
||||
|
||||
void MarkerAddCommand::undo()
|
||||
{
|
||||
added_marker_->setParent(&memory_manager_);
|
||||
}
|
||||
|
||||
MarkerRemoveCommand::MarkerRemoveCommand(TimelineMarker *marker) :
|
||||
marker_(marker)
|
||||
{
|
||||
}
|
||||
|
||||
Project* MarkerRemoveCommand::GetRelevantProject() const
|
||||
{
|
||||
return Project::GetProjectFromObject(marker_);
|
||||
}
|
||||
|
||||
void MarkerRemoveCommand::redo()
|
||||
{
|
||||
marker_list_ = marker_->parent();
|
||||
marker_->setParent(&memory_manager_);
|
||||
}
|
||||
|
||||
void MarkerRemoveCommand::undo()
|
||||
{
|
||||
marker_->setParent(marker_list_);
|
||||
}
|
||||
|
||||
MarkerChangeColorCommand::MarkerChangeColorCommand(TimelineMarker *marker, int new_color) :
|
||||
marker_(marker),
|
||||
new_color_(new_color)
|
||||
{
|
||||
}
|
||||
|
||||
Project* MarkerChangeColorCommand::GetRelevantProject() const
|
||||
{
|
||||
return Project::GetProjectFromObject(marker_);
|
||||
}
|
||||
|
||||
void MarkerChangeColorCommand::redo()
|
||||
{
|
||||
old_color_ = marker_->color();
|
||||
marker_->set_color(new_color_);
|
||||
}
|
||||
|
||||
void MarkerChangeColorCommand::undo()
|
||||
{
|
||||
marker_->set_color(old_color_);
|
||||
}
|
||||
|
||||
MarkerChangeNameCommand::MarkerChangeNameCommand(TimelineMarker *marker, QString new_name) :
|
||||
marker_(marker),
|
||||
new_name_(new_name)
|
||||
{
|
||||
}
|
||||
|
||||
Project* MarkerChangeNameCommand::GetRelevantProject() const
|
||||
{
|
||||
return Project::GetProjectFromObject(marker_);
|
||||
}
|
||||
|
||||
void MarkerChangeNameCommand::redo()
|
||||
{
|
||||
old_name_ = marker_->name();
|
||||
marker_->set_name(new_name_);
|
||||
}
|
||||
|
||||
void MarkerChangeNameCommand::undo()
|
||||
{
|
||||
marker_->set_name(old_name_);
|
||||
}
|
||||
|
||||
MarkerChangeTimeCommand::MarkerChangeTimeCommand(TimelineMarker* marker, TimeRange time) :
|
||||
marker_(marker),
|
||||
new_time_(time)
|
||||
{
|
||||
}
|
||||
|
||||
Project* MarkerChangeTimeCommand::GetRelevantProject() const
|
||||
{
|
||||
return Project::GetProjectFromObject(marker_);
|
||||
}
|
||||
|
||||
void MarkerChangeTimeCommand::redo()
|
||||
{
|
||||
old_time_ = marker_->time_range();
|
||||
marker_->set_time(new_time_);
|
||||
}
|
||||
|
||||
void MarkerChangeTimeCommand::undo()
|
||||
{
|
||||
marker_->set_time(old_time_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,11 +21,13 @@
|
||||
#ifndef TIMELINEMARKER_H
|
||||
#define TIMELINEMARKER_H
|
||||
|
||||
#include <QPainter>
|
||||
#include <QString>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
#include "common/timerange.h"
|
||||
#include "undo/undocommand.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -33,47 +35,210 @@ class TimelineMarker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TimelineMarker(const TimeRange& time = TimeRange(), const QString& name = QString(), QObject* parent = nullptr);
|
||||
TimelineMarker(QObject* parent = nullptr);
|
||||
TimelineMarker(int color, const TimeRange& time, const QString& name = QString(), QObject* parent = nullptr);
|
||||
|
||||
const TimeRange &time() const;
|
||||
/**
|
||||
* @brief Dummy function for TimeBasedViewSelectionManager compatibility
|
||||
*
|
||||
* FIXME: Once we upgrade to C++17, we won't need this because we'll be able to check types in
|
||||
* TimeBasedViewSelectionManager's template functions
|
||||
*/
|
||||
const rational &time() const { return time_.in(); }
|
||||
void set_time(const rational& time);
|
||||
|
||||
const TimeRange &time_range() const { return time_; }
|
||||
void set_time(const TimeRange& time);
|
||||
|
||||
const QString& name() const;
|
||||
bool has_sibling_at_time(const rational &t) const;
|
||||
|
||||
const QString& name() const { return name_; }
|
||||
void set_name(const QString& name);
|
||||
|
||||
int color() const { return color_; }
|
||||
void set_color(int c);
|
||||
|
||||
static int GetMarkerHeight(const QFontMetrics &fm);
|
||||
QRect Draw(QPainter *p, const QPoint &pt, double scale, bool selected);
|
||||
|
||||
signals:
|
||||
void TimeChanged(const TimeRange& time);
|
||||
|
||||
void NameChanged(const QString& name);
|
||||
|
||||
void ColorChanged(int c);
|
||||
|
||||
private:
|
||||
TimeRange time_;
|
||||
|
||||
QString name_;
|
||||
|
||||
int color_;
|
||||
|
||||
};
|
||||
|
||||
class TimelineMarkerList : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TimelineMarkerList() = default;
|
||||
TimelineMarkerList(QObject *parent = nullptr) :
|
||||
QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~TimelineMarkerList() override;
|
||||
inline bool empty() const { return markers_.empty(); }
|
||||
inline std::vector<TimelineMarker*>::iterator begin() { return markers_.begin(); }
|
||||
inline std::vector<TimelineMarker*>::iterator end() { return markers_.end(); }
|
||||
inline std::vector<TimelineMarker*>::const_iterator cbegin() const { return markers_.cbegin(); }
|
||||
inline std::vector<TimelineMarker*>::const_iterator cend() const { return markers_.cend(); }
|
||||
inline TimelineMarker *back() const { return markers_.back(); }
|
||||
inline TimelineMarker *front() const { return markers_.front(); }
|
||||
inline size_t size() const { return markers_.size(); }
|
||||
|
||||
TimelineMarker *AddMarker(const TimeRange& time = TimeRange(), const QString& name = QString());
|
||||
TimelineMarker *GetMarkerAtTime(const rational &t) const
|
||||
{
|
||||
for (auto it=markers_.cbegin(); it!=markers_.cend(); it++) {
|
||||
TimelineMarker *m = *it;
|
||||
if (m->time() == t) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveMarker(TimelineMarker* marker);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const QList<TimelineMarker *> &list() const;
|
||||
TimelineMarker *GetClosestMarkerToTime(const rational &t) const
|
||||
{
|
||||
TimelineMarker *closest = nullptr;
|
||||
|
||||
for (auto it=markers_.cbegin(); it!=markers_.cend(); it++) {
|
||||
TimelineMarker *m = *it;
|
||||
|
||||
rational this_diff = qAbs(m->time() - t);
|
||||
|
||||
if (closest) {
|
||||
rational stored_diff = qAbs(closest->time() - t);
|
||||
|
||||
if (this_diff > stored_diff) {
|
||||
// Since the list is organized by time, if the diff increases, assume we are only going
|
||||
// to move further away from here and there's no need to check
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
closest = m;
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
signals:
|
||||
void MarkerAdded(TimelineMarker* marker);
|
||||
|
||||
void MarkerRemoved(TimelineMarker* marker);
|
||||
|
||||
void MarkerModified(TimelineMarker* marker);
|
||||
|
||||
protected:
|
||||
virtual void childEvent(QChildEvent *e) override;
|
||||
|
||||
private:
|
||||
QList<TimelineMarker*> markers_;
|
||||
void InsertIntoList(TimelineMarker *m);
|
||||
bool RemoveFromList(TimelineMarker *m);
|
||||
|
||||
std::vector<TimelineMarker*> markers_;
|
||||
|
||||
private slots:
|
||||
void HandleMarkerModification();
|
||||
|
||||
void HandleMarkerTimeChange();
|
||||
|
||||
};
|
||||
|
||||
class MarkerAddCommand : public UndoCommand {
|
||||
public:
|
||||
MarkerAddCommand(TimelineMarkerList* marker_list, const TimeRange& range, const QString& name, int color);
|
||||
MarkerAddCommand(TimelineMarkerList* marker_list, TimelineMarker *marker);
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
TimelineMarkerList* marker_list_;
|
||||
|
||||
TimelineMarker* added_marker_;
|
||||
QObject memory_manager_;
|
||||
|
||||
};
|
||||
|
||||
class MarkerRemoveCommand : public UndoCommand {
|
||||
public:
|
||||
MarkerRemoveCommand(TimelineMarker* marker);
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
TimelineMarker* marker_;
|
||||
QObject* marker_list_;
|
||||
|
||||
QObject memory_manager_;
|
||||
|
||||
};
|
||||
|
||||
class MarkerChangeColorCommand : public UndoCommand {
|
||||
public:
|
||||
MarkerChangeColorCommand(TimelineMarker* marker, int new_color);
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
TimelineMarker* marker_;
|
||||
int old_color_;
|
||||
int new_color_;
|
||||
|
||||
};
|
||||
|
||||
class MarkerChangeNameCommand : public UndoCommand {
|
||||
public:
|
||||
MarkerChangeNameCommand(TimelineMarker* marker, QString name);
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
TimelineMarker* marker_;
|
||||
QString old_name_;
|
||||
QString new_name_;
|
||||
};
|
||||
|
||||
class MarkerChangeTimeCommand : public UndoCommand {
|
||||
public:
|
||||
MarkerChangeTimeCommand(TimelineMarker* marker, TimeRange time);
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
TimelineMarker* marker_;
|
||||
TimeRange old_time_;
|
||||
TimeRange new_time_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -24,24 +24,31 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
TimelinePoints::TimelinePoints(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
markers_ = new TimelineMarkerList(this);
|
||||
workarea_ = new TimelineWorkArea(this);
|
||||
}
|
||||
|
||||
TimelineMarkerList *TimelinePoints::markers()
|
||||
{
|
||||
return &markers_;
|
||||
return markers_;
|
||||
}
|
||||
|
||||
const TimelineMarkerList *TimelinePoints::markers() const
|
||||
{
|
||||
return &markers_;
|
||||
return markers_;
|
||||
}
|
||||
|
||||
const TimelineWorkArea *TimelinePoints::workarea() const
|
||||
{
|
||||
return &workarea_;
|
||||
return workarea_;
|
||||
}
|
||||
|
||||
TimelineWorkArea *TimelinePoints::workarea()
|
||||
{
|
||||
return &workarea_;
|
||||
return workarea_;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,10 +29,11 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
class TimelinePoints
|
||||
class TimelinePoints : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TimelinePoints() = default;
|
||||
TimelinePoints(QObject *parent = nullptr);
|
||||
|
||||
TimelineMarkerList* markers();
|
||||
const TimelineMarkerList* markers() const;
|
||||
@@ -41,9 +42,9 @@ public:
|
||||
const TimelineWorkArea* workarea() const;
|
||||
|
||||
private:
|
||||
TimelineMarkerList markers_;
|
||||
TimelineMarkerList *markers_;
|
||||
|
||||
TimelineWorkArea workarea_;
|
||||
TimelineWorkArea *workarea_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ QIcon icon::ToolSlip;
|
||||
QIcon icon::ToolSlide;
|
||||
QIcon icon::ToolHand;
|
||||
QIcon icon::ToolTransition;
|
||||
QIcon icon::ToolTrackSelect;
|
||||
QIcon icon::Folder;
|
||||
QIcon icon::Sequence;
|
||||
QIcon icon::Video;
|
||||
@@ -68,6 +69,8 @@ QIcon icon::TriRight;
|
||||
QIcon icon::TextBold;
|
||||
QIcon icon::TextItalic;
|
||||
QIcon icon::TextUnderline;
|
||||
QIcon icon::TextStrikethrough;
|
||||
QIcon icon::TextSmallCaps;
|
||||
QIcon icon::TextAlignLeft;
|
||||
QIcon icon::TextAlignRight;
|
||||
QIcon icon::TextAlignCenter;
|
||||
@@ -116,6 +119,7 @@ void icon::LoadAll(const QString& theme)
|
||||
ToolSlide = Create(theme, "slide");
|
||||
ToolHand = Create(theme, "hand");
|
||||
ToolTransition = Create(theme, "transition-tool");
|
||||
ToolTrackSelect = Create(theme, "track-tool");
|
||||
|
||||
Folder = Create(theme, "folder");
|
||||
Sequence = Create(theme, "sequence");
|
||||
@@ -133,6 +137,8 @@ void icon::LoadAll(const QString& theme)
|
||||
TextBold = Create(theme, "text-bold");
|
||||
TextItalic = Create(theme, "text-italic");
|
||||
TextUnderline = Create(theme, "text-underline");
|
||||
TextStrikethrough = Create(theme, "text-strikethrough");
|
||||
TextSmallCaps = Create(theme, "text-small-caps");
|
||||
TextAlignLeft = Create(theme, "align-left");
|
||||
TextAlignRight = Create(theme, "align-right");
|
||||
TextAlignCenter = Create(theme, "align-center");
|
||||
|
||||
@@ -57,6 +57,7 @@ extern QIcon ToolSlip;
|
||||
extern QIcon ToolSlide;
|
||||
extern QIcon ToolHand;
|
||||
extern QIcon ToolTransition;
|
||||
extern QIcon ToolTrackSelect;
|
||||
|
||||
// Project Icons
|
||||
extern QIcon Folder;
|
||||
@@ -78,6 +79,8 @@ extern QIcon TriRight;
|
||||
extern QIcon TextBold;
|
||||
extern QIcon TextItalic;
|
||||
extern QIcon TextUnderline;
|
||||
extern QIcon TextStrikethrough;
|
||||
extern QIcon TextSmallCaps;
|
||||
extern QIcon TextAlignLeft;
|
||||
extern QIcon TextAlignRight;
|
||||
extern QIcon TextAlignCenter;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 662 B |
Binary file not shown.
|
After Width: | Height: | Size: 517 B |
Binary file not shown.
|
After Width: | Height: | Size: 511 B |
Binary file not shown.
|
After Width: | Height: | Size: 355 B |
Binary file not shown.
|
After Width: | Height: | Size: 526 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user