Merge branch 'olive-editor:master' into av1
This commit is contained in:
@@ -90,6 +90,15 @@ void AudioVisualWaveform::OverwriteSamplesFromMipmap(const AudioVisualWaveform::
|
|||||||
input_length = samples_length;
|
input_length = samples_length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioVisualWaveform::ValidateVirtualStart(const rational &new_start)
|
||||||
|
{
|
||||||
|
if (length_ == 0) {
|
||||||
|
virtual_start_ = new_start;
|
||||||
|
} else if (virtual_start_ > new_start) {
|
||||||
|
TrimIn(new_start - virtual_start_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AudioVisualWaveform::OverwriteSamples(const SampleBuffer &samples, int sample_rate, const rational &start)
|
void AudioVisualWaveform::OverwriteSamples(const SampleBuffer &samples, int sample_rate, const rational &start)
|
||||||
{
|
{
|
||||||
if (!channels_) {
|
if (!channels_) {
|
||||||
@@ -97,18 +106,12 @@ void AudioVisualWaveform::OverwriteSamples(const SampleBuffer &samples, int samp
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Old less optimized code. Keeping this around as a reference, but the below code is at least
|
ValidateVirtualStart(start);
|
||||||
// 10x faster so this shouldn't be used in production.
|
|
||||||
//
|
|
||||||
// size_t input_start, input_length;
|
|
||||||
// for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
|
||||||
// OverwriteSamplesFromBuffer(samples, sample_rate, start, it->first.toDouble(), it->second, input_start, input_length);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Process the largest mipmap directly for the samples
|
// Process the largest mipmap directly for the samples
|
||||||
auto current_mipmap = mipmapped_data_.rbegin();
|
auto current_mipmap = mipmapped_data_.rbegin();
|
||||||
size_t input_start, input_length;
|
size_t input_start, input_length;
|
||||||
OverwriteSamplesFromBuffer(samples, sample_rate, start, current_mipmap->first.toDouble(), current_mipmap->second, input_start, input_length);
|
OverwriteSamplesFromBuffer(samples, sample_rate, start - virtual_start_, current_mipmap->first.toDouble(), current_mipmap->second, input_start, input_length);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
// For each smaller mipmap, we just process from the mipmap before it, making each one
|
// For each smaller mipmap, we just process from the mipmap before it, making each one
|
||||||
@@ -120,7 +123,7 @@ void AudioVisualWaveform::OverwriteSamples(const SampleBuffer &samples, int samp
|
|||||||
}
|
}
|
||||||
|
|
||||||
OverwriteSamplesFromMipmap(previous_mipmap->second, previous_mipmap->first.toDouble(),
|
OverwriteSamplesFromMipmap(previous_mipmap->second, previous_mipmap->first.toDouble(),
|
||||||
input_start, input_length, start, current_mipmap->first.toDouble(),
|
input_start, input_length, start - virtual_start_, current_mipmap->first.toDouble(),
|
||||||
current_mipmap->second);
|
current_mipmap->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +133,8 @@ void AudioVisualWaveform::OverwriteSamples(const SampleBuffer &samples, int samp
|
|||||||
|
|
||||||
void AudioVisualWaveform::OverwriteSums(const AudioVisualWaveform &sums, const rational &dest, const rational& offset, const rational& length)
|
void AudioVisualWaveform::OverwriteSums(const AudioVisualWaveform &sums, const rational &dest, const rational& offset, const rational& length)
|
||||||
{
|
{
|
||||||
|
ValidateVirtualStart(dest);
|
||||||
|
|
||||||
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
||||||
rational rate = it->first;
|
rational rate = it->first;
|
||||||
|
|
||||||
@@ -139,7 +144,7 @@ void AudioVisualWaveform::OverwriteSums(const AudioVisualWaveform &sums, const r
|
|||||||
double rate_dbl = rate.toDouble();
|
double rate_dbl = rate.toDouble();
|
||||||
|
|
||||||
// Get our destination sample
|
// Get our destination sample
|
||||||
size_t our_start_index = time_to_samples(dest, rate_dbl);
|
size_t our_start_index = time_to_samples(dest - virtual_start_, rate_dbl);
|
||||||
|
|
||||||
// Get our source sample
|
// Get our source sample
|
||||||
size_t their_start_index = time_to_samples(offset, rate_dbl);
|
size_t their_start_index = time_to_samples(offset, rate_dbl);
|
||||||
@@ -172,6 +177,8 @@ void AudioVisualWaveform::OverwriteSums(const AudioVisualWaveform &sums, const r
|
|||||||
|
|
||||||
void AudioVisualWaveform::OverwriteSilence(const rational &start, const rational &length)
|
void AudioVisualWaveform::OverwriteSilence(const rational &start, const rational &length)
|
||||||
{
|
{
|
||||||
|
ValidateVirtualStart(start);
|
||||||
|
|
||||||
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
||||||
rational rate = it->first;
|
rational rate = it->first;
|
||||||
|
|
||||||
@@ -180,7 +187,7 @@ void AudioVisualWaveform::OverwriteSilence(const rational &start, const rational
|
|||||||
double rate_dbl = rate.toDouble();
|
double rate_dbl = rate.toDouble();
|
||||||
|
|
||||||
// Get our destination sample
|
// Get our destination sample
|
||||||
size_t our_start_index = time_to_samples(start, rate_dbl);
|
size_t our_start_index = time_to_samples(start - virtual_start_, rate_dbl);
|
||||||
size_t our_length_index = time_to_samples(length, rate_dbl);
|
size_t our_length_index = time_to_samples(length, rate_dbl);
|
||||||
size_t our_end_index = our_start_index + our_length_index;
|
size_t our_end_index = our_start_index + our_length_index;
|
||||||
|
|
||||||
@@ -190,6 +197,8 @@ void AudioVisualWaveform::OverwriteSilence(const rational &start, const rational
|
|||||||
|
|
||||||
memset(reinterpret_cast<char*>(our_arr.data()) + our_start_index * sizeof(SamplePerChannel), 0, our_length_index * sizeof(SamplePerChannel));
|
memset(reinterpret_cast<char*>(our_arr.data()) + our_start_index * sizeof(SamplePerChannel), 0, our_length_index * sizeof(SamplePerChannel));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
length_ = qMax(length_, start + length);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioVisualWaveform::TrimIn(rational length)
|
void AudioVisualWaveform::TrimIn(rational length)
|
||||||
@@ -198,6 +207,8 @@ void AudioVisualWaveform::TrimIn(rational length)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual_start_ += length;
|
||||||
|
|
||||||
bool negative = (length < 0);
|
bool negative = (length < 0);
|
||||||
if (negative) {
|
if (negative) {
|
||||||
length = -length;
|
length = -length;
|
||||||
@@ -225,9 +236,9 @@ void AudioVisualWaveform::TrimIn(rational length)
|
|||||||
|
|
||||||
AudioVisualWaveform AudioVisualWaveform::Mid(const rational &offset) const
|
AudioVisualWaveform AudioVisualWaveform::Mid(const rational &offset) const
|
||||||
{
|
{
|
||||||
AudioVisualWaveform mid = *this;
|
AudioVisualWaveform mid = *this;
|
||||||
|
|
||||||
mid.TrimIn(offset);
|
mid.TrimIn(offset - virtual_start_);
|
||||||
|
|
||||||
return mid;
|
return mid;
|
||||||
}
|
}
|
||||||
@@ -236,7 +247,7 @@ AudioVisualWaveform AudioVisualWaveform::Mid(const rational &offset, const ratio
|
|||||||
{
|
{
|
||||||
AudioVisualWaveform mid = *this;
|
AudioVisualWaveform mid = *this;
|
||||||
|
|
||||||
mid.TrimRange(offset, length);
|
mid.TrimRange(offset - virtual_start_, length);
|
||||||
|
|
||||||
return mid;
|
return mid;
|
||||||
}
|
}
|
||||||
@@ -273,7 +284,7 @@ AudioVisualWaveform::Sample AudioVisualWaveform::GetSummaryFromTime(const ration
|
|||||||
|
|
||||||
double rate_dbl = using_mipmap->first.toDouble();
|
double rate_dbl = using_mipmap->first.toDouble();
|
||||||
|
|
||||||
size_t start_sample = time_to_samples(start, rate_dbl);
|
size_t start_sample = time_to_samples(start - virtual_start_, rate_dbl);
|
||||||
size_t sample_length = time_to_samples(length, rate_dbl);
|
size_t sample_length = time_to_samples(length, rate_dbl);
|
||||||
|
|
||||||
const Sample &mipmap_data = using_mipmap->second;
|
const Sample &mipmap_data = using_mipmap->second;
|
||||||
@@ -421,7 +432,7 @@ void AudioVisualWaveform::DrawWaveform(QPainter *painter, const QRect& rect, con
|
|||||||
double rate_dbl = rate.toDouble();
|
double rate_dbl = rate.toDouble();
|
||||||
const Sample& arr = using_mipmap->second;
|
const Sample& arr = using_mipmap->second;
|
||||||
|
|
||||||
size_t start_sample_index = samples.time_to_samples(start_time, rate_dbl);
|
size_t start_sample_index = samples.time_to_samples(start_time - samples.virtual_start_, rate_dbl);
|
||||||
|
|
||||||
if (start_sample_index >= arr.size()) {
|
if (start_sample_index >= arr.size()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -123,6 +123,10 @@ private:
|
|||||||
|
|
||||||
std::map<rational, Sample>::const_iterator GetMipmapForScale(double scale) const;
|
std::map<rational, Sample>::const_iterator GetMipmapForScale(double scale) const;
|
||||||
|
|
||||||
|
void ValidateVirtualStart(const rational &new_start);
|
||||||
|
|
||||||
|
rational virtual_start_;
|
||||||
|
|
||||||
int channels_;
|
int channels_;
|
||||||
|
|
||||||
std::map<rational, Sample> mipmapped_data_;
|
std::map<rational, Sample> mipmapped_data_;
|
||||||
|
|||||||
@@ -126,6 +126,17 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool OverlapsWith(const TimeRange& r, bool in_inclusive = true, bool out_inclusive = true) const
|
||||||
|
{
|
||||||
|
for (const TimeRange &range : array_) {
|
||||||
|
if (range.OverlapsWith(r, in_inclusive, out_inclusive)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool isEmpty() const
|
bool isEmpty() const
|
||||||
{
|
{
|
||||||
return array_.isEmpty();
|
return array_.isEmpty();
|
||||||
|
|||||||
+2
-2
@@ -1490,7 +1490,7 @@ bool Core::LabelNodes(const QVector<Node *> &nodes, MultiUndoCommand *parent)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Sequence *Core::CreateNewSequenceForProject(Project* project) const
|
Sequence *Core::CreateNewSequenceForProject(const QString &format, Project* project)
|
||||||
{
|
{
|
||||||
Sequence* new_sequence = new Sequence();
|
Sequence* new_sequence = new Sequence();
|
||||||
|
|
||||||
@@ -1498,7 +1498,7 @@ Sequence *Core::CreateNewSequenceForProject(Project* project) const
|
|||||||
int sequence_number = 1;
|
int sequence_number = 1;
|
||||||
QString sequence_name;
|
QString sequence_name;
|
||||||
do {
|
do {
|
||||||
sequence_name = tr("Sequence %1").arg(sequence_number);
|
sequence_name = format.arg(sequence_number);
|
||||||
sequence_number++;
|
sequence_number++;
|
||||||
} while (project->root()->ChildExistsWithName(sequence_name));
|
} while (project->root()->ChildExistsWithName(sequence_name));
|
||||||
new_sequence->SetLabel(sequence_name);
|
new_sequence->SetLabel(sequence_name);
|
||||||
|
|||||||
+5
-1
@@ -255,7 +255,11 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @brief Create a new sequence named appropriately for the active project
|
* @brief Create a new sequence named appropriately for the active project
|
||||||
*/
|
*/
|
||||||
Sequence* CreateNewSequenceForProject(Project *project) const;
|
static Sequence* CreateNewSequenceForProject(const QString &format, Project *project);
|
||||||
|
static Sequence* CreateNewSequenceForProject(Project *project)
|
||||||
|
{
|
||||||
|
return CreateNewSequenceForProject(tr("Sequence %1"), project);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Opens a project from the recently opened list
|
* @brief Opens a project from the recently opened list
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ void ClipBlock::RequestRangeFromConnected(const TimeRange &range)
|
|||||||
{
|
{
|
||||||
TimeRange thumb_range = range.Intersected(max_range);
|
TimeRange thumb_range = range.Intersected(max_range);
|
||||||
if (GetAdjustedThumbnailRange(&thumb_range)) {
|
if (GetAdjustedThumbnailRange(&thumb_range)) {
|
||||||
emit connected->thumbnail_cache()->Request(thumb_range);
|
connected->thumbnail_cache()->Request(thumb_range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +296,7 @@ void ClipBlock::RequestRangeForCache(PlaybackCache *cache, const TimeRange &max_
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (request) {
|
if (request) {
|
||||||
emit cache->Request(r);
|
cache->Request(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,4 +541,14 @@ TimeRange ClipBlock::media_range() const
|
|||||||
return InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length()));
|
return InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MultiCamNode *ClipBlock::FindMulticam()
|
||||||
|
{
|
||||||
|
auto v = FindInputNodesConnectedToInput<MultiCamNode>(NodeInput(this, kBufferIn));
|
||||||
|
if (v.empty()) {
|
||||||
|
return nullptr;
|
||||||
|
} else {
|
||||||
|
return v.first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include "audio/audiovisualwaveform.h"
|
#include "audio/audiovisualwaveform.h"
|
||||||
#include "codec/decoder.h"
|
#include "codec/decoder.h"
|
||||||
#include "node/block/block.h"
|
#include "node/block/block.h"
|
||||||
|
#include "node/input/multicam/multicamnode.h"
|
||||||
#include "node/output/track/track.h"
|
#include "node/output/track/track.h"
|
||||||
|
|
||||||
namespace olive {
|
namespace olive {
|
||||||
@@ -197,6 +198,8 @@ public:
|
|||||||
SetStandardValue(kLoopModeInput, int(l));
|
SetStandardValue(kLoopModeInput, int(l));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MultiCamNode *FindMulticam();
|
||||||
|
|
||||||
static const QString kBufferIn;
|
static const QString kBufferIn;
|
||||||
static const QString kMediaInInput;
|
static const QString kMediaInInput;
|
||||||
static const QString kSpeedInput;
|
static const QString kSpeedInput;
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
#include "generator/text/textv1.h"
|
#include "generator/text/textv1.h"
|
||||||
#include "generator/text/textv2.h"
|
#include "generator/text/textv2.h"
|
||||||
#include "generator/text/textv3.h"
|
#include "generator/text/textv3.h"
|
||||||
|
#include "input/multicam/multicamnode.h"
|
||||||
#include "input/time/timeinput.h"
|
#include "input/time/timeinput.h"
|
||||||
#include "input/value/valuenode.h"
|
#include "input/value/valuenode.h"
|
||||||
#include "keying/chromakey/chromakey.h"
|
#include "keying/chromakey/chromakey.h"
|
||||||
@@ -306,6 +307,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
|
|||||||
return new SwirlDistortNode();
|
return new SwirlDistortNode();
|
||||||
case kRippleDistort:
|
case kRippleDistort:
|
||||||
return new RippleDistortNode();
|
return new RippleDistortNode();
|
||||||
|
case kMulticamNode:
|
||||||
|
return new MultiCamNode();
|
||||||
|
|
||||||
case kInternalNodeCount:
|
case kInternalNodeCount:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ public:
|
|||||||
kRippleDistort,
|
kRippleDistort,
|
||||||
kTileDistort,
|
kTileDistort,
|
||||||
kSwirlDistort,
|
kSwirlDistort,
|
||||||
|
kMulticamNode,
|
||||||
|
|
||||||
// Count value
|
// Count value
|
||||||
kInternalNodeCount
|
kInternalNodeCount
|
||||||
|
|||||||
@@ -116,9 +116,9 @@ void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) con
|
|||||||
QImage img((uchar *) frame->data(), frame->width(), frame->height(), frame->linesize_bytes(), QImage::Format_RGBA8888_Premultiplied);
|
QImage img((uchar *) frame->data(), frame->width(), frame->height(), frame->linesize_bytes(), QImage::Format_RGBA8888_Premultiplied);
|
||||||
img.fill(Qt::transparent);
|
img.fill(Qt::transparent);
|
||||||
|
|
||||||
QVector<NodeValue> points = job.Get(kPointsInput).value< QVector<NodeValue> >();
|
auto points = job.Get(kPointsInput).toArray();
|
||||||
|
|
||||||
QPainterPath path = GeneratePath(points);
|
QPainterPath path = GeneratePath(points, InputArraySize(kPointsInput));
|
||||||
|
|
||||||
QPainter p(&img);
|
QPainter p(&img);
|
||||||
double par = frame->video_params().pixel_aspect_ratio().toDouble();
|
double par = frame->video_params().pixel_aspect_ratio().toDouble();
|
||||||
@@ -175,7 +175,7 @@ void PolygonGenerator::UpdateGizmoPositions(const NodeValueRow &row, const NodeG
|
|||||||
|
|
||||||
QPointF half_res = res.toPointF()/2;
|
QPointF half_res = res.toPointF()/2;
|
||||||
|
|
||||||
QVector<NodeValue> points = row[kPointsInput].value< QVector<NodeValue> >();
|
auto points = row[kPointsInput].toArray();
|
||||||
|
|
||||||
int current_pos_sz = gizmo_position_handles_.size();
|
int current_pos_sz = gizmo_position_handles_.size();
|
||||||
|
|
||||||
@@ -200,8 +200,9 @@ void PolygonGenerator::UpdateGizmoPositions(const NodeValueRow &row, const NodeG
|
|||||||
bez_gizmo2->SetSmaller(true);
|
bez_gizmo2->SetSmaller(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!points.isEmpty()) {
|
int pts_sz = InputArraySize(kPointsInput);
|
||||||
for (int i=0; i<points.size(); i++) {
|
if (!points.empty()) {
|
||||||
|
for (int i=0; i<pts_sz; i++) {
|
||||||
const Bezier &pt = points.at(i).toBezier();
|
const Bezier &pt = points.at(i).toBezier();
|
||||||
|
|
||||||
QPointF main = pt.ToPointF() + half_res;
|
QPointF main = pt.ToPointF() + half_res;
|
||||||
@@ -217,7 +218,7 @@ void PolygonGenerator::UpdateGizmoPositions(const NodeValueRow &row, const NodeG
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
poly_gizmo_->SetPath(GeneratePath(points).translated(half_res));
|
poly_gizmo_->SetPath(GeneratePath(points, pts_sz).translated(half_res));
|
||||||
}
|
}
|
||||||
|
|
||||||
ShaderCode PolygonGenerator::GetShaderCode(const ShaderRequest &request) const
|
ShaderCode PolygonGenerator::GetShaderCode(const ShaderRequest &request) const
|
||||||
@@ -250,19 +251,19 @@ void PolygonGenerator::AddPointToPath(QPainterPath *path, const Bezier &before,
|
|||||||
after.ToPointF());
|
after.ToPointF());
|
||||||
}
|
}
|
||||||
|
|
||||||
QPainterPath PolygonGenerator::GeneratePath(const QVector<NodeValue> &points)
|
QPainterPath PolygonGenerator::GeneratePath(const NodeValueArray &points, int size)
|
||||||
{
|
{
|
||||||
QPainterPath path;
|
QPainterPath path;
|
||||||
|
|
||||||
if (!points.isEmpty()) {
|
if (!points.empty()) {
|
||||||
const Bezier &first_pt = points.first().toBezier();
|
const Bezier &first_pt = points.at(0).toBezier();
|
||||||
path.moveTo(first_pt.ToPointF());
|
path.moveTo(first_pt.ToPointF());
|
||||||
|
|
||||||
for (int i=1; i<points.size(); i++) {
|
for (int i=1; i<size; i++) {
|
||||||
AddPointToPath(&path, points.at(i-1).toBezier(), points.at(i).toBezier());
|
AddPointToPath(&path, points.at(i-1).toBezier(), points.at(i).toBezier());
|
||||||
}
|
}
|
||||||
|
|
||||||
AddPointToPath(&path, points.last().toBezier(), first_pt);
|
AddPointToPath(&path, points.at(size-1).toBezier(), first_pt);
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ protected slots:
|
|||||||
private:
|
private:
|
||||||
static void AddPointToPath(QPainterPath *path, const Bezier &before, const Bezier &after);
|
static void AddPointToPath(QPainterPath *path, const Bezier &before, const Bezier &after);
|
||||||
|
|
||||||
static QPainterPath GeneratePath(const QVector<NodeValue> &points);
|
static QPainterPath GeneratePath(const NodeValueArray &points, int size);
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void ValidateGizmoVectorSize(QVector<T*> &vec, int new_sz);
|
void ValidateGizmoVectorSize(QVector<T*> &vec, int new_sz);
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ void GeneratorWithMerge::PushMergableJob(const NodeValueRow &value, TexturePtr j
|
|||||||
|
|
||||||
merge.SetShaderID(QStringLiteral("mrg"));
|
merge.SetShaderID(QStringLiteral("mrg"));
|
||||||
merge.Insert(MergeNode::kBaseIn, value[kBaseInput]);
|
merge.Insert(MergeNode::kBaseIn, value[kBaseInput]);
|
||||||
merge.Insert(MergeNode::kBlendIn, NodeValue(NodeValue::kTexture, base->toJob(*job->job()), this));
|
merge.Insert(MergeNode::kBlendIn, NodeValue(NodeValue::kTexture, job, this));
|
||||||
|
|
||||||
table->Push(NodeValue::kTexture, base->toJob(merge), this);
|
table->Push(NodeValue::kTexture, base->toJob(merge), this);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &global
|
|||||||
if (!args.empty()) {
|
if (!args.empty()) {
|
||||||
QStringList list;
|
QStringList list;
|
||||||
list.reserve(args.size());
|
list.reserve(args.size());
|
||||||
for (int i=0; i<args.size(); i++) {
|
for (size_t i=0; i<args.size(); i++) {
|
||||||
list.append(args[i].toString());
|
list.append(args[i].toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -24,6 +24,7 @@
|
|||||||
#include <QVector2D>
|
#include <QVector2D>
|
||||||
|
|
||||||
#include "common/timerange.h"
|
#include "common/timerange.h"
|
||||||
|
#include "render/audioparams.h"
|
||||||
#include "render/videoparams.h"
|
#include "render/videoparams.h"
|
||||||
|
|
||||||
namespace olive {
|
namespace olive {
|
||||||
@@ -33,19 +34,22 @@ class NodeGlobals
|
|||||||
public:
|
public:
|
||||||
NodeGlobals(){}
|
NodeGlobals(){}
|
||||||
|
|
||||||
NodeGlobals(const VideoParams &vparam, const TimeRange &time) :
|
NodeGlobals(const VideoParams &vparam, const AudioParams &aparam, const TimeRange &time) :
|
||||||
video_params_(vparam),
|
video_params_(vparam),
|
||||||
|
audio_params_(aparam),
|
||||||
time_(time)
|
time_(time)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector2D square_resolution() const { return video_params_.square_resolution(); }
|
QVector2D square_resolution() const { return video_params_.square_resolution(); }
|
||||||
QVector2D nonsquare_resolution() const { return video_params_.resolution(); }
|
QVector2D nonsquare_resolution() const { return video_params_.resolution(); }
|
||||||
|
const AudioParams &aparams() const { return audio_params_; }
|
||||||
const VideoParams &vparams() const { return video_params_; }
|
const VideoParams &vparams() const { return video_params_; }
|
||||||
const TimeRange &time() const { return time_; }
|
const TimeRange &time() const { return time_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VideoParams video_params_;
|
VideoParams video_params_;
|
||||||
|
AudioParams audio_params_;
|
||||||
TimeRange time_;
|
TimeRange time_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
add_subdirectory(multicam)
|
||||||
add_subdirectory(time)
|
add_subdirectory(time)
|
||||||
add_subdirectory(value)
|
add_subdirectory(value)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Olive - Non-Linear Video Editor
|
||||||
|
# Copyright (C) 2022 Olive Team
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
set(OLIVE_SOURCES
|
||||||
|
${OLIVE_SOURCES}
|
||||||
|
node/input/multicam/multicamnode.h
|
||||||
|
node/input/multicam/multicamnode.cpp
|
||||||
|
PARENT_SCOPE
|
||||||
|
)
|
||||||
@@ -1,6 +1,98 @@
|
|||||||
#include "multicamnode.h"
|
#include "multicamnode.h"
|
||||||
|
|
||||||
|
namespace olive {
|
||||||
|
|
||||||
|
#define super Node
|
||||||
|
|
||||||
|
const QString MultiCamNode::kCurrentInput = QStringLiteral("current_in");
|
||||||
|
const QString MultiCamNode::kSourcesInput = QStringLiteral("sources_in");
|
||||||
|
|
||||||
MultiCamNode::MultiCamNode()
|
MultiCamNode::MultiCamNode()
|
||||||
{
|
{
|
||||||
|
AddInput(kCurrentInput, NodeValue::kInt, InputFlags(kInputFlagStatic));
|
||||||
|
|
||||||
|
// Make current index start at 1 instead of 0
|
||||||
|
SetInputProperty(kCurrentInput, QStringLiteral("offset"), 1);
|
||||||
|
SetInputProperty(kCurrentInput, QStringLiteral("min"), 0);
|
||||||
|
|
||||||
|
AddInput(kSourcesInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray));
|
||||||
|
SetInputProperty(kSourcesInput, QStringLiteral("arraystart"), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString MultiCamNode::Name() const
|
||||||
|
{
|
||||||
|
return tr("Multi-Cam");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString MultiCamNode::id() const
|
||||||
|
{
|
||||||
|
return QStringLiteral("org.olivevideoeditor.Olive.multicam");
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<Node::CategoryID> MultiCamNode::Category() const
|
||||||
|
{
|
||||||
|
return {kCategoryTimeline};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString MultiCamNode::Description() const
|
||||||
|
{
|
||||||
|
return tr("Allows easy switching between multiple sources.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Node::ActiveElements MultiCamNode::GetActiveElementsAtTime(const QString &input, const TimeRange &r) const
|
||||||
|
{
|
||||||
|
if (input == kSourcesInput) {
|
||||||
|
int src = GetCurrentSource();
|
||||||
|
if (src >= 0 && src < InputArraySize(kSourcesInput)) {
|
||||||
|
Node::ActiveElements a;
|
||||||
|
a.add(src);
|
||||||
|
return a;
|
||||||
|
} else {
|
||||||
|
return ActiveElements::kNoElements;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return super::GetActiveElementsAtTime(input, r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiCamNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||||
|
{
|
||||||
|
NodeValueArray arr = value[kSourcesInput].toArray();
|
||||||
|
if (!arr.empty()) {
|
||||||
|
table->Push(arr.begin()->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiCamNode::IndexToRowCols(int index, int total_rows, int total_cols, int *row, int *col)
|
||||||
|
{
|
||||||
|
Q_UNUSED(total_rows)
|
||||||
|
|
||||||
|
*col = index%total_cols;
|
||||||
|
*row = index/total_cols;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiCamNode::Retranslate()
|
||||||
|
{
|
||||||
|
super::Retranslate();
|
||||||
|
|
||||||
|
SetInputName(kCurrentInput, tr("Current"));
|
||||||
|
SetInputName(kSourcesInput, tr("Sources"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiCamNode::GetRowsAndColumns(int sources, int *rows_in, int *cols_in)
|
||||||
|
{
|
||||||
|
int &rows = *rows_in;
|
||||||
|
int &cols = *cols_in;
|
||||||
|
|
||||||
|
rows = 1;
|
||||||
|
cols = 1;
|
||||||
|
while (rows*cols < sources) {
|
||||||
|
if (rows < cols) {
|
||||||
|
rows++;
|
||||||
|
} else {
|
||||||
|
cols++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,57 @@
|
|||||||
#ifndef MULTICAMNODE_H
|
#ifndef MULTICAMNODE_H
|
||||||
#define MULTICAMNODE_H
|
#define MULTICAMNODE_H
|
||||||
|
|
||||||
|
#include "node/node.h"
|
||||||
|
|
||||||
class MultiCamNode
|
namespace olive {
|
||||||
|
|
||||||
|
class MultiCamNode : public Node
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
MultiCamNode();
|
MultiCamNode();
|
||||||
|
|
||||||
|
NODE_DEFAULT_FUNCTIONS(MultiCamNode)
|
||||||
|
|
||||||
|
virtual QString Name() const override;
|
||||||
|
virtual QString id() const override;
|
||||||
|
virtual QVector<CategoryID> Category() const override;
|
||||||
|
virtual QString Description() const override;
|
||||||
|
|
||||||
|
virtual ActiveElements GetActiveElementsAtTime(const QString &input, const TimeRange &r) const override;
|
||||||
|
|
||||||
|
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||||
|
|
||||||
|
virtual void Retranslate() override;
|
||||||
|
|
||||||
|
static const QString kCurrentInput;
|
||||||
|
static const QString kSourcesInput;
|
||||||
|
|
||||||
|
int GetCurrentSource() const
|
||||||
|
{
|
||||||
|
return GetStandardValue(kCurrentInput).toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetSourceCount() const
|
||||||
|
{
|
||||||
|
return InputArraySize(kSourcesInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GetRowsAndColumns(int sources, int *rows, int *cols);
|
||||||
|
void GetRowsAndColumns(int *rows, int *cols) const
|
||||||
|
{
|
||||||
|
return GetRowsAndColumns(GetSourceCount(), rows, cols);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void IndexToRowCols(int index, int total_rows, int total_cols, int *row, int *col);
|
||||||
|
|
||||||
|
static int RowsColsToIndex(int row, int col, int total_rows, int total_cols)
|
||||||
|
{
|
||||||
|
return col + row * total_cols;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#endif // MULTICAMNODE_H
|
#endif // MULTICAMNODE_H
|
||||||
|
|||||||
+36
-39
@@ -27,11 +27,8 @@
|
|||||||
|
|
||||||
#include "common/bezier.h"
|
#include "common/bezier.h"
|
||||||
#include "common/lerp.h"
|
#include "common/lerp.h"
|
||||||
#include "common/timecodefunctions.h"
|
|
||||||
#include "common/xmlutils.h"
|
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
#include "config/config.h"
|
#include "config/config.h"
|
||||||
#include "node/project/footage/footage.h"
|
|
||||||
#include "project/project.h"
|
#include "project/project.h"
|
||||||
#include "ui/colorcoding.h"
|
#include "ui/colorcoding.h"
|
||||||
#include "ui/icons/icons.h"
|
#include "ui/icons/icons.h"
|
||||||
@@ -251,7 +248,7 @@ QString Node::GetInputName(const QString &id) const
|
|||||||
if (i) {
|
if (i) {
|
||||||
return i->human_name;
|
return i->human_name;
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("get name of", id);
|
ReportInvalidInput("get name of", id, -1);
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,7 +275,7 @@ bool Node::IsInputKeyframing(const QString &input, int element) const
|
|||||||
if (imm) {
|
if (imm) {
|
||||||
return imm->is_keyframing();
|
return imm->is_keyframing();
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("get keyframing state of", input);
|
ReportInvalidInput("get keyframing state of", input, element);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -297,7 +294,7 @@ void Node::SetInputIsKeyframing(const QString &input, bool e, int element)
|
|||||||
|
|
||||||
emit KeyframeEnableChanged(NodeInput(this, input, element), e);
|
emit KeyframeEnableChanged(NodeInput(this, input, element), e);
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("set keyframing state of", input);
|
ReportInvalidInput("set keyframing state of", input, element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,7 +321,7 @@ bool Node::IsUsingStandardValue(const QString &input, int track, int element) co
|
|||||||
if (imm) {
|
if (imm) {
|
||||||
return imm->is_using_standard_value(track);
|
return imm->is_using_standard_value(track);
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("determine whether using standard value in", input);
|
ReportInvalidInput("determine whether using standard value in", input, element);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,7 +333,7 @@ NodeValue::Type Node::GetInputDataType(const QString &id) const
|
|||||||
if (i) {
|
if (i) {
|
||||||
return i->type;
|
return i->type;
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("get data type of", id);
|
ReportInvalidInput("get data type of", id, -1);
|
||||||
return NodeValue::kNone;
|
return NodeValue::kNone;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,7 +352,7 @@ void Node::SetInputDataType(const QString &id, const NodeValue::Type &type)
|
|||||||
|
|
||||||
emit InputDataTypeChanged(id, type);
|
emit InputDataTypeChanged(id, type);
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("set data type of", id);
|
ReportInvalidInput("set data type of", id, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,7 +363,7 @@ bool Node::HasInputProperty(const QString &id, const QString &name) const
|
|||||||
if (i) {
|
if (i) {
|
||||||
return i->properties.contains(name);
|
return i->properties.contains(name);
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("get property of", id);
|
ReportInvalidInput("get property of", id, -1);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -378,7 +375,7 @@ QHash<QString, QVariant> Node::GetInputProperties(const QString &id) const
|
|||||||
if (i) {
|
if (i) {
|
||||||
return i->properties;
|
return i->properties;
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("get property table of", id);
|
ReportInvalidInput("get property table of", id, -1);
|
||||||
return QHash<QString, QVariant>();
|
return QHash<QString, QVariant>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,7 +387,7 @@ QVariant Node::GetInputProperty(const QString &id, const QString &name) const
|
|||||||
if (i) {
|
if (i) {
|
||||||
return i->properties.value(name);
|
return i->properties.value(name);
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("get property of", id);
|
ReportInvalidInput("get property of", id, -1);
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -404,7 +401,7 @@ void Node::SetInputProperty(const QString &id, const QString &name, const QVaria
|
|||||||
|
|
||||||
emit InputPropertyChanged(id, name, value);
|
emit InputPropertyChanged(id, name, value);
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("set property of", id);
|
ReportInvalidInput("set property of", id, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,7 +545,7 @@ SplitValue Node::GetSplitDefaultValue(const QString &input) const
|
|||||||
if (i) {
|
if (i) {
|
||||||
return i->default_value;
|
return i->default_value;
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("retrieve default value of", input);
|
ReportInvalidInput("retrieve default value of", input, -1);
|
||||||
return SplitValue();
|
return SplitValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -577,7 +574,7 @@ void Node::SetSplitDefaultValue(const QString &input, const SplitValue &val)
|
|||||||
if (i) {
|
if (i) {
|
||||||
i->default_value = val;
|
i->default_value = val;
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("set default value of", input);
|
ReportInvalidInput("set default value of", input, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -590,7 +587,7 @@ void Node::SetSplitDefaultValueOnTrack(const QString &input, const QVariant &val
|
|||||||
i->default_value[track] = val;
|
i->default_value[track] = val;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("set default value on track of", input);
|
ReportInvalidInput("set default value on track of", input, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,7 +603,7 @@ QVector<NodeKeyframe *> Node::GetKeyframesAtTime(const QString &input, const rat
|
|||||||
if (imm) {
|
if (imm) {
|
||||||
return imm->get_keyframe_at_time(time);
|
return imm->get_keyframe_at_time(time);
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("get keyframes at time from", input);
|
ReportInvalidInput("get keyframes at time from", input, element);
|
||||||
return QVector<NodeKeyframe*>();
|
return QVector<NodeKeyframe*>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -618,7 +615,7 @@ NodeKeyframe *Node::GetKeyframeAtTimeOnTrack(const QString &input, const rationa
|
|||||||
if (imm) {
|
if (imm) {
|
||||||
return imm->get_keyframe_at_time_on_track(time, track);
|
return imm->get_keyframe_at_time_on_track(time, track);
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("get keyframe at time on track from", input);
|
ReportInvalidInput("get keyframe at time on track from", input, element);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -630,7 +627,7 @@ NodeKeyframe::Type Node::GetBestKeyframeTypeForTimeOnTrack(const QString &input,
|
|||||||
if (imm) {
|
if (imm) {
|
||||||
return imm->get_best_keyframe_type_for_time(time, track);
|
return imm->get_best_keyframe_type_for_time(time, track);
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("get closest keyframe before a time from", input);
|
ReportInvalidInput("get closest keyframe before a time from", input, element);
|
||||||
return NodeKeyframe::kDefaultType;
|
return NodeKeyframe::kDefaultType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -647,7 +644,7 @@ NodeKeyframe *Node::GetEarliestKeyframe(const QString &id, int element) const
|
|||||||
if (imm) {
|
if (imm) {
|
||||||
return imm->get_earliest_keyframe();
|
return imm->get_earliest_keyframe();
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("get earliest keyframe from", id);
|
ReportInvalidInput("get earliest keyframe from", id, element);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -659,7 +656,7 @@ NodeKeyframe *Node::GetLatestKeyframe(const QString &id, int element) const
|
|||||||
if (imm) {
|
if (imm) {
|
||||||
return imm->get_latest_keyframe();
|
return imm->get_latest_keyframe();
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("get latest keyframe from", id);
|
ReportInvalidInput("get latest keyframe from", id, element);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -671,7 +668,7 @@ NodeKeyframe *Node::GetClosestKeyframeBeforeTime(const QString &id, const ration
|
|||||||
if (imm) {
|
if (imm) {
|
||||||
return imm->get_closest_keyframe_before_time(time);
|
return imm->get_closest_keyframe_before_time(time);
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("get closest keyframe before a time from", id);
|
ReportInvalidInput("get closest keyframe before a time from", id, element);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -683,7 +680,7 @@ NodeKeyframe *Node::GetClosestKeyframeAfterTime(const QString &id, const rationa
|
|||||||
if (imm) {
|
if (imm) {
|
||||||
return imm->get_closest_keyframe_after_time(time);
|
return imm->get_closest_keyframe_after_time(time);
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("get closest keyframe after a time from", id);
|
ReportInvalidInput("get closest keyframe after a time from", id, element);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -695,7 +692,7 @@ bool Node::HasKeyframeAtTime(const QString &id, const rational &time, int elemen
|
|||||||
if (imm) {
|
if (imm) {
|
||||||
return imm->has_keyframe_at_time(time);
|
return imm->has_keyframe_at_time(time);
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("determine if it has a keyframe at a time from", id);
|
ReportInvalidInput("determine if it has a keyframe at a time from", id, element);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -719,7 +716,7 @@ SplitValue Node::GetSplitStandardValue(const QString &id, int element) const
|
|||||||
if (imm) {
|
if (imm) {
|
||||||
return imm->get_split_standard_value();
|
return imm->get_split_standard_value();
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("get standard value of", id);
|
ReportInvalidInput("get standard value of", id, element);
|
||||||
return SplitValue();
|
return SplitValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -731,7 +728,7 @@ QVariant Node::GetSplitStandardValueOnTrack(const QString &input, int track, int
|
|||||||
if (imm) {
|
if (imm) {
|
||||||
return imm->get_split_standard_value_on_track(track);
|
return imm->get_split_standard_value_on_track(track);
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("get standard value of", input);
|
ReportInvalidInput("get standard value of", input, element);
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -758,7 +755,7 @@ void Node::SetSplitStandardValue(const QString &id, const SplitValue &value, int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("set standard value of", id);
|
ReportInvalidInput("set standard value of", id, element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -774,7 +771,7 @@ void Node::SetSplitStandardValueOnTrack(const QString &id, int track, const QVar
|
|||||||
ParameterValueChanged(id, element, TimeRange(RATIONAL_MIN, RATIONAL_MAX));
|
ParameterValueChanged(id, element, TimeRange(RATIONAL_MIN, RATIONAL_MAX));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("set standard value of", id);
|
ReportInvalidInput("set standard value of", id, element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -873,7 +870,7 @@ int Node::InputArraySize(const QString &id) const
|
|||||||
if (i) {
|
if (i) {
|
||||||
return i->array_size;
|
return i->array_size;
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("retrieve array size of", id);
|
ReportInvalidInput("retrieve array size of", id, -1);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -914,7 +911,7 @@ InputFlags Node::GetInputFlags(const QString &input) const
|
|||||||
if (i) {
|
if (i) {
|
||||||
return i->flags;
|
return i->flags;
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("retrieve flags of", input);
|
ReportInvalidInput("retrieve flags of", input, -1);
|
||||||
return InputFlags(kInputFlagNormal);
|
return InputFlags(kInputFlagNormal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -927,7 +924,7 @@ void Node::SetInputFlags(const QString &input, const InputFlags &f)
|
|||||||
i->flags = f;
|
i->flags = f;
|
||||||
emit InputFlagsChanged(input, i->flags);
|
emit InputFlagsChanged(input, i->flags);
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("set flags of", input);
|
ReportInvalidInput("set flags of", input, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1227,7 +1224,7 @@ void Node::RemoveInput(const QString &id)
|
|||||||
int index = input_ids_.indexOf(id);
|
int index = input_ids_.indexOf(id);
|
||||||
|
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
ReportInvalidInput("remove", id);
|
ReportInvalidInput("remove", id, -1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1237,9 +1234,9 @@ void Node::RemoveInput(const QString &id)
|
|||||||
emit InputRemoved(id);
|
emit InputRemoved(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::ReportInvalidInput(const char *attempted_action, const QString& id) const
|
void Node::ReportInvalidInput(const char *attempted_action, const QString& id, int element) const
|
||||||
{
|
{
|
||||||
qWarning() << "Failed to" << attempted_action << "parameter" << id
|
qWarning() << "Failed to" << attempted_action << "parameter" << id << "element" << element
|
||||||
<< "in node" << this->id() << "- input doesn't exist";
|
<< "in node" << this->id() << "- input doesn't exist";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1250,7 +1247,7 @@ NodeInputImmediate *Node::CreateImmediate(const QString &input)
|
|||||||
if (i) {
|
if (i) {
|
||||||
return new NodeInputImmediate(i->type, i->default_value);
|
return new NodeInputImmediate(i->type, i->default_value);
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("create immediate", input);
|
ReportInvalidInput("create immediate", input, -1);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1260,7 +1257,7 @@ void Node::ArrayResizeInternal(const QString &id, int size)
|
|||||||
Input* imm = GetInternalInputData(id);
|
Input* imm = GetInternalInputData(id);
|
||||||
|
|
||||||
if (!imm) {
|
if (!imm) {
|
||||||
ReportInvalidInput("set array size", id);
|
ReportInvalidInput("set array size", id, -1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1299,7 +1296,7 @@ void Node::SetInputName(const QString &id, const QString &name)
|
|||||||
|
|
||||||
emit InputNameChanged(id, name);
|
emit InputNameChanged(id, name);
|
||||||
} else {
|
} else {
|
||||||
ReportInvalidInput("set name of", id);
|
ReportInvalidInput("set name of", id, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1426,8 +1423,8 @@ void Node::CopyValuesOfElement(const Node *src, Node *dst, const QString &input,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (const NodeKeyframeTrack& track, src->GetImmediate(input, src_element)->keyframe_tracks()) {
|
for (const NodeKeyframeTrack& track : src->GetImmediate(input, src_element)->keyframe_tracks()) {
|
||||||
foreach (NodeKeyframe* key, track) {
|
for (NodeKeyframe* key : track) {
|
||||||
NodeKeyframe *copy = key->copy(dst_element, command ? nullptr : dst);
|
NodeKeyframe *copy = key->copy(dst_element, command ? nullptr : dst);
|
||||||
if (command) {
|
if (command) {
|
||||||
command->add_child(new NodeParamInsertKeyframeCommand(dst, copy));
|
command->add_child(new NodeParamInsertKeyframeCommand(dst, copy));
|
||||||
|
|||||||
+35
-1
@@ -212,6 +212,40 @@ public:
|
|||||||
return input_ids_;
|
return input_ids_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ActiveElements
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Mode {
|
||||||
|
kAllElements,
|
||||||
|
kSpecified,
|
||||||
|
kNoElements
|
||||||
|
};
|
||||||
|
|
||||||
|
ActiveElements(Mode m = kAllElements)
|
||||||
|
{
|
||||||
|
mode_ = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mode mode() const { return mode_; }
|
||||||
|
std::list<int> elements() const { return elements_; }
|
||||||
|
|
||||||
|
void add(int e)
|
||||||
|
{
|
||||||
|
elements_.push_back(e);
|
||||||
|
mode_ = kSpecified;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Mode mode_;
|
||||||
|
std::list<int> elements_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual ActiveElements GetActiveElementsAtTime(const QString &input, const TimeRange &r) const
|
||||||
|
{
|
||||||
|
return ActiveElements::kAllElements;
|
||||||
|
}
|
||||||
|
|
||||||
bool HasInputWithID(const QString& id) const
|
bool HasInputWithID(const QString& id) const
|
||||||
{
|
{
|
||||||
return input_ids_.contains(id);
|
return input_ids_.contains(id);
|
||||||
@@ -1318,7 +1352,7 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReportInvalidInput(const char* attempted_action, const QString &id) const;
|
void ReportInvalidInput(const char* attempted_action, const QString &id, int element) const;
|
||||||
|
|
||||||
void ArrayResizeInternal(const QString& id, int size);
|
void ArrayResizeInternal(const QString& id, int size);
|
||||||
|
|
||||||
|
|||||||
+187
-43
@@ -24,8 +24,10 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFontMetrics>
|
#include <QFontMetrics>
|
||||||
|
|
||||||
|
#include "audio/audioprocessor.h"
|
||||||
|
#include "node/block/clip/clip.h"
|
||||||
#include "node/block/gap/gap.h"
|
#include "node/block/gap/gap.h"
|
||||||
#include "node/graph.h"
|
#include "node/block/transition/transition.h"
|
||||||
|
|
||||||
namespace olive {
|
namespace olive {
|
||||||
|
|
||||||
@@ -95,6 +97,55 @@ QString Track::Description() const
|
|||||||
"a Sequence.");
|
"a Sequence.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Node::ActiveElements Track::GetActiveElementsAtTime(const QString &input, const TimeRange &r) const
|
||||||
|
{
|
||||||
|
if (input == kBlockInput) {
|
||||||
|
if (IsMuted() || blocks_.empty() || r.in() >= track_length() || r.out() <= 0) {
|
||||||
|
return ActiveElements::kNoElements;
|
||||||
|
} else {
|
||||||
|
int start = GetBlockIndexAtTime(r.in());
|
||||||
|
int end = GetBlockIndexAtTime(r.out());
|
||||||
|
|
||||||
|
if (start == -1) {
|
||||||
|
start = 0;
|
||||||
|
}
|
||||||
|
if (end == -1) {
|
||||||
|
end = blocks_.size()-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveElements a;
|
||||||
|
for (int i=start; i<=end; i++) {
|
||||||
|
Block *b = blocks_.at(i);
|
||||||
|
if (b->is_enabled() && (dynamic_cast<ClipBlock*>(b) || dynamic_cast<TransitionBlock*>(b))) {
|
||||||
|
a.add(GetArrayIndexFromCacheIndex(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.elements().empty()) {
|
||||||
|
return ActiveElements::kNoElements;
|
||||||
|
} else {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return super::GetActiveElementsAtTime(input, r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Track::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||||
|
{
|
||||||
|
if (this->type() == Track::kVideo) {
|
||||||
|
// Just pass straight through
|
||||||
|
NodeValueArray a = value[kBlockInput].toArray();
|
||||||
|
if (!a.empty()) {
|
||||||
|
table->Push(a.begin()->second);
|
||||||
|
}
|
||||||
|
} else if (this->type() == Track::kAudio) {
|
||||||
|
// Audio
|
||||||
|
ProcessAudioTrack(value, globals, table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TimeRange Track::InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const
|
TimeRange Track::InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const
|
||||||
{
|
{
|
||||||
if (input == kBlockInput && element >= 0) {
|
if (input == kBlockInput && element >= 0) {
|
||||||
@@ -352,56 +403,31 @@ Block *Track::NearestBlockAfter(const rational &time) const
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Block *Track::BlockAtTime(const rational &time) const
|
bool Track::IsRangeFree(const TimeRange &range) const
|
||||||
{
|
{
|
||||||
if (IsMuted() || time > track_length() || blocks_.isEmpty()) {
|
Block *b = NearestBlockBeforeOrAt(range.in());
|
||||||
return nullptr;
|
if (!b) {
|
||||||
|
// No block here, assume track is empty here
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use binary search to find block at time
|
if (!dynamic_cast<GapBlock*>(b)) {
|
||||||
Block* using_block = nullptr;
|
// There's a block at or around the start point that isn't a gap, range is not free
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
int low = 0;
|
while ((b = b->next())) {
|
||||||
int high = blocks_.size() - 1;
|
if (b->in() >= range.out()) {
|
||||||
while (low <= high) {
|
// This block is after the range, no longer relevant
|
||||||
int mid = low + (high - low) / 2;
|
|
||||||
|
|
||||||
Block* block = blocks_.at(mid);
|
|
||||||
if (block->in() <= time && block->out() > time) {
|
|
||||||
using_block = block;
|
|
||||||
break;
|
break;
|
||||||
} else if (block->out() <= time) {
|
} else if (!dynamic_cast<GapBlock*>(b)) {
|
||||||
low = mid + 1;
|
// Found a block in this range, range is not free
|
||||||
} else {
|
return false;
|
||||||
high = mid - 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (using_block && !using_block->is_enabled()) {
|
// If we get here, we couldn't find anything in the way of this range
|
||||||
using_block = nullptr;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
return using_block;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<Block *> Track::BlocksAtTimeRange(const TimeRange &range) const
|
|
||||||
{
|
|
||||||
QVector<Block*> list;
|
|
||||||
|
|
||||||
if (IsMuted()) {
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Block* block, blocks_) {
|
|
||||||
if (block
|
|
||||||
&& block->is_enabled()
|
|
||||||
&& block->out() > range.in()
|
|
||||||
&& block->in() < range.out()) {
|
|
||||||
list.append(block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Track::InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options)
|
void Track::InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options)
|
||||||
@@ -579,6 +605,124 @@ int Track::GetCacheIndexFromArrayIndex(int index) const
|
|||||||
return block_array_indexes_.indexOf(index);
|
return block_array_indexes_.indexOf(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Track::GetBlockIndexAtTime(const rational &time) const
|
||||||
|
{
|
||||||
|
if (time < 0 || time >= track_length()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use binary search to find block at time
|
||||||
|
int low = 0;
|
||||||
|
int high = blocks_.size() - 1;
|
||||||
|
while (low <= high) {
|
||||||
|
int mid = low + (high - low) / 2;
|
||||||
|
|
||||||
|
Block* block = blocks_.at(mid);
|
||||||
|
if (block->in() <= time && block->out() > time) {
|
||||||
|
return mid;
|
||||||
|
} else if (block->out() <= time) {
|
||||||
|
low = mid + 1;
|
||||||
|
} else {
|
||||||
|
high = mid - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Track::ProcessAudioTrack(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||||
|
{
|
||||||
|
const TimeRange &range = globals.time();
|
||||||
|
|
||||||
|
// All these blocks will need to output to a buffer so we create one here
|
||||||
|
SampleBuffer block_range_buffer(globals.aparams(), range.length());
|
||||||
|
block_range_buffer.silence();
|
||||||
|
|
||||||
|
// Loop through active blocks retrieving their audio
|
||||||
|
NodeValueArray arr = value[kBlockInput].toArray();
|
||||||
|
|
||||||
|
for (auto it=arr.cbegin(); it!=arr.cend(); it++) {
|
||||||
|
Block *b = blocks_.at(GetCacheIndexFromArrayIndex(it->first));
|
||||||
|
|
||||||
|
TimeRange range_for_block(qMax(b->in(), range.in()),
|
||||||
|
qMin(b->out(), range.out()));
|
||||||
|
|
||||||
|
qint64 destination_offset = globals.aparams().time_to_samples(range_for_block.in() - range.in());
|
||||||
|
qint64 max_dest_sz = globals.aparams().time_to_samples(range_for_block.length());
|
||||||
|
|
||||||
|
// Destination buffer
|
||||||
|
SampleBuffer samples_from_this_block = it->second.toSamples();
|
||||||
|
ClipBlock *clip_cast = dynamic_cast<ClipBlock*>(b);
|
||||||
|
|
||||||
|
if (samples_from_this_block.is_allocated()) {
|
||||||
|
// If this is a clip, we might have extra speed/reverse information
|
||||||
|
if (clip_cast) {
|
||||||
|
double speed_value = clip_cast->speed();
|
||||||
|
bool reversed = clip_cast->reverse();
|
||||||
|
|
||||||
|
if (qIsNull(speed_value)) {
|
||||||
|
// Just silence, don't think there's any other practical application of 0 speed audio
|
||||||
|
samples_from_this_block.silence();
|
||||||
|
} else if (!qFuzzyCompare(speed_value, 1.0)) {
|
||||||
|
if (clip_cast->maintain_audio_pitch()) {
|
||||||
|
AudioProcessor processor;
|
||||||
|
|
||||||
|
if (processor.Open(samples_from_this_block.audio_params(), samples_from_this_block.audio_params(), speed_value)) {
|
||||||
|
AudioProcessor::Buffer out;
|
||||||
|
|
||||||
|
// FIXME: This is not the best way to do this, the TempoProcessor works best
|
||||||
|
// when it's given a continuous stream of audio, which is challenging
|
||||||
|
// in our current "modular" audio system. This should still work reasonably
|
||||||
|
// well on export (assuming audio is all generated at once on export), but
|
||||||
|
// users may hear clicks and pops in the audio during preview due to this
|
||||||
|
// approach.
|
||||||
|
int r = processor.Convert(samples_from_this_block.to_raw_ptrs().data(), samples_from_this_block.sample_count(), nullptr);
|
||||||
|
|
||||||
|
if (r < 0) {
|
||||||
|
qCritical() << "Failed to change tempo of audio:" << r;
|
||||||
|
} else {
|
||||||
|
processor.Flush();
|
||||||
|
|
||||||
|
processor.Convert(nullptr, 0, &out);
|
||||||
|
|
||||||
|
if (!out.empty()) {
|
||||||
|
int nb_samples = out.front().size() * samples_from_this_block.audio_params().bytes_per_sample_per_channel();
|
||||||
|
|
||||||
|
if (nb_samples) {
|
||||||
|
SampleBuffer new_samples(samples_from_this_block.audio_params(), nb_samples);
|
||||||
|
|
||||||
|
for (int i=0; i<out.size(); i++) {
|
||||||
|
memcpy(new_samples.data(i), out[i].data(), out[i].size());
|
||||||
|
}
|
||||||
|
|
||||||
|
samples_from_this_block = new_samples;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Multiply time
|
||||||
|
samples_from_this_block.speed(speed_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reversed) {
|
||||||
|
samples_from_this_block.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 copy_length = qMin(max_dest_sz, qint64(samples_from_this_block.sample_count() - destination_offset));
|
||||||
|
|
||||||
|
// Copy samples into destination buffer
|
||||||
|
for (int i=0; i<samples_from_this_block.audio_params().channel_count(); i++) {
|
||||||
|
block_range_buffer.set(i, samples_from_this_block.data(i) + destination_offset, destination_offset, copy_length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table->Push(NodeValue::kSamples, QVariant::fromValue(block_range_buffer), this);
|
||||||
|
}
|
||||||
|
|
||||||
void Track::BlockLengthChanged()
|
void Track::BlockLengthChanged()
|
||||||
{
|
{
|
||||||
// Assumes sender is a Block
|
// Assumes sender is a Block
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
#define TRACK_H
|
#define TRACK_H
|
||||||
|
|
||||||
#include "node/block/block.h"
|
#include "node/block/block.h"
|
||||||
#include "timeline/timelinecommon.h"
|
|
||||||
|
|
||||||
namespace olive {
|
namespace olive {
|
||||||
|
|
||||||
@@ -55,6 +54,9 @@ public:
|
|||||||
virtual QVector<CategoryID> Category() const override;
|
virtual QVector<CategoryID> Category() const override;
|
||||||
virtual QString Description() const override;
|
virtual QString Description() const override;
|
||||||
|
|
||||||
|
virtual ActiveElements GetActiveElementsAtTime(const QString &input, const TimeRange &r) const override;
|
||||||
|
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||||
|
|
||||||
virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override;
|
virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override;
|
||||||
|
|
||||||
virtual TimeRange OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override;
|
virtual TimeRange OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override;
|
||||||
@@ -314,29 +316,10 @@ public:
|
|||||||
*/
|
*/
|
||||||
Block* NearestBlockAfter(const rational& time) const;
|
Block* NearestBlockAfter(const rational& time) const;
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* @brief Returns the block that should be rendered/visible at a given time
|
* @brief Returns whether a time range is empty or only has a gap
|
||||||
*
|
|
||||||
* Use this for any video rendering or determining which block will actually be active at any
|
|
||||||
* time.
|
|
||||||
*
|
|
||||||
* @return Catches the first block that matches `block.in <= time && block.out > time`. Returns
|
|
||||||
* nullptr if the time exceeds the track length, the block active at this time is disabled, or
|
|
||||||
* if IsMuted() is true.
|
|
||||||
*/
|
*/
|
||||||
Block* BlockAtTime(const rational& time) const;
|
bool IsRangeFree(const TimeRange &range) const;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a list of blocks that should be rendered/visible during a given time range
|
|
||||||
*
|
|
||||||
* Use this for audio rendering to determine all blocks that will be active throughout a range
|
|
||||||
* of time.
|
|
||||||
*
|
|
||||||
* @return Similar to BlockAtTime() but will match several blocks where
|
|
||||||
* `block.in < range.out && block.out > range.in`. Returns an empty list if IsMuted() or if
|
|
||||||
* `range.in >= track.length`. Blocks that are not enabled will be omitted from the returned list.
|
|
||||||
*/
|
|
||||||
QVector<Block*> BlocksAtTimeRange(const TimeRange& range) const;
|
|
||||||
|
|
||||||
const QVector<Block *> &Blocks() const
|
const QVector<Block *> &Blocks() const
|
||||||
{
|
{
|
||||||
@@ -345,6 +328,12 @@ public:
|
|||||||
|
|
||||||
virtual void InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) override;
|
virtual void InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) override;
|
||||||
|
|
||||||
|
Block *VisibleBlockAtTime(const rational &t) const
|
||||||
|
{
|
||||||
|
int index = GetBlockIndexAtTime(t);
|
||||||
|
return (index == -1) ? nullptr : blocks_.at(index);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Adds Block `block` at the very beginning of the Sequence before all other clips
|
* @brief Adds Block `block` at the very beginning of the Sequence before all other clips
|
||||||
*/
|
*/
|
||||||
@@ -467,6 +456,10 @@ private:
|
|||||||
|
|
||||||
int GetCacheIndexFromArrayIndex(int index) const;
|
int GetCacheIndexFromArrayIndex(int index) const;
|
||||||
|
|
||||||
|
int GetBlockIndexAtTime(const rational &time) const;
|
||||||
|
|
||||||
|
void ProcessAudioTrack(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const;
|
||||||
|
|
||||||
TimeRangeList block_length_pending_invalidations_;
|
TimeRangeList block_length_pending_invalidations_;
|
||||||
|
|
||||||
QVector<Block*> blocks_;
|
QVector<Block*> blocks_;
|
||||||
|
|||||||
@@ -224,16 +224,16 @@ void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from,
|
|||||||
|
|
||||||
if (Node *connected = GetConnectedOutput(from, element)) {
|
if (Node *connected = GetConnectedOutput(from, element)) {
|
||||||
if (from == kTextureInput) {
|
if (from == kTextureInput) {
|
||||||
//emit connected->thumbnail_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly);
|
//connected->thumbnail_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly);
|
||||||
if (autocache_input_video_) {
|
if (autocache_input_video_) {
|
||||||
TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetVideoLength()));
|
TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetVideoLength()));
|
||||||
emit connected->video_frame_cache()->Request(range.Intersected(max_range));
|
connected->video_frame_cache()->Request(range.Intersected(max_range));
|
||||||
}
|
}
|
||||||
} else if (from == kSamplesInput) {
|
} else if (from == kSamplesInput) {
|
||||||
TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetAudioLength()));
|
TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetAudioLength()));
|
||||||
emit connected->waveform_cache()->Request(range.Intersected(max_range));
|
connected->waveform_cache()->Request(range.Intersected(max_range));
|
||||||
if (autocache_input_audio_) {
|
if (autocache_input_audio_) {
|
||||||
emit connected->audio_playback_cache()->Request(range.Intersected(max_range));
|
connected->audio_playback_cache()->Request(range.Intersected(max_range));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -389,11 +389,11 @@ Node::ValueHint ViewerOutput::GetConnectedSampleValueHint()
|
|||||||
|
|
||||||
void ViewerOutput::ConnectedToPreviewEvent()
|
void ViewerOutput::ConnectedToPreviewEvent()
|
||||||
{
|
{
|
||||||
if (Node *connected = GetConnectedOutput(kSamplesInput)) {
|
if (Node *connected = this->GetConnectedSampleOutput()) {
|
||||||
TimeRange max_range = InputTimeAdjustment(kSamplesInput, -1, TimeRange(0, GetAudioLength()));
|
TimeRange max_range = InputTimeAdjustment(kSamplesInput, -1, TimeRange(0, GetAudioLength()));
|
||||||
TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range);
|
TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range);
|
||||||
for (const TimeRange &r : invalid) {
|
for (const TimeRange &r : invalid) {
|
||||||
emit connected->waveform_cache()->Request(r);
|
connected->waveform_cache()->Request(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-40
@@ -88,11 +88,11 @@ NodeValue NodeTraverser::GenerateRowValue(const Node *node, const QString &input
|
|||||||
|
|
||||||
if (value.array()) {
|
if (value.array()) {
|
||||||
// Resolve each element of array
|
// Resolve each element of array
|
||||||
QVector<NodeValueTable> tables = value.value<QVector<NodeValueTable> >();
|
NodeValueTableArray tables = value.value<NodeValueTableArray>();
|
||||||
QVector<NodeValue> output(tables.size());
|
NodeValueArray output;
|
||||||
|
|
||||||
for (int i=0; i<tables.size(); i++) {
|
for (auto it=tables.begin(); it!=tables.end(); it++) {
|
||||||
output[i] = GenerateRowValueElement(node, input, i, &tables[i], time);
|
output[it->first] = GenerateRowValueElement(node, input, it->first, &it->second, time);
|
||||||
}
|
}
|
||||||
|
|
||||||
value = NodeValue(value.type(), QVariant::fromValue(output), value.source(), value.array(), value.tag());
|
value = NodeValue(value.type(), QVariant::fromValue(output), value.source(), value.array(), value.tag());
|
||||||
@@ -179,9 +179,9 @@ void NodeTraverser::Transform(QTransform *transform, const Node *start, const No
|
|||||||
transform_ = nullptr;
|
transform_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeGlobals NodeTraverser::GenerateGlobals(const VideoParams ¶ms, const TimeRange &time)
|
NodeGlobals NodeTraverser::GenerateGlobals(const VideoParams &vparams, const AudioParams &aparams, const TimeRange &time)
|
||||||
{
|
{
|
||||||
return NodeGlobals(params, time);
|
return NodeGlobals(vparams, aparams, time);
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& input, const TimeRange& range)
|
NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& input, const TimeRange& range)
|
||||||
@@ -205,18 +205,17 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu
|
|||||||
if (is_array) {
|
if (is_array) {
|
||||||
|
|
||||||
// Value is an array, we will return a list of NodeValueTables
|
// Value is an array, we will return a list of NodeValueTables
|
||||||
QVector<NodeValueTable> array_tbl(node->InputArraySize(input));
|
NodeValueTableArray array_tbl;
|
||||||
|
|
||||||
for (int i=0; i<array_tbl.size(); i++) {
|
Node::ActiveElements a = node->GetActiveElementsAtTime(input, range);
|
||||||
NodeValueTable& sub_tbl = array_tbl[i];
|
if (a.mode() == Node::ActiveElements::kAllElements) {
|
||||||
TimeRange adjusted_range = node->InputTimeAdjustment(input, i, range);
|
int sz = node->InputArraySize(input);
|
||||||
|
for (int i=0; i<sz; i++) {
|
||||||
if (node->IsInputConnected(input, i)) {
|
ProcessInputElement(array_tbl, node, input, i, range);
|
||||||
Node *output = node->GetConnectedOutput(input, i);
|
}
|
||||||
sub_tbl = GenerateTable(output, adjusted_range, node);
|
} else if (a.mode() == Node::ActiveElements::kSpecified) {
|
||||||
} else {
|
for (int ele : a.elements()) {
|
||||||
QVariant input_value = node->GetValueAtTime(input, adjusted_range.in(), i);
|
ProcessInputElement(array_tbl, node, input, ele, range);
|
||||||
sub_tbl.Push(node->GetInputDataType(input), input_value, node);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,6 +237,20 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NodeTraverser::ProcessInputElement(NodeValueTableArray &array_tbl, const Node *node, const QString &input, int element, const TimeRange &range)
|
||||||
|
{
|
||||||
|
NodeValueTable& sub_tbl = array_tbl[element];
|
||||||
|
TimeRange adjusted_range = node->InputTimeAdjustment(input, element, range);
|
||||||
|
|
||||||
|
if (node->IsInputConnected(input, element)) {
|
||||||
|
Node *output = node->GetConnectedOutput(input, element);
|
||||||
|
sub_tbl = GenerateTable(output, adjusted_range, node);
|
||||||
|
} else {
|
||||||
|
QVariant input_value = node->GetValueAtTime(input, adjusted_range.in(), element);
|
||||||
|
sub_tbl.Push(node->GetInputDataType(input), input_value, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NodeTraverser::NodeTraverser() :
|
NodeTraverser::NodeTraverser() :
|
||||||
cancel_(nullptr),
|
cancel_(nullptr),
|
||||||
transform_(nullptr),
|
transform_(nullptr),
|
||||||
@@ -262,12 +275,6 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& rang
|
|||||||
// NOTE: Times how long a node takes to process, useful for profiling.
|
// NOTE: Times how long a node takes to process, useful for profiling.
|
||||||
//GTTTime gtt(n);Q_UNUSED(gtt);
|
//GTTTime gtt(n);Q_UNUSED(gtt);
|
||||||
|
|
||||||
const Track* track = dynamic_cast<const Track*>(n);
|
|
||||||
if (track) {
|
|
||||||
// If the range is not wholly contained in this Block, we'll need to do some extra processing
|
|
||||||
return GenerateBlockTable(track, range);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use table cache to skip processing where available
|
// Use table cache to skip processing where available
|
||||||
if (value_cache_.contains(n)) {
|
if (value_cache_.contains(n)) {
|
||||||
QHash<TimeRange, NodeValueTable> &node_value_map = value_cache_[n];
|
QHash<TimeRange, NodeValueTable> &node_value_map = value_cache_[n];
|
||||||
@@ -297,7 +304,7 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& rang
|
|||||||
table = database.Merge();
|
table = database.Merge();
|
||||||
|
|
||||||
// By this point, the node should have all the inputs it needs to render correctly
|
// By this point, the node should have all the inputs it needs to render correctly
|
||||||
NodeGlobals globals = GenerateGlobals(video_params_, range);
|
NodeGlobals globals = GenerateGlobals(video_params_, audio_params_, range);
|
||||||
n->Value(row, globals, &table);
|
n->Value(row, globals, &table);
|
||||||
|
|
||||||
// `transform_now_` is the next node in the path that needs to be traversed. It only ever goes
|
// `transform_now_` is the next node in the path that needs to be traversed. It only ever goes
|
||||||
@@ -331,22 +338,6 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& rang
|
|||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeValueTable NodeTraverser::GenerateBlockTable(const Track *track, const TimeRange &range)
|
|
||||||
{
|
|
||||||
// By default, just follow the in point
|
|
||||||
Block* active_block = track->BlockAtTime(range.in());
|
|
||||||
|
|
||||||
NodeValueTable table;
|
|
||||||
|
|
||||||
if (active_block) {
|
|
||||||
block_stack_.push_back(active_block);
|
|
||||||
table = GenerateTable(active_block, Track::TransformRangeForBlock(active_block, range), track);
|
|
||||||
block_stack_.pop_back();
|
|
||||||
}
|
|
||||||
|
|
||||||
return table;
|
|
||||||
}
|
|
||||||
|
|
||||||
TexturePtr NodeTraverser::ProcessVideoCacheJob(const CacheJob *val)
|
TexturePtr NodeTraverser::ProcessVideoCacheJob(const CacheJob *val)
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public:
|
|||||||
|
|
||||||
NodeValueTable GenerateTable(const Node *n, const TimeRange &range, const Node *next_node = nullptr);
|
NodeValueTable GenerateTable(const Node *n, const TimeRange &range, const Node *next_node = nullptr);
|
||||||
|
|
||||||
NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range);
|
virtual NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range);
|
||||||
|
|
||||||
NodeValueRow GenerateRow(NodeValueDatabase *database, const Node *node, const TimeRange &range);
|
NodeValueRow GenerateRow(NodeValueDatabase *database, const Node *node, const TimeRange &range);
|
||||||
NodeValueRow GenerateRow(const Node *node, const TimeRange &range);
|
NodeValueRow GenerateRow(const Node *node, const TimeRange &range);
|
||||||
@@ -54,10 +54,10 @@ public:
|
|||||||
|
|
||||||
void Transform(QTransform *transform, const Node *start, const Node *end, const TimeRange &range);
|
void Transform(QTransform *transform, const Node *start, const Node *end, const TimeRange &range);
|
||||||
|
|
||||||
static NodeGlobals GenerateGlobals(const VideoParams ¶ms, const TimeRange &time);
|
static NodeGlobals GenerateGlobals(const VideoParams &vparams, const AudioParams &aparams, const TimeRange &time);
|
||||||
static NodeGlobals GenerateGlobals(const VideoParams ¶ms, const rational &time)
|
static NodeGlobals GenerateGlobals(const VideoParams &vparams, const AudioParams &aparams, const rational &time)
|
||||||
{
|
{
|
||||||
return GenerateGlobals(params, TimeRange(time, time + params.frame_rate_as_time_base()));
|
return GenerateGlobals(vparams, aparams, TimeRange(time, time + vparams.frame_rate_as_time_base()));
|
||||||
}
|
}
|
||||||
|
|
||||||
const VideoParams& GetCacheVideoParams() const
|
const VideoParams& GetCacheVideoParams() const
|
||||||
@@ -83,7 +83,7 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
NodeValueTable ProcessInput(const Node *node, const QString &input, const TimeRange &range);
|
NodeValueTable ProcessInput(const Node *node, const QString &input, const TimeRange &range);
|
||||||
|
|
||||||
virtual NodeValueTable GenerateBlockTable(const Track *track, const TimeRange& range);
|
void ProcessInputElement(NodeValueTableArray &array_tbl, const Node *node, const QString &input, int element, const TimeRange &range);
|
||||||
|
|
||||||
virtual void ProcessVideoFootage(TexturePtr destination, const FootageJob *stream, const rational &input_time){}
|
virtual void ProcessVideoFootage(TexturePtr destination, const FootageJob *stream, const rational &input_time){}
|
||||||
|
|
||||||
|
|||||||
+6
-2
@@ -31,11 +31,15 @@
|
|||||||
#include "node/splitvalue.h"
|
#include "node/splitvalue.h"
|
||||||
#include "render/color.h"
|
#include "render/color.h"
|
||||||
#include "render/texture.h"
|
#include "render/texture.h"
|
||||||
#include "undo/undocommand.h"
|
|
||||||
|
|
||||||
namespace olive {
|
namespace olive {
|
||||||
|
|
||||||
class Node;
|
class Node;
|
||||||
|
class NodeValue;
|
||||||
|
class NodeValueTable;
|
||||||
|
|
||||||
|
using NodeValueArray = std::map<int, NodeValue>;
|
||||||
|
using NodeValueTableArray = std::map<int, NodeValueTable>;
|
||||||
|
|
||||||
class NodeValue
|
class NodeValue
|
||||||
{
|
{
|
||||||
@@ -344,7 +348,7 @@ public:
|
|||||||
QVector3D toVec3() const { return value<QVector3D>(); }
|
QVector3D toVec3() const { return value<QVector3D>(); }
|
||||||
QVector4D toVec4() const { return value<QVector4D>(); }
|
QVector4D toVec4() const { return value<QVector4D>(); }
|
||||||
Bezier toBezier() const { return value<Bezier>(); }
|
Bezier toBezier() const { return value<Bezier>(); }
|
||||||
QVector<NodeValue> toArray() const { return value<QVector<NodeValue> >(); }
|
NodeValueArray toArray() const { return value<NodeValueArray>(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Type type_;
|
Type type_;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
add_subdirectory(audiomonitor)
|
add_subdirectory(audiomonitor)
|
||||||
add_subdirectory(curve)
|
add_subdirectory(curve)
|
||||||
add_subdirectory(footageviewer)
|
add_subdirectory(footageviewer)
|
||||||
|
add_subdirectory(multicam)
|
||||||
add_subdirectory(node)
|
add_subdirectory(node)
|
||||||
add_subdirectory(param)
|
add_subdirectory(param)
|
||||||
add_subdirectory(pixelsampler)
|
add_subdirectory(pixelsampler)
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Olive - Non-Linear Video Editor
|
||||||
|
# Copyright (C) 2022 Olive Team
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
set(OLIVE_SOURCES
|
||||||
|
${OLIVE_SOURCES}
|
||||||
|
panel/multicam/multicampanel.h
|
||||||
|
panel/multicam/multicampanel.cpp
|
||||||
|
PARENT_SCOPE
|
||||||
|
)
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#include "multicampanel.h"
|
||||||
|
|
||||||
|
namespace olive {
|
||||||
|
|
||||||
|
#define super TimeBasedPanel
|
||||||
|
|
||||||
|
MulticamPanel::MulticamPanel(QWidget *parent) :
|
||||||
|
super(QStringLiteral("MultiCamPanel"), parent)
|
||||||
|
{
|
||||||
|
SetTimeBasedWidget(new MulticamWidget());
|
||||||
|
|
||||||
|
Retranslate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MulticamPanel::Retranslate()
|
||||||
|
{
|
||||||
|
super::Retranslate();
|
||||||
|
|
||||||
|
SetTitle(tr("Multi-Cam"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef MULTICAMPANEL_H
|
||||||
|
#define MULTICAMPANEL_H
|
||||||
|
|
||||||
|
#include "panel/viewer/viewerbase.h"
|
||||||
|
#include "widget/multicam/multicamwidget.h"
|
||||||
|
|
||||||
|
namespace olive {
|
||||||
|
|
||||||
|
class MulticamPanel : public TimeBasedPanel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
MulticamPanel(QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
MulticamWidget *GetMulticamWidget() const { return static_cast<MulticamWidget *>(GetTimeBasedWidget()); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void Retranslate() override;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // MULTICAMPANEL_H
|
||||||
@@ -106,6 +106,8 @@ public:
|
|||||||
|
|
||||||
virtual void Paste() override;
|
virtual void Paste() override;
|
||||||
|
|
||||||
|
TimeBasedWidget* GetTimeBasedWidget() const { return widget_; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void SetTimebase(const rational& timebase);
|
void SetTimebase(const rational& timebase);
|
||||||
|
|
||||||
@@ -127,11 +129,6 @@ signals:
|
|||||||
void ShuttleRightRequested();
|
void ShuttleRightRequested();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
TimeBasedWidget* GetTimeBasedWidget() const
|
|
||||||
{
|
|
||||||
return widget_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetTimeBasedWidget(TimeBasedWidget* widget);
|
void SetTimeBasedWidget(TimeBasedWidget* widget);
|
||||||
|
|
||||||
virtual void Retranslate() override;
|
virtual void Retranslate() override;
|
||||||
|
|||||||
@@ -98,6 +98,11 @@ public:
|
|||||||
timeline_widget()->ShowSpeedDurationDialogForSelectedClips();
|
timeline_widget()->ShowSpeedDurationDialogForSelectedClips();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NestSelectedClips()
|
||||||
|
{
|
||||||
|
timeline_widget()->NestSelectedClips();
|
||||||
|
}
|
||||||
|
|
||||||
void InsertFootageAtPlayhead(const QVector<ViewerOutput *> &footage);
|
void InsertFootageAtPlayhead(const QVector<ViewerOutput *> &footage);
|
||||||
|
|
||||||
void OverwriteFootageAtPlayhead(const QVector<ViewerOutput *> &footage);
|
void OverwriteFootageAtPlayhead(const QVector<ViewerOutput *> &footage);
|
||||||
@@ -107,6 +112,11 @@ public:
|
|||||||
return timeline_widget()->GetSelectedBlocks();
|
return timeline_widget()->GetSelectedBlocks();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Sequence *GetSequence() const
|
||||||
|
{
|
||||||
|
return dynamic_cast<Sequence*>(GetConnectedViewer());
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void Retranslate() override;
|
virtual void Retranslate() override;
|
||||||
|
|
||||||
|
|||||||
@@ -24,35 +24,37 @@
|
|||||||
|
|
||||||
namespace olive {
|
namespace olive {
|
||||||
|
|
||||||
|
#define super TimeBasedPanel
|
||||||
|
|
||||||
ViewerPanelBase::ViewerPanelBase(const QString& object_name, QWidget *parent) :
|
ViewerPanelBase::ViewerPanelBase(const QString& object_name, QWidget *parent) :
|
||||||
TimeBasedPanel(object_name, parent)
|
super(object_name, parent)
|
||||||
{
|
{
|
||||||
connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &ViewerPanelBase::FocusedPanelChanged);
|
connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &ViewerPanelBase::FocusedPanelChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewerPanelBase::PlayPause()
|
void ViewerPanelBase::PlayPause()
|
||||||
{
|
{
|
||||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->TogglePlayPause();
|
GetViewerWidget()->TogglePlayPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewerPanelBase::PlayInToOut()
|
void ViewerPanelBase::PlayInToOut()
|
||||||
{
|
{
|
||||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->Play(true);
|
GetViewerWidget()->Play(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewerPanelBase::ShuttleLeft()
|
void ViewerPanelBase::ShuttleLeft()
|
||||||
{
|
{
|
||||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->ShuttleLeft();
|
GetViewerWidget()->ShuttleLeft();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewerPanelBase::ShuttleStop()
|
void ViewerPanelBase::ShuttleStop()
|
||||||
{
|
{
|
||||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->ShuttleStop();
|
GetViewerWidget()->ShuttleStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewerPanelBase::ShuttleRight()
|
void ViewerPanelBase::ShuttleRight()
|
||||||
{
|
{
|
||||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->ShuttleRight();
|
GetViewerWidget()->ShuttleRight();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewerPanelBase::ConnectTimeBasedPanel(TimeBasedPanel *panel)
|
void ViewerPanelBase::ConnectTimeBasedPanel(TimeBasedPanel *panel)
|
||||||
@@ -75,22 +77,22 @@ void ViewerPanelBase::DisconnectTimeBasedPanel(TimeBasedPanel *panel)
|
|||||||
|
|
||||||
void ViewerPanelBase::SetFullScreen(QScreen *screen)
|
void ViewerPanelBase::SetFullScreen(QScreen *screen)
|
||||||
{
|
{
|
||||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->SetFullScreen(screen);
|
GetViewerWidget()->SetFullScreen(screen);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewerPanelBase::SetGizmos(Node *node)
|
void ViewerPanelBase::SetGizmos(Node *node)
|
||||||
{
|
{
|
||||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->SetGizmos(node);
|
GetViewerWidget()->SetGizmos(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewerPanelBase::CacheEntireSequence()
|
void ViewerPanelBase::CacheEntireSequence()
|
||||||
{
|
{
|
||||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->CacheEntireSequence();
|
GetViewerWidget()->CacheEntireSequence();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewerPanelBase::CacheSequenceInOut()
|
void ViewerPanelBase::CacheSequenceInOut()
|
||||||
{
|
{
|
||||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->CacheSequenceInOut();
|
GetViewerWidget()->CacheSequenceInOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewerPanelBase::SetViewerWidget(ViewerWidget *vw)
|
void ViewerPanelBase::SetViewerWidget(ViewerWidget *vw)
|
||||||
@@ -104,9 +106,11 @@ void ViewerPanelBase::SetViewerWidget(ViewerWidget *vw)
|
|||||||
|
|
||||||
void ViewerPanelBase::FocusedPanelChanged(PanelWidget *panel)
|
void ViewerPanelBase::FocusedPanelChanged(PanelWidget *panel)
|
||||||
{
|
{
|
||||||
auto vw = static_cast<ViewerWidget*>(GetTimeBasedWidget());
|
if (dynamic_cast<ViewerPanelBase*>(panel)) {
|
||||||
if (vw->IsPlaying() && panel != this) {
|
auto vw = GetViewerWidget();
|
||||||
vw->Pause();
|
if (vw->IsPlaying() && panel != this) {
|
||||||
|
vw->Pause();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ class ViewerPanelBase : public TimeBasedPanel
|
|||||||
public:
|
public:
|
||||||
ViewerPanelBase(const QString& object_name, QWidget* parent = nullptr);
|
ViewerPanelBase(const QString& object_name, QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
ViewerWidget *GetViewerWidget() const
|
||||||
|
{
|
||||||
|
return static_cast<ViewerWidget*>(GetTimeBasedWidget());
|
||||||
|
}
|
||||||
|
|
||||||
virtual void PlayPause() override;
|
virtual void PlayPause() override;
|
||||||
|
|
||||||
virtual void PlayInToOut() override;
|
virtual void PlayInToOut() override;
|
||||||
@@ -54,12 +59,32 @@ public:
|
|||||||
|
|
||||||
ColorManager *GetColorManager()
|
ColorManager *GetColorManager()
|
||||||
{
|
{
|
||||||
return static_cast<ViewerWidget*>(GetTimeBasedWidget())->color_manager();
|
return GetViewerWidget()->color_manager();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateTextureFromNode()
|
void UpdateTextureFromNode()
|
||||||
{
|
{
|
||||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->UpdateTextureFromNode();
|
GetViewerWidget()->UpdateTextureFromNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddPlaybackDevice(ViewerDisplayWidget *vw)
|
||||||
|
{
|
||||||
|
GetViewerWidget()->AddPlaybackDevice(vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetTimelineSelectedBlocks(const QVector<Block*> &b)
|
||||||
|
{
|
||||||
|
GetViewerWidget()->SetTimelineSelectedBlocks(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetNodeViewSelections(const QVector<Node*> &n)
|
||||||
|
{
|
||||||
|
GetViewerWidget()->SetNodeViewSelections(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConnectMulticamWidget(MulticamWidget *p)
|
||||||
|
{
|
||||||
|
GetViewerWidget()->ConnectMulticamWidget(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
@@ -71,7 +96,7 @@ public slots:
|
|||||||
|
|
||||||
void RequestStartEditingText()
|
void RequestStartEditingText()
|
||||||
{
|
{
|
||||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->RequestStartEditingText();
|
GetViewerWidget()->RequestStartEditingText();
|
||||||
}
|
}
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|||||||
@@ -22,113 +22,94 @@
|
|||||||
|
|
||||||
namespace olive {
|
namespace olive {
|
||||||
|
|
||||||
|
#define super PlaybackCache
|
||||||
|
|
||||||
AudioWaveformCache::AudioWaveformCache(QObject *parent) :
|
AudioWaveformCache::AudioWaveformCache(QObject *parent) :
|
||||||
PlaybackCache{parent}
|
super{parent}
|
||||||
{
|
{
|
||||||
|
waveforms_ = std::make_shared<AudioVisualWaveform>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioWaveformCache::WriteWaveform(const TimeRange &range, const TimeRangeList &valid_ranges, const AudioVisualWaveform *waveform)
|
void AudioWaveformCache::WriteWaveform(const TimeRange &range, const TimeRangeList &valid_ranges, const AudioVisualWaveform *waveform)
|
||||||
{
|
{
|
||||||
// Write each valid range to the segments
|
// Write each valid range to the segments
|
||||||
foreach (const TimeRange& r, valid_ranges) {
|
foreach (const TimeRange& r, valid_ranges) {
|
||||||
#ifdef AVW_USE_LIST
|
|
||||||
// Write visual
|
|
||||||
TimeRangeList::util_remove(&waveforms_, r);
|
|
||||||
|
|
||||||
if (waveform) {
|
if (waveform) {
|
||||||
TimeRangeWithWaveform wv = r;
|
waveforms_->OverwriteSums(*waveform, r.in(), r.in() - range.in(), r.length());
|
||||||
rational local_start = r.in() - range.in();
|
|
||||||
if (local_start != 0) {
|
|
||||||
wv.waveform = waveform->Mid(local_start, r.length());
|
|
||||||
} else {
|
|
||||||
wv.waveform = *waveform;
|
|
||||||
}
|
|
||||||
waveforms_.append(wv);
|
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
if (waveform) {
|
|
||||||
waveforms_.OverwriteSums(*waveform, r.in(), r.in() - range.in(), r.length());
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Validate(r);
|
Validate(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DrawSubRect(QPainter *painter, const QRect &rect, const double &scale, const TimeRange &wave_range, const AudioVisualWaveform &waveform, const TimeRange &subrange)
|
||||||
|
{
|
||||||
|
// Find start time of passthrough
|
||||||
|
TimeRange intersect = wave_range.Intersected(subrange);
|
||||||
|
|
||||||
|
// Create new rect that starts at the offset of pass_start from start_time
|
||||||
|
// Set rect width to either length of passthrough or until the end
|
||||||
|
QRect pass_rect(rect.x() + (intersect.in() - wave_range.in()).toDouble() * scale,
|
||||||
|
rect.y(),
|
||||||
|
intersect.length().toDouble() * scale,
|
||||||
|
rect.height());
|
||||||
|
|
||||||
|
// Draw waveform with this info
|
||||||
|
AudioVisualWaveform::DrawWaveform(painter, pass_rect, scale, waveform, intersect.in());
|
||||||
|
}
|
||||||
|
|
||||||
void AudioWaveformCache::Draw(QPainter *painter, const QRect &rect, const double &scale, const rational &start_time) const
|
void AudioWaveformCache::Draw(QPainter *painter, const QRect &rect, const double &scale, const rational &start_time) const
|
||||||
{
|
{
|
||||||
rational end = start_time + rational::fromDouble(rect.width() / scale);
|
if (!passthroughs_.empty()) {
|
||||||
TimeRange draw_range(start_time, end);
|
TimeRange wave_range(start_time, start_time + rational::fromDouble(rect.width() / scale));
|
||||||
|
TimeRangeList draw_range = {wave_range};
|
||||||
|
for (const WaveformPassthrough &p : passthroughs_) {
|
||||||
|
if (draw_range.OverlapsWith(p, true, false)) {
|
||||||
|
DrawSubRect(painter, rect, scale, wave_range, *p.waveform, p);
|
||||||
|
|
||||||
#ifdef AVW_USE_LIST
|
// Remove this range
|
||||||
foreach (const TimeRangeWithWaveform &wv, waveforms_) {
|
draw_range.remove(p);
|
||||||
if (wv.OverlapsWith(draw_range)) {
|
}
|
||||||
rational substart = std::max(wv.in(), draw_range.in());
|
|
||||||
rational subend = std::min(wv.out(), draw_range.out());
|
|
||||||
|
|
||||||
QRect subrect = rect;
|
|
||||||
subrect.setLeft(subrect.left() + (substart - draw_range.in()).toDouble()*scale);
|
|
||||||
subrect.setWidth((subend - substart).toDouble()*scale);
|
|
||||||
|
|
||||||
rational local_start = substart - wv.in();
|
|
||||||
AudioVisualWaveform::DrawWaveform(painter, subrect, scale, wv.waveform, local_start);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const TimeRange &r : draw_range) {
|
||||||
|
DrawSubRect(painter, rect, scale, wave_range, *waveforms_, r);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AudioVisualWaveform::DrawWaveform(painter, rect, scale, *waveforms_, start_time);
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
AudioVisualWaveform::DrawWaveform(painter, rect, scale, waveforms_, start_time);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioVisualWaveform::Sample AudioWaveformCache::GetSummaryFromTime(const rational &start, const rational &length) const
|
AudioVisualWaveform::Sample AudioWaveformCache::GetSummaryFromTime(const rational &start, const rational &length) const
|
||||||
{
|
{
|
||||||
#ifdef AVW_USE_LIST
|
return waveforms_->GetSummaryFromTime(start, length);
|
||||||
QMap<rational, AudioVisualWaveform::Sample> sample;
|
|
||||||
|
|
||||||
TimeRange acquire(start, start+length);
|
|
||||||
foreach (const TimeRangeWithWaveform &wv, waveforms_) {
|
|
||||||
if (wv.OverlapsWith(acquire)) {
|
|
||||||
TimeRange this_range = wv.Intersected(acquire);
|
|
||||||
auto sum = wv.waveform.GetSummaryFromTime(this_range.in() - wv.in(), this_range.length());
|
|
||||||
sample.insert(this_range.in(), sum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioVisualWaveform::Sample result;
|
|
||||||
|
|
||||||
for (auto it=sample.cbegin(); it!=sample.cend(); it++) {
|
|
||||||
result.insert(result.end(), it.value().begin(), it.value().end());
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
#else
|
|
||||||
return waveforms_.GetSummaryFromTime(start, length);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rational AudioWaveformCache::length() const
|
rational AudioWaveformCache::length() const
|
||||||
{
|
{
|
||||||
#ifdef AVW_USE_LIST
|
return waveforms_->length();
|
||||||
rational len = 0;
|
|
||||||
|
|
||||||
foreach (const TimeRangeWithWaveform &wv, waveforms_) {
|
|
||||||
len = std::max(len, wv.out());
|
|
||||||
}
|
|
||||||
|
|
||||||
return len;
|
|
||||||
#else
|
|
||||||
return waveforms_.length();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioWaveformCache::SetPassthrough(PlaybackCache *cache)
|
void AudioWaveformCache::SetPassthrough(PlaybackCache *cache)
|
||||||
{
|
{
|
||||||
AudioWaveformCache *c = static_cast<AudioWaveformCache*>(cache);
|
AudioWaveformCache *c = static_cast<AudioWaveformCache*>(cache);
|
||||||
waveforms_ = c->waveforms_;
|
|
||||||
for (const TimeRange &r : c->GetValidatedRanges()) {
|
for (const TimeRange &r : c->GetValidatedRanges()) {
|
||||||
Validate(r);
|
WaveformPassthrough t = r;
|
||||||
|
t.waveform = c->waveforms_;
|
||||||
|
passthroughs_.append(t);
|
||||||
}
|
}
|
||||||
|
passthroughs_.append(c->passthroughs_);
|
||||||
|
|
||||||
SetParameters(c->GetParameters());
|
SetParameters(c->GetParameters());
|
||||||
SetSavingEnabled(c->IsSavingEnabled());
|
SetSavingEnabled(c->IsSavingEnabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioWaveformCache::InvalidateEvent(const TimeRange& range)
|
||||||
|
{
|
||||||
|
TimeRangeList::util_remove(&passthroughs_, range);
|
||||||
|
|
||||||
|
super::InvalidateEvent(range);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,6 @@
|
|||||||
#include "audio/audiovisualwaveform.h"
|
#include "audio/audiovisualwaveform.h"
|
||||||
#include "playbackcache.h"
|
#include "playbackcache.h"
|
||||||
|
|
||||||
//#define AVW_USE_LIST
|
|
||||||
|
|
||||||
namespace olive {
|
namespace olive {
|
||||||
|
|
||||||
class AudioWaveformCache : public PlaybackCache
|
class AudioWaveformCache : public PlaybackCache
|
||||||
@@ -40,7 +38,7 @@ public:
|
|||||||
void SetParameters(const AudioParams &p)
|
void SetParameters(const AudioParams &p)
|
||||||
{
|
{
|
||||||
params_ = p;
|
params_ = p;
|
||||||
waveforms_.set_channel_count(p.channel_count());
|
waveforms_->set_channel_count(p.channel_count());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Draw(QPainter* painter, const QRect &rect, const double &scale, const rational &start_time) const;
|
void Draw(QPainter* painter, const QRect &rect, const double &scale, const rational &start_time) const;
|
||||||
@@ -51,45 +49,28 @@ public:
|
|||||||
|
|
||||||
virtual void SetPassthrough(PlaybackCache *cache) override;
|
virtual void SetPassthrough(PlaybackCache *cache) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void InvalidateEvent(const TimeRange& range) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#ifdef AVW_USE_LIST
|
using WaveformPtr = std::shared_ptr<AudioVisualWaveform>;
|
||||||
class TimeRangeWithWaveform : public TimeRange
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
TimeRangeWithWaveform() = default;
|
|
||||||
TimeRangeWithWaveform(const TimeRange &r) :
|
|
||||||
TimeRange(r)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_in(const rational& in)
|
WaveformPtr waveforms_;
|
||||||
{
|
|
||||||
waveform.TrimIn(in - this->in());
|
|
||||||
TimeRange::set_in(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_out(const rational& out)
|
|
||||||
{
|
|
||||||
waveform.Resize(out - this->in());
|
|
||||||
TimeRange::set_out(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_range(const rational& in, const rational& out)
|
|
||||||
{
|
|
||||||
waveform.TrimRange(in, out-in);
|
|
||||||
TimeRange::set_range(in, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioVisualWaveform waveform;
|
|
||||||
};
|
|
||||||
|
|
||||||
QVector<TimeRangeWithWaveform> waveforms_;
|
|
||||||
#else
|
|
||||||
AudioVisualWaveform waveforms_;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
AudioParams params_;
|
AudioParams params_;
|
||||||
|
|
||||||
|
class WaveformPassthrough : public TimeRange
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WaveformPassthrough(const TimeRange &r) :
|
||||||
|
TimeRange(r)
|
||||||
|
{}
|
||||||
|
|
||||||
|
WaveformPtr waveform;
|
||||||
|
};
|
||||||
|
|
||||||
|
QVector<WaveformPassthrough> passthroughs_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,6 +223,13 @@ void PlaybackCache::InvalidateAll()
|
|||||||
Invalidate(TimeRange(0, RATIONAL_MAX));
|
Invalidate(TimeRange(0, RATIONAL_MAX));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlaybackCache::Request(const TimeRange &r)
|
||||||
|
{
|
||||||
|
requested_.insert(r);
|
||||||
|
|
||||||
|
emit Requested(r);
|
||||||
|
}
|
||||||
|
|
||||||
void PlaybackCache::Validate(const TimeRange &r, bool signal)
|
void PlaybackCache::Validate(const TimeRange &r, bool signal)
|
||||||
{
|
{
|
||||||
validated_.insert(r);
|
validated_.insert(r);
|
||||||
|
|||||||
@@ -98,15 +98,29 @@ public:
|
|||||||
|
|
||||||
const QVector<Passthrough> &GetPassthroughs() const { return passthroughs_; }
|
const QVector<Passthrough> &GetPassthroughs() const { return passthroughs_; }
|
||||||
|
|
||||||
|
void ClearRequestRange(const olive::TimeRange &r)
|
||||||
|
{
|
||||||
|
requested_.remove(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResignalRequests()
|
||||||
|
{
|
||||||
|
for (const TimeRange &r : requested_) {
|
||||||
|
emit Requested(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void InvalidateAll();
|
void InvalidateAll();
|
||||||
|
|
||||||
|
void Request(const olive::TimeRange &r);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void Invalidated(const olive::TimeRange& r);
|
void Invalidated(const olive::TimeRange& r);
|
||||||
|
|
||||||
void Validated(const olive::TimeRange& r);
|
void Validated(const olive::TimeRange& r);
|
||||||
|
|
||||||
void Request(const olive::TimeRange& r);
|
void Requested(const olive::TimeRange& r);
|
||||||
|
|
||||||
void CancelAll();
|
void CancelAll();
|
||||||
|
|
||||||
@@ -124,6 +138,8 @@ protected:
|
|||||||
private:
|
private:
|
||||||
TimeRangeList validated_;
|
TimeRangeList validated_;
|
||||||
|
|
||||||
|
TimeRangeList requested_;
|
||||||
|
|
||||||
QUuid uuid_;
|
QUuid uuid_;
|
||||||
|
|
||||||
bool saving_enabled_;
|
bool saving_enabled_;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include <QtConcurrent/QtConcurrent>
|
#include <QtConcurrent/QtConcurrent>
|
||||||
|
|
||||||
#include "codec/conformmanager.h"
|
#include "codec/conformmanager.h"
|
||||||
|
#include "node/input/multicam/multicamnode.h"
|
||||||
#include "node/inputdragger.h"
|
#include "node/inputdragger.h"
|
||||||
#include "node/project/project.h"
|
#include "node/project/project.h"
|
||||||
#include "render/diskmanager.h"
|
#include "render/diskmanager.h"
|
||||||
@@ -41,7 +42,9 @@ PreviewAutoCacher::PreviewAutoCacher(QObject *parent) :
|
|||||||
use_custom_range_(false),
|
use_custom_range_(false),
|
||||||
pause_renders_(false),
|
pause_renders_(false),
|
||||||
single_frame_render_(nullptr),
|
single_frame_render_(nullptr),
|
||||||
display_color_processor_(nullptr)
|
display_color_processor_(nullptr),
|
||||||
|
multicam_(nullptr),
|
||||||
|
ignore_cache_requests_(false)
|
||||||
{
|
{
|
||||||
// Set defaults
|
// Set defaults
|
||||||
SetPlayhead(0);
|
SetPlayhead(0);
|
||||||
@@ -62,6 +65,11 @@ PreviewAutoCacher::~PreviewAutoCacher()
|
|||||||
}
|
}
|
||||||
|
|
||||||
RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, bool dry)
|
RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, bool dry)
|
||||||
|
{
|
||||||
|
return GetSingleFrame(viewer_node_->GetConnectedTextureOutput(), t, dry);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderTicketPtr PreviewAutoCacher::GetSingleFrame(Node *n, const rational &t, bool dry)
|
||||||
{
|
{
|
||||||
// If we have a single frame render queued (but not yet sent to the RenderManager), cancel it now
|
// If we have a single frame render queued (but not yet sent to the RenderManager), cancel it now
|
||||||
CancelQueuedSingleFrameRender();
|
CancelQueuedSingleFrameRender();
|
||||||
@@ -71,6 +79,7 @@ RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, bool dry)
|
|||||||
sfr->Start();
|
sfr->Start();
|
||||||
sfr->setProperty("time", QVariant::fromValue(t));
|
sfr->setProperty("time", QVariant::fromValue(t));
|
||||||
sfr->setProperty("dry", dry);
|
sfr->setProperty("dry", dry);
|
||||||
|
sfr->setProperty("node", Node::PtrToValue(n));
|
||||||
|
|
||||||
// Queue it and try to render
|
// Queue it and try to render
|
||||||
single_frame_render_ = sfr;
|
single_frame_render_ = sfr;
|
||||||
@@ -100,6 +109,8 @@ void PreviewAutoCacher::VideoInvalidatedFromCache(const TimeRange &range)
|
|||||||
{
|
{
|
||||||
PlaybackCache *cache = static_cast<PlaybackCache*>(sender());
|
PlaybackCache *cache = static_cast<PlaybackCache*>(sender());
|
||||||
|
|
||||||
|
cache->ClearRequestRange(range);
|
||||||
|
|
||||||
VideoInvalidatedFromNode(cache, range);
|
VideoInvalidatedFromNode(cache, range);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +118,8 @@ void PreviewAutoCacher::AudioInvalidatedFromCache(const TimeRange &range)
|
|||||||
{
|
{
|
||||||
PlaybackCache *cache = static_cast<PlaybackCache*>(sender());
|
PlaybackCache *cache = static_cast<PlaybackCache*>(sender());
|
||||||
|
|
||||||
|
cache->ClearRequestRange(range);
|
||||||
|
|
||||||
AudioInvalidatedFromNode(cache, range);
|
AudioInvalidatedFromNode(cache, range);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,6 +220,7 @@ void PreviewAutoCacher::VideoRendered()
|
|||||||
QVector<RenderTicketPtr> tickets = video_immediate_passthroughs_.take(watcher);
|
QVector<RenderTicketPtr> tickets = video_immediate_passthroughs_.take(watcher);
|
||||||
foreach (RenderTicketPtr t, tickets) {
|
foreach (RenderTicketPtr t, tickets) {
|
||||||
if (watcher->HasResult()) {
|
if (watcher->HasResult()) {
|
||||||
|
t->setProperty("multicam_output", watcher->GetTicket()->property("multicam_output"));
|
||||||
t->Finish(watcher->Get());
|
t->Finish(watcher->Get());
|
||||||
} else {
|
} else {
|
||||||
t->Finish();
|
t->Finish();
|
||||||
@@ -240,7 +254,10 @@ void PreviewAutoCacher::VideoRendered()
|
|||||||
void PreviewAutoCacher::ProcessUpdateQueue()
|
void PreviewAutoCacher::ProcessUpdateQueue()
|
||||||
{
|
{
|
||||||
// Iterate everything that happened to the graph and do the same thing on our end
|
// Iterate everything that happened to the graph and do the same thing on our end
|
||||||
foreach (const QueuedJob& job, graph_update_queue_) {
|
while (!graph_update_queue_.empty()) {
|
||||||
|
QueuedJob job = graph_update_queue_.front();
|
||||||
|
graph_update_queue_.pop_front();
|
||||||
|
|
||||||
switch (job.type) {
|
switch (job.type) {
|
||||||
case QueuedJob::kNodeAdded:
|
case QueuedJob::kNodeAdded:
|
||||||
AddNode(job.node);
|
AddNode(job.node);
|
||||||
@@ -262,7 +279,6 @@ void PreviewAutoCacher::ProcessUpdateQueue()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
graph_update_queue_.clear();
|
|
||||||
|
|
||||||
// Indicate that we have synchronized to this point, which is compared with the graph change
|
// Indicate that we have synchronized to this point, which is compared with the graph change
|
||||||
// time to see if our copied graph is up to date
|
// time to see if our copied graph is up to date
|
||||||
@@ -362,28 +378,30 @@ void PreviewAutoCacher::InsertIntoCopyMap(Node *node, Node *copy)
|
|||||||
Node::CopyInputs(node, copy, false);
|
Node::CopyInputs(node, copy, false);
|
||||||
|
|
||||||
// Connect to node's cache
|
// Connect to node's cache
|
||||||
ConnectToNodeCache(node);
|
if (!ignore_cache_requests_) {
|
||||||
|
ConnectToNodeCache(node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreviewAutoCacher::ConnectToNodeCache(Node *node)
|
void PreviewAutoCacher::ConnectToNodeCache(Node *node)
|
||||||
{
|
{
|
||||||
connect(node->video_frame_cache(),
|
connect(node->video_frame_cache(),
|
||||||
&PlaybackCache::Request,
|
&PlaybackCache::Requested,
|
||||||
this,
|
this,
|
||||||
&PreviewAutoCacher::VideoInvalidatedFromCache);
|
&PreviewAutoCacher::VideoInvalidatedFromCache);
|
||||||
|
|
||||||
connect(node->thumbnail_cache(),
|
connect(node->thumbnail_cache(),
|
||||||
&PlaybackCache::Request,
|
&PlaybackCache::Requested,
|
||||||
this,
|
this,
|
||||||
&PreviewAutoCacher::VideoInvalidatedFromCache);
|
&PreviewAutoCacher::VideoInvalidatedFromCache);
|
||||||
|
|
||||||
connect(node->audio_playback_cache(),
|
connect(node->audio_playback_cache(),
|
||||||
&PlaybackCache::Request,
|
&PlaybackCache::Requested,
|
||||||
this,
|
this,
|
||||||
&PreviewAutoCacher::AudioInvalidatedFromCache);
|
&PreviewAutoCacher::AudioInvalidatedFromCache);
|
||||||
|
|
||||||
connect(node->waveform_cache(),
|
connect(node->waveform_cache(),
|
||||||
&PlaybackCache::Request,
|
&PlaybackCache::Requested,
|
||||||
this,
|
this,
|
||||||
&PreviewAutoCacher::AudioInvalidatedFromCache);
|
&PreviewAutoCacher::AudioInvalidatedFromCache);
|
||||||
|
|
||||||
@@ -396,27 +414,32 @@ void PreviewAutoCacher::ConnectToNodeCache(Node *node)
|
|||||||
&PlaybackCache::CancelAll,
|
&PlaybackCache::CancelAll,
|
||||||
this,
|
this,
|
||||||
&PreviewAutoCacher::CancelForCache);
|
&PreviewAutoCacher::CancelForCache);
|
||||||
|
|
||||||
|
node->video_frame_cache()->ResignalRequests();
|
||||||
|
node->thumbnail_cache()->ResignalRequests();
|
||||||
|
node->audio_playback_cache()->ResignalRequests();
|
||||||
|
node->waveform_cache()->ResignalRequests();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreviewAutoCacher::DisconnectFromNodeCache(Node *node)
|
void PreviewAutoCacher::DisconnectFromNodeCache(Node *node)
|
||||||
{
|
{
|
||||||
disconnect(node->video_frame_cache(),
|
disconnect(node->video_frame_cache(),
|
||||||
&PlaybackCache::Request,
|
&PlaybackCache::Requested,
|
||||||
this,
|
this,
|
||||||
&PreviewAutoCacher::VideoInvalidatedFromCache);
|
&PreviewAutoCacher::VideoInvalidatedFromCache);
|
||||||
|
|
||||||
disconnect(node->thumbnail_cache(),
|
disconnect(node->thumbnail_cache(),
|
||||||
&PlaybackCache::Request,
|
&PlaybackCache::Requested,
|
||||||
this,
|
this,
|
||||||
&PreviewAutoCacher::VideoInvalidatedFromCache);
|
&PreviewAutoCacher::VideoInvalidatedFromCache);
|
||||||
|
|
||||||
disconnect(node->audio_playback_cache(),
|
disconnect(node->audio_playback_cache(),
|
||||||
&PlaybackCache::Request,
|
&PlaybackCache::Requested,
|
||||||
this,
|
this,
|
||||||
&PreviewAutoCacher::AudioInvalidatedFromCache);
|
&PreviewAutoCacher::AudioInvalidatedFromCache);
|
||||||
|
|
||||||
disconnect(node->waveform_cache(),
|
disconnect(node->waveform_cache(),
|
||||||
&PlaybackCache::Request,
|
&PlaybackCache::Requested,
|
||||||
this,
|
this,
|
||||||
&PreviewAutoCacher::AudioInvalidatedFromCache);
|
&PreviewAutoCacher::AudioInvalidatedFromCache);
|
||||||
|
|
||||||
@@ -466,6 +489,8 @@ void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeR
|
|||||||
using_tb = viewer_node_->GetVideoParams().frame_rate_as_time_base();
|
using_tb = viewer_node_->GetVideoParams().frame_rate_as_time_base();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cache->ClearRequestRange(range);
|
||||||
|
|
||||||
TimeRangeListFrameIterator iterator({range}, using_tb);
|
TimeRangeListFrameIterator iterator({range}, using_tb);
|
||||||
pending_video_jobs_.push_back({node, cache, range, iterator});
|
pending_video_jobs_.push_back({node, cache, range, iterator});
|
||||||
video_cache_data_[cache].job_tracker.insert(TimeRange(iterator.Snap(range.in()), range.out()), graph_changed_time_);
|
video_cache_data_[cache].job_tracker.insert(TimeRange(iterator.Snap(range.in()), range.out()), graph_changed_time_);
|
||||||
@@ -475,6 +500,9 @@ void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeR
|
|||||||
void PreviewAutoCacher::StartCachingAudioRange(PlaybackCache *cache, const TimeRange &range)
|
void PreviewAutoCacher::StartCachingAudioRange(PlaybackCache *cache, const TimeRange &range)
|
||||||
{
|
{
|
||||||
Node *node = cache->parent();
|
Node *node = cache->parent();
|
||||||
|
|
||||||
|
cache->ClearRequestRange(range);
|
||||||
|
|
||||||
pending_audio_jobs_.push_back({node, cache, range});
|
pending_audio_jobs_.push_back({node, cache, range});
|
||||||
audio_cache_data_[cache].job_tracker.insert(range, graph_changed_time_);
|
audio_cache_data_[cache].job_tracker.insert(range, graph_changed_time_);
|
||||||
TryRender();
|
TryRender();
|
||||||
@@ -486,6 +514,8 @@ void PreviewAutoCacher::VideoInvalidatedFromNode(PlaybackCache *cache, const Tim
|
|||||||
// want to dedicate all our rendering power to realtime feedback for the user
|
// want to dedicate all our rendering power to realtime feedback for the user
|
||||||
//CancelVideoTasks(node);
|
//CancelVideoTasks(node);
|
||||||
|
|
||||||
|
cache->ClearRequestRange(range);
|
||||||
|
|
||||||
// If auto-cache is enabled and a slider is not being dragged, queue up to hash these frames
|
// If auto-cache is enabled and a slider is not being dragged, queue up to hash these frames
|
||||||
if (!NodeInputDragger::IsInputBeingDragged()) {
|
if (!NodeInputDragger::IsInputBeingDragged()) {
|
||||||
StartCachingVideoRange(cache, range);
|
StartCachingVideoRange(cache, range);
|
||||||
@@ -498,6 +528,8 @@ void PreviewAutoCacher::AudioInvalidatedFromNode(PlaybackCache *cache, const Tim
|
|||||||
// cancelled, so some areas may end up unrendered forever
|
// cancelled, so some areas may end up unrendered forever
|
||||||
// ClearAudioQueue();
|
// ClearAudioQueue();
|
||||||
|
|
||||||
|
cache->ClearRequestRange(range);
|
||||||
|
|
||||||
// If we're auto-caching audio or require realtime waveforms, we'll have to render this
|
// If we're auto-caching audio or require realtime waveforms, we'll have to render this
|
||||||
StartCachingAudioRange(cache, range);
|
StartCachingAudioRange(cache, range);
|
||||||
}
|
}
|
||||||
@@ -553,37 +585,37 @@ void PreviewAutoCacher::SetRendersPaused(bool e)
|
|||||||
|
|
||||||
void PreviewAutoCacher::NodeAdded(Node *node)
|
void PreviewAutoCacher::NodeAdded(Node *node)
|
||||||
{
|
{
|
||||||
graph_update_queue_.append({QueuedJob::kNodeAdded, node, NodeInput(), nullptr});
|
graph_update_queue_.push_back({QueuedJob::kNodeAdded, node, NodeInput(), nullptr});
|
||||||
UpdateGraphChangeValue();
|
UpdateGraphChangeValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreviewAutoCacher::NodeRemoved(Node *node)
|
void PreviewAutoCacher::NodeRemoved(Node *node)
|
||||||
{
|
{
|
||||||
graph_update_queue_.append({QueuedJob::kNodeRemoved, node, NodeInput(), nullptr});
|
graph_update_queue_.push_back({QueuedJob::kNodeRemoved, node, NodeInput(), nullptr});
|
||||||
UpdateGraphChangeValue();
|
UpdateGraphChangeValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreviewAutoCacher::EdgeAdded(Node *output, const NodeInput &input)
|
void PreviewAutoCacher::EdgeAdded(Node *output, const NodeInput &input)
|
||||||
{
|
{
|
||||||
graph_update_queue_.append({QueuedJob::kEdgeAdded, nullptr, input, output});
|
graph_update_queue_.push_back({QueuedJob::kEdgeAdded, nullptr, input, output});
|
||||||
UpdateGraphChangeValue();
|
UpdateGraphChangeValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreviewAutoCacher::EdgeRemoved(Node *output, const NodeInput &input)
|
void PreviewAutoCacher::EdgeRemoved(Node *output, const NodeInput &input)
|
||||||
{
|
{
|
||||||
graph_update_queue_.append({QueuedJob::kEdgeRemoved, nullptr, input, output});
|
graph_update_queue_.push_back({QueuedJob::kEdgeRemoved, nullptr, input, output});
|
||||||
UpdateGraphChangeValue();
|
UpdateGraphChangeValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreviewAutoCacher::ValueChanged(const NodeInput &input)
|
void PreviewAutoCacher::ValueChanged(const NodeInput &input)
|
||||||
{
|
{
|
||||||
graph_update_queue_.append({QueuedJob::kValueChanged, nullptr, input, nullptr});
|
graph_update_queue_.push_back({QueuedJob::kValueChanged, nullptr, input, nullptr});
|
||||||
UpdateGraphChangeValue();
|
UpdateGraphChangeValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreviewAutoCacher::ValueHintChanged(const NodeInput &input)
|
void PreviewAutoCacher::ValueHintChanged(const NodeInput &input)
|
||||||
{
|
{
|
||||||
graph_update_queue_.append({QueuedJob::kValueHintChanged, nullptr, input, nullptr});
|
graph_update_queue_.push_back({QueuedJob::kValueHintChanged, nullptr, input, nullptr});
|
||||||
UpdateGraphChangeValue();
|
UpdateGraphChangeValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,7 +623,7 @@ void PreviewAutoCacher::TryRender()
|
|||||||
{
|
{
|
||||||
delayed_requeue_timer_.stop();
|
delayed_requeue_timer_.stop();
|
||||||
|
|
||||||
if (!graph_update_queue_.isEmpty()) {
|
if (!graph_update_queue_.empty()) {
|
||||||
// Check if we have jobs running in other threads that shouldn't be interrupted right now
|
// Check if we have jobs running in other threads that shouldn't be interrupted right now
|
||||||
// NOTE: We don't check for downloads because, while they run in another thread, they don't
|
// NOTE: We don't check for downloads because, while they run in another thread, they don't
|
||||||
// require any access to the graph and therefore don't risk race conditions.
|
// require any access to the graph and therefore don't risk race conditions.
|
||||||
@@ -611,11 +643,19 @@ void PreviewAutoCacher::TryRender()
|
|||||||
single_frame_render_ = nullptr;
|
single_frame_render_ = nullptr;
|
||||||
|
|
||||||
// Check if already caching this
|
// Check if already caching this
|
||||||
RenderTicketWatcher *watcher = RenderFrame(copied_viewer_node_->GetConnectedTextureOutput(),
|
Node *n = Node::ValueToPtr<Node>(t->property("node"));
|
||||||
t->property("time").value<rational>(),
|
Node *copy = copy_map_.value(n);
|
||||||
nullptr,
|
|
||||||
t->property("dry").toBool());
|
if (copy) {
|
||||||
video_immediate_passthroughs_[watcher].append(t);
|
RenderTicketWatcher *watcher = RenderFrame(copy,
|
||||||
|
t->property("time").value<rational>(),
|
||||||
|
nullptr,
|
||||||
|
t->property("dry").toBool());
|
||||||
|
video_immediate_passthroughs_[watcher].append(t);
|
||||||
|
} else {
|
||||||
|
qWarning() << "Failed to find copied node for SFR ticket";
|
||||||
|
t->Finish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pause_renders_) {
|
if (!pause_renders_) {
|
||||||
@@ -716,6 +756,9 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational&
|
|||||||
// Allow using cached images for this render job
|
// Allow using cached images for this render job
|
||||||
rvp.use_cache = true;
|
rvp.use_cache = true;
|
||||||
|
|
||||||
|
// Multicam
|
||||||
|
rvp.multicam = static_cast<MultiCamNode*>(copy_map_.value(multicam_));
|
||||||
|
|
||||||
watcher->SetTicket(RenderManager::instance()->RenderFrame(rvp));
|
watcher->SetTicket(RenderManager::instance()->RenderFrame(rvp));
|
||||||
|
|
||||||
return watcher;
|
return watcher;
|
||||||
@@ -822,6 +865,9 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node)
|
|||||||
video_cache_data_.clear();
|
video_cache_data_.clear();
|
||||||
audio_cache_data_.clear();
|
audio_cache_data_.clear();
|
||||||
|
|
||||||
|
// Clear multicam reference
|
||||||
|
multicam_ = nullptr;
|
||||||
|
|
||||||
// Disconnect signals for future node additions/deletions
|
// Disconnect signals for future node additions/deletions
|
||||||
NodeGraph* graph = viewer_node_->parent();
|
NodeGraph* graph = viewer_node_->parent();
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ public:
|
|||||||
virtual ~PreviewAutoCacher() override;
|
virtual ~PreviewAutoCacher() override;
|
||||||
|
|
||||||
RenderTicketPtr GetSingleFrame(const rational& t, bool dry = false);
|
RenderTicketPtr GetSingleFrame(const rational& t, bool dry = false);
|
||||||
|
RenderTicketPtr GetSingleFrame(Node *n, const rational& t, bool dry = false);
|
||||||
|
|
||||||
RenderTicketPtr GetRangeOfAudio(TimeRange range);
|
RenderTicketPtr GetRangeOfAudio(TimeRange range);
|
||||||
|
|
||||||
@@ -89,6 +90,10 @@ public:
|
|||||||
|
|
||||||
void SetRendersPaused(bool e);
|
void SetRendersPaused(bool e);
|
||||||
|
|
||||||
|
void SetMulticamNode(MultiCamNode *n) { multicam_ = n; }
|
||||||
|
|
||||||
|
void SetIgnoreCacheRequests(bool e) { ignore_cache_requests_ = e; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void SetDisplayColorProcessor(ColorProcessorPtr processor)
|
void SetDisplayColorProcessor(ColorProcessorPtr processor)
|
||||||
{
|
{
|
||||||
@@ -160,7 +165,7 @@ private:
|
|||||||
|
|
||||||
Project copied_project_;
|
Project copied_project_;
|
||||||
|
|
||||||
QVector<QueuedJob> graph_update_queue_;
|
std::list<QueuedJob> graph_update_queue_;
|
||||||
QHash<Node*, Node*> copy_map_;
|
QHash<Node*, Node*> copy_map_;
|
||||||
QHash<NodeGraph*, NodeGraph*> graph_map_;
|
QHash<NodeGraph*, NodeGraph*> graph_map_;
|
||||||
ViewerOutput* copied_viewer_node_;
|
ViewerOutput* copied_viewer_node_;
|
||||||
@@ -217,6 +222,10 @@ private:
|
|||||||
|
|
||||||
ColorProcessorPtr display_color_processor_;
|
ColorProcessorPtr display_color_processor_;
|
||||||
|
|
||||||
|
MultiCamNode *multicam_;
|
||||||
|
|
||||||
|
bool ignore_cache_requests_;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
/**
|
/**
|
||||||
* @brief Handler for when the NodeGraph reports a video change over a certain time range
|
* @brief Handler for when the NodeGraph reports a video change over a certain time range
|
||||||
|
|||||||
@@ -55,7 +55,11 @@ RenderManager::RenderManager(QObject *parent) :
|
|||||||
video_thread_ = CreateThread(context_);
|
video_thread_ = CreateThread(context_);
|
||||||
dry_run_thread_ = CreateThread();
|
dry_run_thread_ = CreateThread();
|
||||||
audio_thread_ = CreateThread();
|
audio_thread_ = CreateThread();
|
||||||
waveform_thread_ = CreateThread();
|
|
||||||
|
waveform_threads_.resize(QThread::idealThreadCount());
|
||||||
|
for (size_t i=0; i<waveform_threads_.size(); i++) {
|
||||||
|
waveform_threads_[i] = CreateThread();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
decoder_clear_timer_ = new QTimer(this);
|
decoder_clear_timer_ = new QTimer(this);
|
||||||
@@ -111,6 +115,7 @@ RenderTicketPtr RenderManager::RenderFrame(const RenderVideoParams ¶ms)
|
|||||||
ticket->setProperty("cache", params.cache_dir);
|
ticket->setProperty("cache", params.cache_dir);
|
||||||
ticket->setProperty("cachetimebase", QVariant::fromValue(params.cache_timebase));
|
ticket->setProperty("cachetimebase", QVariant::fromValue(params.cache_timebase));
|
||||||
ticket->setProperty("cacheid", QVariant::fromValue(params.cache_id));
|
ticket->setProperty("cacheid", QVariant::fromValue(params.cache_id));
|
||||||
|
ticket->setProperty("multicam", Node::PtrToValue(params.multicam));
|
||||||
|
|
||||||
if (params.return_type == ReturnType::kNull) {
|
if (params.return_type == ReturnType::kNull) {
|
||||||
dry_run_thread_->AddTicket(ticket);
|
dry_run_thread_->AddTicket(ticket);
|
||||||
@@ -135,7 +140,10 @@ RenderTicketPtr RenderManager::RenderAudio(const RenderAudioParams ¶ms)
|
|||||||
ticket->setProperty("mode", params.mode);
|
ticket->setProperty("mode", params.mode);
|
||||||
|
|
||||||
if (params.generate_waveforms) {
|
if (params.generate_waveforms) {
|
||||||
waveform_thread_->AddTicket(ticket);
|
size_t thread_index = last_waveform_thread_%waveform_threads_.size();
|
||||||
|
RenderThread *thread = waveform_threads_[thread_index];
|
||||||
|
thread->AddTicket(ticket);
|
||||||
|
last_waveform_thread_++;
|
||||||
} else {
|
} else {
|
||||||
audio_thread_->AddTicket(ticket);
|
audio_thread_->AddTicket(ticket);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ public:
|
|||||||
force_size = QSize(0, 0);
|
force_size = QSize(0, 0);
|
||||||
force_channel_count = 0;
|
force_channel_count = 0;
|
||||||
mode = m;
|
mode = m;
|
||||||
|
multicam = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddCache(FrameHashCache *cache)
|
void AddCache(FrameHashCache *cache)
|
||||||
@@ -134,6 +135,7 @@ public:
|
|||||||
bool use_cache;
|
bool use_cache;
|
||||||
ReturnType return_type;
|
ReturnType return_type;
|
||||||
RenderMode::Mode mode;
|
RenderMode::Mode mode;
|
||||||
|
MultiCamNode *multicam;
|
||||||
|
|
||||||
QString cache_dir;
|
QString cache_dir;
|
||||||
rational cache_timebase;
|
rational cache_timebase;
|
||||||
@@ -230,7 +232,9 @@ private:
|
|||||||
RenderThread *video_thread_;
|
RenderThread *video_thread_;
|
||||||
RenderThread *dry_run_thread_;
|
RenderThread *dry_run_thread_;
|
||||||
RenderThread *audio_thread_;
|
RenderThread *audio_thread_;
|
||||||
RenderThread *waveform_thread_;
|
|
||||||
|
std::vector<RenderThread *> waveform_threads_;
|
||||||
|
size_t last_waveform_thread_;
|
||||||
|
|
||||||
std::list<RenderThread *> render_threads_;
|
std::list<RenderThread *> render_threads_;
|
||||||
|
|
||||||
|
|||||||
+24
-106
@@ -294,118 +294,36 @@ DecoderPtr RenderProcessor::ResolveDecoderFromInput(const QString& decoder_id, c
|
|||||||
return dec;
|
return dec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NodeValueDatabase RenderProcessor::GenerateDatabase(const Node *node, const TimeRange &range)
|
||||||
|
{
|
||||||
|
NodeValueDatabase db = super::GenerateDatabase(node, range);
|
||||||
|
|
||||||
|
if (const MultiCamNode *multicam = dynamic_cast<const MultiCamNode*>(node)) {
|
||||||
|
if (Node::ValueToPtr<MultiCamNode>(ticket_->property("multicam")) == multicam) {
|
||||||
|
int sz = multicam->InputArraySize(multicam->kSourcesInput);
|
||||||
|
NodeValueTableArray arr;
|
||||||
|
QVector<TexturePtr> multicam_tex(sz);
|
||||||
|
for (int i=0; i<sz; i++) {
|
||||||
|
ProcessInputElement(arr, multicam, multicam->kSourcesInput, i, range);
|
||||||
|
|
||||||
|
NodeValue val = GenerateRowValueElement(multicam, multicam->kSourcesInput, i, &arr.at(i), range);
|
||||||
|
ResolveJobs(val);
|
||||||
|
|
||||||
|
multicam_tex[i] = val.toTexture();
|
||||||
|
}
|
||||||
|
ticket_->setProperty("multicam_output", QVariant::fromValue(multicam_tex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
void RenderProcessor::Process(RenderTicketPtr ticket, Renderer *render_ctx, DecoderCache *decoder_cache, ShaderCache *shader_cache)
|
void RenderProcessor::Process(RenderTicketPtr ticket, Renderer *render_ctx, DecoderCache *decoder_cache, ShaderCache *shader_cache)
|
||||||
{
|
{
|
||||||
RenderProcessor p(ticket, render_ctx, decoder_cache, shader_cache);
|
RenderProcessor p(ticket, render_ctx, decoder_cache, shader_cache);
|
||||||
p.Run();
|
p.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const TimeRange &range)
|
|
||||||
{
|
|
||||||
if (track->type() == Track::kAudio) {
|
|
||||||
|
|
||||||
const AudioParams& audio_params = GetCacheAudioParams();
|
|
||||||
|
|
||||||
QVector<Block*> active_blocks = track->BlocksAtTimeRange(range);
|
|
||||||
|
|
||||||
// All these blocks will need to output to a buffer so we create one here
|
|
||||||
SampleBuffer block_range_buffer(audio_params, range.length());
|
|
||||||
block_range_buffer.silence();
|
|
||||||
|
|
||||||
NodeValueTable merged_table;
|
|
||||||
|
|
||||||
// Loop through active blocks retrieving their audio
|
|
||||||
foreach (Block* b, active_blocks) {
|
|
||||||
if (dynamic_cast<ClipBlock*>(b) || dynamic_cast<TransitionBlock*>(b)) {
|
|
||||||
TimeRange range_for_block(qMax(b->in(), range.in()),
|
|
||||||
qMin(b->out(), range.out()));
|
|
||||||
|
|
||||||
qint64 destination_offset = audio_params.time_to_samples(range_for_block.in() - range.in());
|
|
||||||
qint64 max_dest_sz = audio_params.time_to_samples(range_for_block.length());
|
|
||||||
|
|
||||||
// Destination buffer
|
|
||||||
NodeValueTable table = GenerateTable(b, Track::TransformRangeForBlock(b, range_for_block));
|
|
||||||
SampleBuffer samples_from_this_block = table.Take(NodeValue::kSamples).toSamples();
|
|
||||||
ClipBlock *clip_cast = dynamic_cast<ClipBlock*>(b);
|
|
||||||
|
|
||||||
if (samples_from_this_block.is_allocated()) {
|
|
||||||
// If this is a clip, we might have extra speed/reverse information
|
|
||||||
if (clip_cast) {
|
|
||||||
double speed_value = clip_cast->speed();
|
|
||||||
bool reversed = clip_cast->reverse();
|
|
||||||
|
|
||||||
if (qIsNull(speed_value)) {
|
|
||||||
// Just silence, don't think there's any other practical application of 0 speed audio
|
|
||||||
samples_from_this_block.silence();
|
|
||||||
} else if (!qFuzzyCompare(speed_value, 1.0)) {
|
|
||||||
if (clip_cast->maintain_audio_pitch()) {
|
|
||||||
AudioProcessor processor;
|
|
||||||
|
|
||||||
if (processor.Open(samples_from_this_block.audio_params(), samples_from_this_block.audio_params(), speed_value)) {
|
|
||||||
AudioProcessor::Buffer out;
|
|
||||||
|
|
||||||
// FIXME: This is not the best way to do this, the TempoProcessor works best
|
|
||||||
// when it's given a continuous stream of audio, which is challenging
|
|
||||||
// in our current "modular" audio system. This should still work reasonably
|
|
||||||
// well on export (assuming audio is all generated at once on export), but
|
|
||||||
// users may hear clicks and pops in the audio during preview due to this
|
|
||||||
// approach.
|
|
||||||
int r = processor.Convert(samples_from_this_block.to_raw_ptrs().data(), samples_from_this_block.sample_count(), nullptr);
|
|
||||||
|
|
||||||
if (r < 0) {
|
|
||||||
qCritical() << "Failed to change tempo of audio:" << r;
|
|
||||||
} else {
|
|
||||||
processor.Flush();
|
|
||||||
|
|
||||||
processor.Convert(nullptr, 0, &out);
|
|
||||||
|
|
||||||
if (!out.empty()) {
|
|
||||||
int nb_samples = out.front().size() * samples_from_this_block.audio_params().bytes_per_sample_per_channel();
|
|
||||||
|
|
||||||
if (nb_samples) {
|
|
||||||
SampleBuffer new_samples(samples_from_this_block.audio_params(), nb_samples);
|
|
||||||
|
|
||||||
for (int i=0; i<out.size(); i++) {
|
|
||||||
memcpy(new_samples.data(i), out[i].data(), out[i].size());
|
|
||||||
}
|
|
||||||
|
|
||||||
samples_from_this_block = new_samples;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Multiply time
|
|
||||||
samples_from_this_block.speed(speed_value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reversed) {
|
|
||||||
samples_from_this_block.reverse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qint64 copy_length = qMin(max_dest_sz, qint64(samples_from_this_block.sample_count()));
|
|
||||||
|
|
||||||
// Copy samples into destination buffer
|
|
||||||
for (int i=0; i<samples_from_this_block.audio_params().channel_count(); i++) {
|
|
||||||
block_range_buffer.set(i, samples_from_this_block.data(i), destination_offset, copy_length);
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeValueTable::Merge({merged_table, table});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
merged_table.Push(NodeValue::kSamples, QVariant::fromValue(block_range_buffer), track);
|
|
||||||
|
|
||||||
return merged_table;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return super::GenerateBlockTable(track, range);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJob *stream, const rational &input_time)
|
void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJob *stream, const rational &input_time)
|
||||||
{
|
{
|
||||||
if (ticket_->property("type").value<RenderManager::TicketType>() != RenderManager::kTypeVideo) {
|
if (ticket_->property("type").value<RenderManager::TicketType>() != RenderManager::kTypeVideo) {
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ namespace olive {
|
|||||||
class RenderProcessor : public NodeTraverser
|
class RenderProcessor : public NodeTraverser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
virtual NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range) override;
|
||||||
|
|
||||||
static void Process(RenderTicketPtr ticket, Renderer* render_ctx, DecoderCache* decoder_cache, ShaderCache* shader_cache);
|
static void Process(RenderTicketPtr ticket, Renderer* render_ctx, DecoderCache* decoder_cache, ShaderCache* shader_cache);
|
||||||
|
|
||||||
struct RenderedWaveform {
|
struct RenderedWaveform {
|
||||||
@@ -42,8 +44,6 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual NodeValueTable GenerateBlockTable(const Track *track, const TimeRange &range) override;
|
|
||||||
|
|
||||||
virtual void ProcessVideoFootage(TexturePtr destination, const FootageJob *stream, const rational &input_time) override;
|
virtual void ProcessVideoFootage(TexturePtr destination, const FootageJob *stream, const rational &input_time) override;
|
||||||
|
|
||||||
virtual void ProcessAudioFootage(SampleBuffer &destination, const FootageJob *stream, const TimeRange &input_time) override;
|
virtual void ProcessAudioFootage(SampleBuffer &destination, const FootageJob *stream, const TimeRange &input_time) override;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ add_subdirectory(handmovableview)
|
|||||||
add_subdirectory(keyframeview)
|
add_subdirectory(keyframeview)
|
||||||
add_subdirectory(manageddisplay)
|
add_subdirectory(manageddisplay)
|
||||||
add_subdirectory(menu)
|
add_subdirectory(menu)
|
||||||
|
add_subdirectory(multicam)
|
||||||
add_subdirectory(nodecombobox)
|
add_subdirectory(nodecombobox)
|
||||||
add_subdirectory(nodeparamview)
|
add_subdirectory(nodeparamview)
|
||||||
add_subdirectory(nodetableview)
|
add_subdirectory(nodetableview)
|
||||||
|
|||||||
@@ -275,6 +275,14 @@ void ManagedDisplayWidget::SetInnerMouseTracking(bool e)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VideoParams ManagedDisplayWidget::GetViewportParams() const
|
||||||
|
{
|
||||||
|
int device_width = width() * devicePixelRatioF();
|
||||||
|
int device_height = height() * devicePixelRatioF();
|
||||||
|
VideoParams::Format device_format = static_cast<VideoParams::Format>(OLIVE_CONFIG("OfflinePixelFormat").toInt());
|
||||||
|
return VideoParams(device_width, device_height, device_format, VideoParams::kInternalChannelCount);
|
||||||
|
}
|
||||||
|
|
||||||
void ManagedDisplayWidget::update()
|
void ManagedDisplayWidget::update()
|
||||||
{
|
{
|
||||||
if (RenderManager::instance()->backend() == RenderManager::kOpenGL) {
|
if (RenderManager::instance()->backend() == RenderManager::kOpenGL) {
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ protected:
|
|||||||
virtual void initializeGL() override
|
virtual void initializeGL() override
|
||||||
{
|
{
|
||||||
connect(context(), &QOpenGLContext::aboutToBeDestroyed,
|
connect(context(), &QOpenGLContext::aboutToBeDestroyed,
|
||||||
this, &ManagedDisplayWidgetOpenGL::OnDestroy);
|
this, &ManagedDisplayWidgetOpenGL::DestroyListener,
|
||||||
|
Qt::DirectConnection);
|
||||||
|
|
||||||
emit OnInit();
|
emit OnInit();
|
||||||
}
|
}
|
||||||
@@ -211,6 +212,8 @@ protected:
|
|||||||
return wrapper_ ? wrapper_->rect() : QRect();
|
return wrapper_ ? wrapper_->rect() : QRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VideoParams GetViewportParams() const;
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
/**
|
/**
|
||||||
* @brief Called whenever the internal rendering context has been created
|
* @brief Called whenever the internal rendering context has been created
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ void MenuShared::EnableDisableTriggered()
|
|||||||
|
|
||||||
void MenuShared::NestTriggered()
|
void MenuShared::NestTriggered()
|
||||||
{
|
{
|
||||||
qDebug() << "FIXME: Stub";
|
PanelManager::instance()->MostRecentlyFocused<TimelinePanel>()->NestSelectedClips();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuShared::DefaultTransitionTriggered()
|
void MenuShared::DefaultTransitionTriggered()
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Olive - Non-Linear Video Editor
|
||||||
|
# Copyright (C) 2022 Olive Team
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
set(OLIVE_SOURCES
|
||||||
|
${OLIVE_SOURCES}
|
||||||
|
widget/multicam/multicamdisplay.cpp
|
||||||
|
widget/multicam/multicamdisplay.h
|
||||||
|
widget/multicam/multicamwidget.cpp
|
||||||
|
widget/multicam/multicamwidget.h
|
||||||
|
PARENT_SCOPE
|
||||||
|
)
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2022 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 "multicamdisplay.h"
|
||||||
|
|
||||||
|
namespace olive {
|
||||||
|
|
||||||
|
#define super ViewerDisplayWidget
|
||||||
|
|
||||||
|
MulticamDisplay::MulticamDisplay(QWidget *parent) :
|
||||||
|
super(parent),
|
||||||
|
node_(nullptr),
|
||||||
|
rows_(0),
|
||||||
|
cols_(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void MulticamDisplay::OnPaint()
|
||||||
|
{
|
||||||
|
super::OnPaint();
|
||||||
|
|
||||||
|
if (node_) {
|
||||||
|
QPainter p(paint_device());
|
||||||
|
|
||||||
|
p.setPen(QPen(Qt::yellow, fontMetrics().height()/4));
|
||||||
|
p.setBrush(Qt::NoBrush);
|
||||||
|
|
||||||
|
int rows, cols;
|
||||||
|
node_->GetRowsAndColumns(&rows, &cols);
|
||||||
|
|
||||||
|
int multi = std::max(rows, cols);
|
||||||
|
int cell_width = width() / multi;
|
||||||
|
int cell_height = height() / multi;
|
||||||
|
|
||||||
|
int col, row;
|
||||||
|
node_->IndexToRowCols(node_->GetCurrentSource(), rows, cols, &row, &col);
|
||||||
|
|
||||||
|
QRect r(cell_width * col, cell_height * row, cell_width, cell_height);
|
||||||
|
p.drawRect(GenerateWorldTransform().mapRect(r));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MulticamDisplay::OnDestroy()
|
||||||
|
{
|
||||||
|
shader_ = QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
TexturePtr MulticamDisplay::LoadCustomTextureFromFrame(const QVariant &v)
|
||||||
|
{
|
||||||
|
if (v.canConvert<QVector<TexturePtr> >()) {
|
||||||
|
QVector<TexturePtr> tex = v.value<QVector<TexturePtr> >();
|
||||||
|
|
||||||
|
TexturePtr main = renderer()->CreateTexture(this->GetViewportParams());
|
||||||
|
|
||||||
|
int rows, cols;
|
||||||
|
MultiCamNode::GetRowsAndColumns(tex.size(), &rows, &cols);
|
||||||
|
|
||||||
|
if (shader_.isNull() || rows_ != rows || cols_ != cols) {
|
||||||
|
if (!shader_.isNull()) {
|
||||||
|
renderer()->DestroyNativeShader(shader_);
|
||||||
|
}
|
||||||
|
|
||||||
|
shader_ = renderer()->CreateNativeShader(ShaderCode(GenerateShaderCode(rows, cols)));
|
||||||
|
|
||||||
|
rows_ = rows;
|
||||||
|
cols_ = cols;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderJob job;
|
||||||
|
|
||||||
|
for (int i=0; i<tex.size(); i++) {
|
||||||
|
int c, r;
|
||||||
|
MultiCamNode::IndexToRowCols(i, rows, cols, &r, &c);
|
||||||
|
job.Insert(QStringLiteral("tex_%1_%2").arg(QString::number(r), QString::number(c)), NodeValue(NodeValue::kTexture, tex.at(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer()->BlitToTexture(shader_, job, main.get());
|
||||||
|
|
||||||
|
return main;
|
||||||
|
} else {
|
||||||
|
return super::LoadCustomTextureFromFrame(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString dblToGlsl(double d)
|
||||||
|
{
|
||||||
|
return QString::number(d, 'f');
|
||||||
|
}
|
||||||
|
|
||||||
|
QString MulticamDisplay::GenerateShaderCode(int rows, int cols)
|
||||||
|
{
|
||||||
|
int multiplier = std::max(cols, rows);
|
||||||
|
|
||||||
|
QStringList shader;
|
||||||
|
|
||||||
|
shader.append(QStringLiteral("in vec2 ove_texcoord;"));
|
||||||
|
shader.append(QStringLiteral("out vec4 frag_color;"));
|
||||||
|
|
||||||
|
for (int x=0;x<cols;x++) {
|
||||||
|
for (int y=0;y<rows;y++) {
|
||||||
|
shader.append(QStringLiteral("uniform sampler2D tex_%1_%2;").arg(QString::number(y), QString::number(x)));
|
||||||
|
shader.append(QStringLiteral("uniform bool tex_%1_%2_enabled;").arg(QString::number(y), QString::number(x)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shader.append(QStringLiteral("void main() {"));
|
||||||
|
|
||||||
|
for (int x=0;x<cols;x++) {
|
||||||
|
if (x > 0) {
|
||||||
|
shader.append(QStringLiteral(" else"));
|
||||||
|
}
|
||||||
|
if (x == cols-1) {
|
||||||
|
shader.append(QStringLiteral(" {"));
|
||||||
|
} else {
|
||||||
|
shader.append(QStringLiteral(" if (ove_texcoord.x < %1) {").arg(dblToGlsl(double(x+1)/double(multiplier))));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int y=0;y<rows;y++) {
|
||||||
|
if (y > 0) {
|
||||||
|
shader.append(QStringLiteral(" else"));
|
||||||
|
}
|
||||||
|
if (y == rows-1) {
|
||||||
|
shader.append(QStringLiteral(" {"));
|
||||||
|
} else {
|
||||||
|
shader.append(QStringLiteral(" if (ove_texcoord.y < %1) {").arg(dblToGlsl(double(y+1)/double(multiplier))));
|
||||||
|
}
|
||||||
|
QString input = QStringLiteral("tex_%1_%2").arg(QString::number(y), QString::number(x));
|
||||||
|
shader.append(QStringLiteral(" vec2 coord = vec2((ove_texcoord.x+%1)*%2, (ove_texcoord.y+%3)*%4);").arg(
|
||||||
|
dblToGlsl( - double(x)/double(multiplier)),
|
||||||
|
dblToGlsl(multiplier),
|
||||||
|
dblToGlsl( - double(y)/double(multiplier)),
|
||||||
|
dblToGlsl(multiplier)
|
||||||
|
));
|
||||||
|
shader.append(QStringLiteral(" if (%1_enabled && coord.x >= 0.0 && coord.x < 1.0 && coord.y >= 0.0 && coord.y < 1.0) {").arg(input));
|
||||||
|
shader.append(QStringLiteral(" frag_color = texture(%1, coord);").arg(input));
|
||||||
|
shader.append(QStringLiteral(" } else {"));
|
||||||
|
shader.append(QStringLiteral(" discard;"));
|
||||||
|
shader.append(QStringLiteral(" }"));
|
||||||
|
shader.append(QStringLiteral(" }"));
|
||||||
|
}
|
||||||
|
|
||||||
|
shader.append(QStringLiteral(" }"));
|
||||||
|
}
|
||||||
|
|
||||||
|
shader.append(QStringLiteral("}"));
|
||||||
|
|
||||||
|
return shader.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
void MulticamDisplay::SetMulticamNode(MultiCamNode *n)
|
||||||
|
{
|
||||||
|
node_ = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2022 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 MULTICAMDISPLAY_H
|
||||||
|
#define MULTICAMDISPLAY_H
|
||||||
|
|
||||||
|
#include "node/input/multicam/multicamnode.h"
|
||||||
|
#include "widget/viewer/viewerdisplay.h"
|
||||||
|
|
||||||
|
namespace olive {
|
||||||
|
|
||||||
|
class MulticamDisplay : public ViewerDisplayWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit MulticamDisplay(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
void SetMulticamNode(MultiCamNode *n);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void OnPaint() override;
|
||||||
|
|
||||||
|
virtual void OnDestroy() override;
|
||||||
|
|
||||||
|
virtual TexturePtr LoadCustomTextureFromFrame(const QVariant &v) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static QString GenerateShaderCode(int rows, int cols);
|
||||||
|
|
||||||
|
MultiCamNode *node_;
|
||||||
|
|
||||||
|
QVariant shader_;
|
||||||
|
int rows_;
|
||||||
|
int cols_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // MULTICAMDISPLAY_H
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2022 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 "multicamwidget.h"
|
||||||
|
#include "qshortcut.h"
|
||||||
|
#include "widget/nodeparamview/nodeparamviewundo.h"
|
||||||
|
#include "widget/timeruler/timeruler.h"
|
||||||
|
#include "widget/timelinewidget/undo/timelineundosplit.h"
|
||||||
|
|
||||||
|
namespace olive {
|
||||||
|
|
||||||
|
#define super TimeBasedWidget
|
||||||
|
|
||||||
|
MulticamWidget::MulticamWidget(QWidget *parent) :
|
||||||
|
super{false, false, parent},
|
||||||
|
node_(nullptr),
|
||||||
|
clip_(nullptr)
|
||||||
|
{
|
||||||
|
auto layout = new QVBoxLayout(this);
|
||||||
|
|
||||||
|
sizer_ = new ViewerSizer(this);
|
||||||
|
layout->addWidget(sizer_);
|
||||||
|
|
||||||
|
display_ = new MulticamDisplay(this);
|
||||||
|
display_->SetShowWidgetBackground(true);
|
||||||
|
connect(display_, &ViewerDisplayWidget::DragStarted, this, &MulticamWidget::DisplayClicked);
|
||||||
|
|
||||||
|
connect(sizer_, &ViewerSizer::RequestScale, display_, &ViewerDisplayWidget::SetMatrixZoom);
|
||||||
|
connect(sizer_, &ViewerSizer::RequestTranslate, display_, &ViewerDisplayWidget::SetMatrixTranslate);
|
||||||
|
connect(display_, &ViewerDisplayWidget::HandDragMoved, sizer_, &ViewerSizer::HandDragMove);
|
||||||
|
sizer_->SetWidget(display_);
|
||||||
|
|
||||||
|
layout->addWidget(this->ruler());
|
||||||
|
layout->addWidget(this->scrollbar());
|
||||||
|
|
||||||
|
for (int i=0; i<9; i++) {
|
||||||
|
new QShortcut(QStringLiteral("Ctrl+%1").arg(QString::number(i+1)), this, this, [this, i]{Switch(i, false);});
|
||||||
|
new QShortcut(QString::number(i+1), this, this, [this, i]{Switch(i, true);});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MulticamWidget::SetMulticamNodeInternal(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip)
|
||||||
|
{
|
||||||
|
ConnectViewerNode(viewer);
|
||||||
|
node_ = n;
|
||||||
|
display_->SetMulticamNode(n);
|
||||||
|
clip_ = clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MulticamWidget::SetMulticamNode(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip, const rational &time)
|
||||||
|
{
|
||||||
|
if (time.isNaN() || time == GetTime()) {
|
||||||
|
SetMulticamNodeInternal(viewer, n, clip);
|
||||||
|
play_queue_.clear();
|
||||||
|
} else {
|
||||||
|
MulticamNodeQueue m = {time, viewer, n, clip};
|
||||||
|
play_queue_.push_back(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MulticamWidget::ConnectNodeEvent(ViewerOutput *n)
|
||||||
|
{
|
||||||
|
connect(n, &ViewerOutput::SizeChanged, sizer_, &ViewerSizer::SetChildSize);
|
||||||
|
connect(n, &ViewerOutput::PixelAspectChanged, sizer_, &ViewerSizer::SetPixelAspectRatio);
|
||||||
|
|
||||||
|
VideoParams vp = n->GetVideoParams();
|
||||||
|
sizer_->SetChildSize(vp.width(), vp.height());
|
||||||
|
sizer_->SetPixelAspectRatio(vp.pixel_aspect_ratio());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MulticamWidget::DisconnectNodeEvent(ViewerOutput *n)
|
||||||
|
{
|
||||||
|
disconnect(n, &ViewerOutput::SizeChanged, sizer_, &ViewerSizer::SetChildSize);
|
||||||
|
disconnect(n, &ViewerOutput::PixelAspectChanged, sizer_, &ViewerSizer::SetPixelAspectRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MulticamWidget::TimeChangedEvent(const rational &t)
|
||||||
|
{
|
||||||
|
super::TimeChangedEvent(t);
|
||||||
|
|
||||||
|
if (!play_queue_.empty()) {
|
||||||
|
const MulticamNodeQueue &m = play_queue_.front();
|
||||||
|
if (m.time >= t) {
|
||||||
|
SetMulticamNodeInternal(m.viewer, m.node, m.clip);
|
||||||
|
play_queue_.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MulticamWidget::Switch(int source, bool split_clip)
|
||||||
|
{
|
||||||
|
if (!node_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiUndoCommand *command = new MultiUndoCommand();
|
||||||
|
|
||||||
|
MultiCamNode *cam = node_;
|
||||||
|
ClipBlock *clip = clip_;
|
||||||
|
|
||||||
|
BlockSplitPreservingLinksCommand *split = nullptr;
|
||||||
|
|
||||||
|
if (clip_ && split_clip && clip_->in() < GetTime() && clip_->out() > GetTime()) {
|
||||||
|
QVector<Block*> blocks;
|
||||||
|
|
||||||
|
blocks.append(clip_);
|
||||||
|
blocks.append(clip_->block_links());
|
||||||
|
|
||||||
|
split = new BlockSplitPreservingLinksCommand(blocks, {GetTime()});
|
||||||
|
split->redo_now();
|
||||||
|
command->add_child(split);
|
||||||
|
|
||||||
|
clip = static_cast<ClipBlock*>(split->GetSplit(clip_, 0));
|
||||||
|
|
||||||
|
cam = clip->FindMulticam();
|
||||||
|
}
|
||||||
|
|
||||||
|
command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(cam, cam->kCurrentInput)), source));
|
||||||
|
|
||||||
|
for (Block *link : clip->block_links()) {
|
||||||
|
if (ClipBlock *clink = dynamic_cast<ClipBlock*>(link)) {
|
||||||
|
if (MultiCamNode *mlink = clink->FindMulticam()) {
|
||||||
|
command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(mlink, mlink->kCurrentInput)), source));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Core::instance()->undo_stack()->push(command);
|
||||||
|
|
||||||
|
display_->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MulticamWidget::DisplayClicked(const QPoint &p)
|
||||||
|
{
|
||||||
|
if (!node_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPointF click = display_->ScreenToScenePoint(p);
|
||||||
|
int width = display_->GetVideoParams().width();
|
||||||
|
int height = display_->GetVideoParams().height();
|
||||||
|
|
||||||
|
if (click.x() < 0 || click.y() < 0 || click.x() >= width || click.y() >= height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rows, cols;
|
||||||
|
node_->GetRowsAndColumns(&rows, &cols);
|
||||||
|
|
||||||
|
int multi = std::max(cols, rows);
|
||||||
|
|
||||||
|
int c = click.x() / (width/multi);
|
||||||
|
int r = click.y() / (height/multi);
|
||||||
|
|
||||||
|
int source = node_->RowsColsToIndex(r, c, rows, cols);
|
||||||
|
|
||||||
|
Switch(source, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2022 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 MULTICAMWIDGET_H
|
||||||
|
#define MULTICAMWIDGET_H
|
||||||
|
|
||||||
|
#include "multicamdisplay.h"
|
||||||
|
#include "node/input/multicam/multicamnode.h"
|
||||||
|
#include "widget/viewer/viewer.h"
|
||||||
|
|
||||||
|
namespace olive {
|
||||||
|
|
||||||
|
class MulticamWidget : public TimeBasedWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit MulticamWidget(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
MulticamDisplay *GetDisplayWidget() const { return display_; }
|
||||||
|
|
||||||
|
void SetMulticamNode(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip, const rational &time);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void ConnectNodeEvent(ViewerOutput *n) override;
|
||||||
|
virtual void DisconnectNodeEvent(ViewerOutput *n) override;
|
||||||
|
virtual void TimeChangedEvent(const rational &t) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SetMulticamNodeInternal(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip);
|
||||||
|
|
||||||
|
void Switch(int source, bool split_clip);
|
||||||
|
|
||||||
|
ViewerSizer *sizer_;
|
||||||
|
|
||||||
|
MulticamDisplay *display_;
|
||||||
|
|
||||||
|
MultiCamNode *node_;
|
||||||
|
|
||||||
|
ClipBlock *clip_;
|
||||||
|
|
||||||
|
struct MulticamNodeQueue
|
||||||
|
{
|
||||||
|
rational time;
|
||||||
|
ViewerOutput *viewer;
|
||||||
|
MultiCamNode *node;
|
||||||
|
ClipBlock *clip;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::list<MulticamNodeQueue> play_queue_;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void DisplayClicked(const QPoint &p);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // MULTICAMWIDGET_H
|
||||||
@@ -76,7 +76,7 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) :
|
|||||||
// Create contexts for three different types
|
// Create contexts for three different types
|
||||||
context_items_.resize(Track::kCount + 1);
|
context_items_.resize(Track::kCount + 1);
|
||||||
for (int i=0; i<context_items_.size(); i++) {
|
for (int i=0; i<context_items_.size(); i++) {
|
||||||
NodeParamViewContext *c = new NodeParamViewContext;
|
NodeParamViewContext *c = new NodeParamViewContext(param_widget_area_);
|
||||||
c->setVisible(false);
|
c->setVisible(false);
|
||||||
connect(c, &NodeParamViewContext::AboutToDeleteItem, this, &NodeParamView::ItemAboutToBeRemoved, Qt::DirectConnection);
|
connect(c, &NodeParamViewContext::AboutToDeleteItem, this, &NodeParamView::ItemAboutToBeRemoved, Qt::DirectConnection);
|
||||||
|
|
||||||
@@ -245,8 +245,6 @@ void NodeParamView::DeselectNodes(const QVector<Node *> &nodes)
|
|||||||
|
|
||||||
void NodeParamView::UpdateContexts()
|
void NodeParamView::UpdateContexts()
|
||||||
{
|
{
|
||||||
//TIME_THIS_FUNCTION;
|
|
||||||
|
|
||||||
bool changes_made = false;
|
bool changes_made = false;
|
||||||
|
|
||||||
foreach (Node *ctx, current_contexts_) {
|
foreach (Node *ctx, current_contexts_) {
|
||||||
@@ -735,7 +733,7 @@ void NodeParamView::AddNode(Node *n, Node *ctx, NodeParamViewContext *context)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeParamViewItem* item = new NodeParamViewItem(n, IsGroupMode() ? kCheckBoxesOnNonConnected : kNoCheckBoxes, context);
|
NodeParamViewItem* item = new NodeParamViewItem(n, IsGroupMode() ? kCheckBoxesOnNonConnected : kNoCheckBoxes, context->GetDockArea());
|
||||||
|
|
||||||
connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::SetTimeAndSignal);
|
connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::SetTimeAndSignal);
|
||||||
connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::SelectNodeFromConnectedLink);
|
connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::SelectNodeFromConnectedLink);
|
||||||
|
|||||||
@@ -50,13 +50,13 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input,
|
|||||||
label_layout->setMargin(0);
|
label_layout->setMargin(0);
|
||||||
layout->addLayout(label_layout);
|
layout->addLayout(label_layout);
|
||||||
|
|
||||||
CollapseButton *collapse_btn = new CollapseButton();
|
CollapseButton *collapse_btn = new CollapseButton(this);
|
||||||
collapse_btn->setChecked(false);
|
collapse_btn->setChecked(false);
|
||||||
label_layout->addWidget(collapse_btn);
|
label_layout->addWidget(collapse_btn);
|
||||||
|
|
||||||
label_layout->addWidget(new QLabel(tr("Connected to")));
|
label_layout->addWidget(new QLabel(tr("Connected to"), this));
|
||||||
|
|
||||||
connected_to_lbl_ = new ClickableLabel();
|
connected_to_lbl_ = new ClickableLabel(this);
|
||||||
connected_to_lbl_->setCursor(Qt::PointingHandCursor);
|
connected_to_lbl_->setCursor(Qt::PointingHandCursor);
|
||||||
connected_to_lbl_->setContextMenuPolicy(Qt::CustomContextMenu);
|
connected_to_lbl_->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
connect(connected_to_lbl_, &ClickableLabel::MouseClicked, this, &NodeParamViewConnectedLabel::ConnectionClicked);
|
connect(connected_to_lbl_, &ClickableLabel::MouseClicked, this, &NodeParamViewConnectedLabel::ConnectionClicked);
|
||||||
@@ -80,18 +80,23 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input,
|
|||||||
connect(input_.node(), &Node::InputConnected, this, &NodeParamViewConnectedLabel::InputConnected);
|
connect(input_.node(), &Node::InputConnected, this, &NodeParamViewConnectedLabel::InputConnected);
|
||||||
connect(input_.node(), &Node::InputDisconnected, this, &NodeParamViewConnectedLabel::InputDisconnected);
|
connect(input_.node(), &Node::InputDisconnected, this, &NodeParamViewConnectedLabel::InputDisconnected);
|
||||||
|
|
||||||
// Set up table area
|
// Creating the tree is expensive, hold off until the user specifically requests it
|
||||||
value_tree_ = new NodeValueTree();
|
value_tree_ = nullptr;
|
||||||
value_tree_->setVisible(false);
|
|
||||||
layout->addWidget(value_tree_);
|
|
||||||
connect(collapse_btn, &CollapseButton::toggled, this, &NodeParamViewConnectedLabel::SetValueTreeVisible);
|
connect(collapse_btn, &CollapseButton::toggled, this, &NodeParamViewConnectedLabel::SetValueTreeVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NodeParamViewConnectedLabel::CreateTree()
|
||||||
|
{
|
||||||
|
// Set up table area
|
||||||
|
value_tree_ = new NodeValueTree(this);
|
||||||
|
layout()->addWidget(value_tree_);
|
||||||
|
}
|
||||||
|
|
||||||
void NodeParamViewConnectedLabel::SetTime(const rational &time)
|
void NodeParamViewConnectedLabel::SetTime(const rational &time)
|
||||||
{
|
{
|
||||||
time_ = time;
|
time_ = time;
|
||||||
|
|
||||||
if (value_tree_->isVisible()) {
|
if (value_tree_ && value_tree_->isVisible()) {
|
||||||
UpdateValueTree();
|
UpdateValueTree();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,14 +159,22 @@ void NodeParamViewConnectedLabel::UpdateLabel()
|
|||||||
|
|
||||||
void NodeParamViewConnectedLabel::UpdateValueTree()
|
void NodeParamViewConnectedLabel::UpdateValueTree()
|
||||||
{
|
{
|
||||||
value_tree_->SetNode(input_, time_);
|
if (value_tree_) {
|
||||||
|
value_tree_->SetNode(input_, time_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeParamViewConnectedLabel::SetValueTreeVisible(bool e)
|
void NodeParamViewConnectedLabel::SetValueTreeVisible(bool e)
|
||||||
{
|
{
|
||||||
value_tree_->setVisible(e);
|
if (value_tree_) {
|
||||||
|
value_tree_->setVisible(e);
|
||||||
|
}
|
||||||
|
|
||||||
if (e) {
|
if (e) {
|
||||||
|
if (!value_tree_) {
|
||||||
|
CreateTree();
|
||||||
|
}
|
||||||
|
|
||||||
UpdateValueTree();
|
UpdateValueTree();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ private:
|
|||||||
|
|
||||||
void UpdateValueTree();
|
void UpdateValueTree();
|
||||||
|
|
||||||
|
void CreateTree();
|
||||||
|
|
||||||
ClickableLabel* connected_to_lbl_;
|
ClickableLabel* connected_to_lbl_;
|
||||||
|
|
||||||
NodeInput input_;
|
NodeInput input_;
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ void NodeParamViewItem::RecreateBody()
|
|||||||
body_->deleteLater();
|
body_->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
body_ = new NodeParamViewItemBody(node_, create_checkboxes_);
|
body_ = new NodeParamViewItemBody(node_, create_checkboxes_, this);
|
||||||
connect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode);
|
connect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode);
|
||||||
connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime);
|
connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime);
|
||||||
connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged);
|
connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged);
|
||||||
@@ -148,7 +148,7 @@ NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBe
|
|||||||
|
|
||||||
if (n->InputIsArray(input)) {
|
if (n->InputIsArray(input)) {
|
||||||
// Insert here
|
// Insert here
|
||||||
QWidget* array_widget = new QWidget();
|
QWidget* array_widget = new QWidget(this);
|
||||||
|
|
||||||
QGridLayout* array_layout = new QGridLayout(array_widget);
|
QGridLayout* array_layout = new QGridLayout(array_widget);
|
||||||
array_layout->setContentsMargins(QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral(" ")), 0, 0, 0);
|
array_layout->setContentsMargins(QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral(" ")), 0, 0, 0);
|
||||||
@@ -160,7 +160,7 @@ NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBe
|
|||||||
int arr_sz = 0;
|
int arr_sz = 0;
|
||||||
|
|
||||||
// Add one last add button for appending to the array
|
// Add one last add button for appending to the array
|
||||||
NodeParamViewArrayButton* append_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kAdd);
|
NodeParamViewArrayButton* append_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kAdd, this);
|
||||||
connect(append_btn, &NodeParamViewArrayButton::clicked, this, &NodeParamViewItemBody::ArrayAppendClicked);
|
connect(append_btn, &NodeParamViewArrayButton::clicked, this, &NodeParamViewItemBody::ArrayAppendClicked);
|
||||||
array_layout->addWidget(append_btn, arr_sz, kArrayInsertColumn);
|
array_layout->addWidget(append_btn, arr_sz, kArrayInsertColumn);
|
||||||
|
|
||||||
@@ -186,7 +186,7 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
|
|||||||
|
|
||||||
// Create optional checkbox if requested
|
// Create optional checkbox if requested
|
||||||
if (create_checkboxes_) {
|
if (create_checkboxes_) {
|
||||||
ui_objects.optional_checkbox = new QCheckBox();
|
ui_objects.optional_checkbox = new QCheckBox(this);
|
||||||
connect(ui_objects.optional_checkbox, &QCheckBox::clicked, this, &NodeParamViewItemBody::OptionalCheckBoxClicked);
|
connect(ui_objects.optional_checkbox, &QCheckBox::clicked, this, &NodeParamViewItemBody::OptionalCheckBoxClicked);
|
||||||
layout->addWidget(ui_objects.optional_checkbox, row, kOptionalCheckBox);
|
layout->addWidget(ui_objects.optional_checkbox, row, kOptionalCheckBox);
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add descriptor label
|
// Add descriptor label
|
||||||
ui_objects.main_label = new QLabel();
|
ui_objects.main_label = new QLabel(this);
|
||||||
|
|
||||||
// Create input label
|
// Create input label
|
||||||
layout->addWidget(ui_objects.main_label, row, kLabelColumn);
|
layout->addWidget(ui_objects.main_label, row, kLabelColumn);
|
||||||
@@ -205,7 +205,7 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
|
|||||||
if (element == -1) {
|
if (element == -1) {
|
||||||
|
|
||||||
// Create a collapse toggle for expanding/collapsing the array
|
// Create a collapse toggle for expanding/collapsing the array
|
||||||
CollapseButton* array_collapse_btn = new CollapseButton();
|
CollapseButton* array_collapse_btn = new CollapseButton(this);
|
||||||
|
|
||||||
// Default to collapsed
|
// Default to collapsed
|
||||||
array_collapse_btn->setChecked(false);
|
array_collapse_btn->setChecked(false);
|
||||||
@@ -220,8 +220,8 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
NodeParamViewArrayButton* insert_element_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kAdd);
|
NodeParamViewArrayButton* insert_element_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kAdd, this);
|
||||||
NodeParamViewArrayButton* remove_element_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kRemove);
|
NodeParamViewArrayButton* remove_element_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kRemove, this);
|
||||||
|
|
||||||
layout->addWidget(insert_element_btn, row, kArrayInsertColumn);
|
layout->addWidget(insert_element_btn, row, kArrayInsertColumn);
|
||||||
layout->addWidget(remove_element_btn, row, kArrayRemoveColumn);
|
layout->addWidget(remove_element_btn, row, kArrayRemoveColumn);
|
||||||
@@ -249,14 +249,14 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
|
|||||||
|
|
||||||
if (node->IsInputConnectable(input)) {
|
if (node->IsInputConnectable(input)) {
|
||||||
// Create clickable label used when an input is connected
|
// Create clickable label used when an input is connected
|
||||||
ui_objects.connected_label = new NodeParamViewConnectedLabel(resolved);
|
ui_objects.connected_label = new NodeParamViewConnectedLabel(resolved, this);
|
||||||
connect(ui_objects.connected_label, &NodeParamViewConnectedLabel::RequestSelectNode, this, &NodeParamViewItemBody::RequestSelectNode);
|
connect(ui_objects.connected_label, &NodeParamViewConnectedLabel::RequestSelectNode, this, &NodeParamViewItemBody::RequestSelectNode);
|
||||||
layout->addWidget(ui_objects.connected_label, row, kWidgetStartColumn, 1, kKeyControlColumn - kWidgetStartColumn);
|
layout->addWidget(ui_objects.connected_label, row, kWidgetStartColumn, 1, kKeyControlColumn - kWidgetStartColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add keyframe control to this layout if parameter is keyframable
|
// Add keyframe control to this layout if parameter is keyframable
|
||||||
if (node->IsInputKeyframable(input)) {
|
if (node->IsInputKeyframable(input)) {
|
||||||
ui_objects.key_control = new NodeParamViewKeyframeControl();
|
ui_objects.key_control = new NodeParamViewKeyframeControl(this);
|
||||||
ui_objects.key_control->SetInput(resolved);
|
ui_objects.key_control->SetInput(resolved);
|
||||||
layout->addWidget(ui_objects.key_control, row, kKeyControlColumn);
|
layout->addWidget(ui_objects.key_control, row, kKeyControlColumn);
|
||||||
connect(ui_objects.key_control, &NodeParamViewKeyframeControl::RequestSetTime, this, &NodeParamViewItemBody::RequestSetTime);
|
connect(ui_objects.key_control, &NodeParamViewKeyframeControl::RequestSetTime, this, &NodeParamViewItemBody::RequestSetTime);
|
||||||
|
|||||||
@@ -33,31 +33,31 @@ NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) :
|
|||||||
{
|
{
|
||||||
QHBoxLayout* layout = new QHBoxLayout(this);
|
QHBoxLayout* layout = new QHBoxLayout(this);
|
||||||
|
|
||||||
collapse_btn_ = new CollapseButton();
|
collapse_btn_ = new CollapseButton(this);
|
||||||
connect(collapse_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::ExpandedStateChanged);
|
connect(collapse_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::ExpandedStateChanged);
|
||||||
layout->addWidget(collapse_btn_);
|
layout->addWidget(collapse_btn_);
|
||||||
|
|
||||||
lbl_ = new QLabel();
|
lbl_ = new QLabel(this);
|
||||||
layout->addWidget(lbl_);
|
layout->addWidget(lbl_);
|
||||||
|
|
||||||
// Place next buttons on the far side
|
// Place next buttons on the far side
|
||||||
layout->addStretch();
|
layout->addStretch();
|
||||||
|
|
||||||
add_fx_btn_ = new QPushButton();
|
add_fx_btn_ = new QPushButton(this);
|
||||||
add_fx_btn_->setIcon(icon::AddEffect);
|
add_fx_btn_->setIcon(icon::AddEffect);
|
||||||
add_fx_btn_->setFixedSize(add_fx_btn_->sizeHint().height(), add_fx_btn_->sizeHint().height());
|
add_fx_btn_->setFixedSize(add_fx_btn_->sizeHint().height(), add_fx_btn_->sizeHint().height());
|
||||||
add_fx_btn_->setVisible(false);
|
add_fx_btn_->setVisible(false);
|
||||||
layout->addWidget(add_fx_btn_);
|
layout->addWidget(add_fx_btn_);
|
||||||
connect(add_fx_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::AddEffectButtonClicked);
|
connect(add_fx_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::AddEffectButtonClicked);
|
||||||
|
|
||||||
pin_btn_ = new QPushButton(QStringLiteral("P"));
|
pin_btn_ = new QPushButton(QStringLiteral("P"), this);
|
||||||
pin_btn_->setCheckable(true);
|
pin_btn_->setCheckable(true);
|
||||||
pin_btn_->setFixedSize(pin_btn_->sizeHint().height(), pin_btn_->sizeHint().height());
|
pin_btn_->setFixedSize(pin_btn_->sizeHint().height(), pin_btn_->sizeHint().height());
|
||||||
pin_btn_->setVisible(false);
|
pin_btn_->setVisible(false);
|
||||||
layout->addWidget(pin_btn_);
|
layout->addWidget(pin_btn_);
|
||||||
connect(pin_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::PinToggled);
|
connect(pin_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::PinToggled);
|
||||||
|
|
||||||
enabled_checkbox_ = new QCheckBox();
|
enabled_checkbox_ = new QCheckBox(this);
|
||||||
enabled_checkbox_->setVisible(false);
|
enabled_checkbox_->setVisible(false);
|
||||||
layout->addWidget(enabled_checkbox_);
|
layout->addWidget(enabled_checkbox_);
|
||||||
connect(enabled_checkbox_, &QCheckBox::clicked, this, &NodeParamViewItemTitleBar::EnabledCheckBoxClicked);
|
connect(enabled_checkbox_, &QCheckBox::clicked, this, &NodeParamViewItemTitleBar::EnabledCheckBoxClicked);
|
||||||
|
|||||||
@@ -33,7 +33,11 @@ class NodeParamViewKeyframeControl : public QWidget, public TimeTargetObject
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
NodeParamViewKeyframeControl(bool right_align = true, QWidget* parent = nullptr);
|
NodeParamViewKeyframeControl(bool right_align, QWidget* parent = nullptr);
|
||||||
|
NodeParamViewKeyframeControl(QWidget* parent = nullptr) :
|
||||||
|
NodeParamViewKeyframeControl(true, parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
const NodeInput& GetConnectedInput() const
|
const NodeInput& GetConnectedInput() const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -71,9 +71,11 @@ int GetSliderCount(NodeValue::Type type)
|
|||||||
|
|
||||||
void NodeParamViewWidgetBridge::CreateWidgets()
|
void NodeParamViewWidgetBridge::CreateWidgets()
|
||||||
{
|
{
|
||||||
|
QWidget *parent = dynamic_cast<QWidget*>(this->parent());
|
||||||
|
|
||||||
if (GetInnerInput().IsArray() && GetInnerInput().element() == -1) {
|
if (GetInnerInput().IsArray() && GetInnerInput().element() == -1) {
|
||||||
|
|
||||||
NodeParamViewArrayWidget* w = new NodeParamViewArrayWidget(GetInnerInput().node(), GetInnerInput().input());
|
NodeParamViewArrayWidget* w = new NodeParamViewArrayWidget(GetInnerInput().node(), GetInnerInput().input(), parent);
|
||||||
connect(w, &NodeParamViewArrayWidget::DoubleClicked, this, &NodeParamViewWidgetBridge::ArrayWidgetDoubleClicked);
|
connect(w, &NodeParamViewArrayWidget::DoubleClicked, this, &NodeParamViewWidgetBridge::ArrayWidgetDoubleClicked);
|
||||||
widgets_.append(w);
|
widgets_.append(w);
|
||||||
|
|
||||||
@@ -94,12 +96,12 @@ void NodeParamViewWidgetBridge::CreateWidgets()
|
|||||||
break;
|
break;
|
||||||
case NodeValue::kInt:
|
case NodeValue::kInt:
|
||||||
{
|
{
|
||||||
CreateSliders<IntegerSlider>(1);
|
CreateSliders<IntegerSlider>(1, parent);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NodeValue::kRational:
|
case NodeValue::kRational:
|
||||||
{
|
{
|
||||||
CreateSliders<RationalSlider>(1);
|
CreateSliders<RationalSlider>(1, parent);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NodeValue::kFloat:
|
case NodeValue::kFloat:
|
||||||
@@ -107,12 +109,12 @@ void NodeParamViewWidgetBridge::CreateWidgets()
|
|||||||
case NodeValue::kVec3:
|
case NodeValue::kVec3:
|
||||||
case NodeValue::kVec4:
|
case NodeValue::kVec4:
|
||||||
{
|
{
|
||||||
CreateSliders<FloatSlider>(GetSliderCount(t));
|
CreateSliders<FloatSlider>(GetSliderCount(t), parent);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NodeValue::kCombo:
|
case NodeValue::kCombo:
|
||||||
{
|
{
|
||||||
QComboBox* combobox = new QComboBox();
|
QComboBox* combobox = new QComboBox(parent);
|
||||||
|
|
||||||
QStringList items = GetInnerInput().GetComboBoxStrings();
|
QStringList items = GetInnerInput().GetComboBoxStrings();
|
||||||
foreach (const QString& s, items) {
|
foreach (const QString& s, items) {
|
||||||
@@ -125,21 +127,21 @@ void NodeParamViewWidgetBridge::CreateWidgets()
|
|||||||
}
|
}
|
||||||
case NodeValue::kFile:
|
case NodeValue::kFile:
|
||||||
{
|
{
|
||||||
FileField* file_field = new FileField();
|
FileField* file_field = new FileField(parent);
|
||||||
widgets_.append(file_field);
|
widgets_.append(file_field);
|
||||||
connect(file_field, &FileField::FilenameChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
connect(file_field, &FileField::FilenameChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NodeValue::kColor:
|
case NodeValue::kColor:
|
||||||
{
|
{
|
||||||
ColorButton* color_button = new ColorButton(GetInnerInput().node()->project()->color_manager());
|
ColorButton* color_button = new ColorButton(GetInnerInput().node()->project()->color_manager(), parent);
|
||||||
widgets_.append(color_button);
|
widgets_.append(color_button);
|
||||||
connect(color_button, &ColorButton::ColorChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
connect(color_button, &ColorButton::ColorChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NodeValue::kText:
|
case NodeValue::kText:
|
||||||
{
|
{
|
||||||
NodeParamViewTextEdit* line_edit = new NodeParamViewTextEdit();
|
NodeParamViewTextEdit* line_edit = new NodeParamViewTextEdit(parent);
|
||||||
widgets_.append(line_edit);
|
widgets_.append(line_edit);
|
||||||
connect(line_edit, &NodeParamViewTextEdit::textEdited, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
connect(line_edit, &NodeParamViewTextEdit::textEdited, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||||
connect(line_edit, &NodeParamViewTextEdit::RequestEditInViewer, this, &NodeParamViewWidgetBridge::RequestEditTextInViewer);
|
connect(line_edit, &NodeParamViewTextEdit::RequestEditInViewer, this, &NodeParamViewWidgetBridge::RequestEditTextInViewer);
|
||||||
@@ -147,21 +149,21 @@ void NodeParamViewWidgetBridge::CreateWidgets()
|
|||||||
}
|
}
|
||||||
case NodeValue::kBoolean:
|
case NodeValue::kBoolean:
|
||||||
{
|
{
|
||||||
QCheckBox* check_box = new QCheckBox();
|
QCheckBox* check_box = new QCheckBox(parent);
|
||||||
widgets_.append(check_box);
|
widgets_.append(check_box);
|
||||||
connect(check_box, &QCheckBox::clicked, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
connect(check_box, &QCheckBox::clicked, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NodeValue::kFont:
|
case NodeValue::kFont:
|
||||||
{
|
{
|
||||||
QFontComboBox* font_combobox = new QFontComboBox();
|
QFontComboBox* font_combobox = new QFontComboBox(parent);
|
||||||
widgets_.append(font_combobox);
|
widgets_.append(font_combobox);
|
||||||
connect(font_combobox, &QFontComboBox::currentFontChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
connect(font_combobox, &QFontComboBox::currentFontChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NodeValue::kBezier:
|
case NodeValue::kBezier:
|
||||||
{
|
{
|
||||||
BezierWidget *bezier = new BezierWidget();
|
BezierWidget *bezier = new BezierWidget(parent);
|
||||||
widgets_.append(bezier);
|
widgets_.append(bezier);
|
||||||
|
|
||||||
connect(bezier->x_slider(), &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
connect(bezier->x_slider(), &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||||
@@ -384,10 +386,10 @@ void NodeParamViewWidgetBridge::WidgetCallback()
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void NodeParamViewWidgetBridge::CreateSliders(int count)
|
void NodeParamViewWidgetBridge::CreateSliders(int count, QWidget *parent)
|
||||||
{
|
{
|
||||||
for (int i=0;i<count;i++) {
|
for (int i=0;i<count;i++) {
|
||||||
T* fs = new T();
|
T* fs = new T(parent);
|
||||||
fs->SliderBase::SetDefaultValue(GetInnerInput().GetSplitDefaultValueForTrack(i));
|
fs->SliderBase::SetDefaultValue(GetInnerInput().GetSplitDefaultValueForTrack(i));
|
||||||
fs->SetLadderElementCount(2);
|
fs->SetLadderElementCount(2);
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ private:
|
|||||||
void SetProperty(const QString &key, const QVariant &value);
|
void SetProperty(const QString &key, const QVariant &value);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void CreateSliders(int count);
|
void CreateSliders(int count, QWidget *parent);
|
||||||
|
|
||||||
void UpdateWidgetValues();
|
void UpdateWidgetValues();
|
||||||
|
|
||||||
|
|||||||
@@ -47,10 +47,10 @@ void HistogramScope::OnInit()
|
|||||||
|
|
||||||
void HistogramScope::OnDestroy()
|
void HistogramScope::OnDestroy()
|
||||||
{
|
{
|
||||||
super::OnDestroy();
|
|
||||||
|
|
||||||
pipeline_secondary_.clear();
|
pipeline_secondary_.clear();
|
||||||
texture_row_sums_ = nullptr;
|
texture_row_sums_ = nullptr;
|
||||||
|
|
||||||
|
super::OnDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
ShaderCode HistogramScope::GenerateShaderCode()
|
ShaderCode HistogramScope::GenerateShaderCode()
|
||||||
|
|||||||
@@ -52,9 +52,7 @@ void ScopeBase::DrawScope(TexturePtr managed_tex, QVariant pipeline)
|
|||||||
|
|
||||||
job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(managed_tex)));
|
job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(managed_tex)));
|
||||||
|
|
||||||
renderer()->Blit(pipeline, job, VideoParams(width(), height(),
|
renderer()->Blit(pipeline, job, GetViewportParams());
|
||||||
static_cast<VideoParams::Format>(OLIVE_CONFIG("OfflinePixelFormat").toInt()),
|
|
||||||
VideoParams::kInternalChannelCount));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScopeBase::OnInit()
|
void ScopeBase::OnInit()
|
||||||
@@ -89,11 +87,11 @@ void ScopeBase::OnPaint()
|
|||||||
|
|
||||||
void ScopeBase::OnDestroy()
|
void ScopeBase::OnDestroy()
|
||||||
{
|
{
|
||||||
super::OnDestroy();
|
|
||||||
|
|
||||||
managed_tex_ = nullptr;
|
managed_tex_ = nullptr;
|
||||||
texture_ = nullptr;
|
texture_ = nullptr;
|
||||||
pipeline_.clear();
|
pipeline_.clear();
|
||||||
|
|
||||||
|
super::OnDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,9 +72,7 @@ void WaveformScope::DrawScope(TexturePtr managed_tex, QVariant pipeline)
|
|||||||
job.Insert(QStringLiteral("ove_maintex"),
|
job.Insert(QStringLiteral("ove_maintex"),
|
||||||
NodeValue(NodeValue::kTexture, QVariant::fromValue(managed_tex)));
|
NodeValue(NodeValue::kTexture, QVariant::fromValue(managed_tex)));
|
||||||
|
|
||||||
renderer()->Blit(pipeline, job, VideoParams(width(), height(),
|
renderer()->Blit(pipeline, job, GetViewportParams());
|
||||||
static_cast<VideoParams::Format>(OLIVE_CONFIG("OfflinePixelFormat").toInt()),
|
|
||||||
VideoParams::kInternalChannelCount));
|
|
||||||
|
|
||||||
float waveform_dim_x = ceil((width() - 1.0) * waveform_scale);
|
float waveform_dim_x = ceil((width() - 1.0) * waveform_scale);
|
||||||
float waveform_dim_y = ceil((height() - 1.0) * waveform_scale);
|
float waveform_dim_y = ceil((height() - 1.0) * waveform_scale);
|
||||||
|
|||||||
@@ -539,12 +539,16 @@ void TimelineWidget::DecreaseTrackHeight()
|
|||||||
|
|
||||||
void TimelineWidget::InsertFootageAtPlayhead(const QVector<ViewerOutput*>& footage)
|
void TimelineWidget::InsertFootageAtPlayhead(const QVector<ViewerOutput*>& footage)
|
||||||
{
|
{
|
||||||
import_tool_->PlaceAt(footage, GetTime(), true);
|
auto command = new MultiUndoCommand();
|
||||||
|
import_tool_->PlaceAt(footage, GetTime(), true, command);
|
||||||
|
Core::instance()->undo_stack()->push(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimelineWidget::OverwriteFootageAtPlayhead(const QVector<ViewerOutput *> &footage)
|
void TimelineWidget::OverwriteFootageAtPlayhead(const QVector<ViewerOutput *> &footage)
|
||||||
{
|
{
|
||||||
import_tool_->PlaceAt(footage, GetTime(), false);
|
auto command = new MultiUndoCommand();
|
||||||
|
import_tool_->PlaceAt(footage, GetTime(), false, command);
|
||||||
|
Core::instance()->undo_stack()->push(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimelineWidget::ToggleLinksOnSelected()
|
void TimelineWidget::ToggleLinksOnSelected()
|
||||||
@@ -788,13 +792,14 @@ void TimelineWidget::RecordingCallback(const QString &filename, const TimeRange
|
|||||||
task.Start();
|
task.Start();
|
||||||
|
|
||||||
MultiUndoCommand *import_command = task.GetCommand();
|
MultiUndoCommand *import_command = task.GetCommand();
|
||||||
Core::instance()->undo_stack()->pushIfHasChildren(import_command);
|
|
||||||
|
|
||||||
if (task.GetImportedFootage().empty()) {
|
if (task.GetImportedFootage().empty()) {
|
||||||
qCritical() << "Failed to import recorded audio file" << filename;
|
qCritical() << "Failed to import recorded audio file" << filename;
|
||||||
} else {
|
} else {
|
||||||
import_tool_->PlaceAt({task.GetImportedFootage().front()}, time.in(), false, track.index());
|
import_tool_->PlaceAt({task.GetImportedFootage().front()}, time.in(), false, import_command, track.index());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Core::instance()->undo_stack()->pushIfHasChildren(import_command);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimelineWidget::EnableRecordingOverlay(const TimelineCoordinate &coord)
|
void TimelineWidget::EnableRecordingOverlay(const TimelineCoordinate &coord)
|
||||||
@@ -839,6 +844,95 @@ void TimelineWidget::AddTentativeSubtitleTrack()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TimelineWidget::NestSelectedClips()
|
||||||
|
{
|
||||||
|
if (!GetConnectedNode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<Block*> blocks = this->selected_blocks_;
|
||||||
|
if (blocks.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<Track::Reference> tracks(blocks.size());
|
||||||
|
QVector<TimeRange> times(blocks.size());
|
||||||
|
QVector<int> track_offset(Track::kCount, INT_MAX);
|
||||||
|
rational start_time = RATIONAL_MAX;
|
||||||
|
rational end_time = RATIONAL_MIN;
|
||||||
|
for (int i=0; i<blocks.size(); i++) {
|
||||||
|
Block *b = blocks.at(i);
|
||||||
|
|
||||||
|
Track::Reference tf = b->track()->ToReference();;
|
||||||
|
tracks[i] = tf;
|
||||||
|
times[i] = b->range();
|
||||||
|
|
||||||
|
int &to = track_offset[tf.type()];
|
||||||
|
to = std::min(to, tf.index());
|
||||||
|
|
||||||
|
start_time = std::min(start_time, b->in());
|
||||||
|
end_time = std::max(end_time, b->out());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto move_to_nest_command = new MultiUndoCommand();
|
||||||
|
|
||||||
|
// Remove blocks from this sequence
|
||||||
|
ReplaceBlocksWithGaps(blocks, false, move_to_nest_command);
|
||||||
|
|
||||||
|
// Create new sequence
|
||||||
|
Project *project = this->GetConnectedNode()->project();
|
||||||
|
Sequence *nest = Core::CreateNewSequenceForProject(tr("Nested Sequence %1"), project);
|
||||||
|
nest->SetVideoParams(GetConnectedNode()->GetVideoParams());
|
||||||
|
nest->SetAudioParams(GetConnectedNode()->GetAudioParams());
|
||||||
|
move_to_nest_command->add_child(new NodeAddCommand(project, nest));
|
||||||
|
|
||||||
|
// Add to same folder
|
||||||
|
move_to_nest_command->add_child(new FolderAddChild(this->GetConnectedNode()->folder(), nest));
|
||||||
|
|
||||||
|
// Place blocks in new sequence
|
||||||
|
for (int i=0; i<blocks.size(); i++) {
|
||||||
|
Block *b = blocks.at(i);
|
||||||
|
|
||||||
|
const TimeRange &range = times.at(i);
|
||||||
|
Track::Reference track = tracks.at(i);
|
||||||
|
|
||||||
|
move_to_nest_command->add_child(new TrackPlaceBlockCommand(nest->track_list(track.type()),
|
||||||
|
track.index() - track_offset.at(track.type()),
|
||||||
|
b, range.in() - start_time));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do this command now, because we later do checks and actions that rely on these having been done
|
||||||
|
move_to_nest_command->redo_now();
|
||||||
|
|
||||||
|
auto meta_command = new MultiUndoCommand();
|
||||||
|
meta_command->add_child(move_to_nest_command);
|
||||||
|
|
||||||
|
// Find first free track index
|
||||||
|
bool empty = false;
|
||||||
|
int index = -1;
|
||||||
|
while (!empty) {
|
||||||
|
index++;
|
||||||
|
empty = true;
|
||||||
|
for (int i=0; i<Track::kCount; i++) {
|
||||||
|
if (track_offset.at(i) == INT_MAX) {
|
||||||
|
// No clips on this track
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackList *list = sequence()->track_list(static_cast<Track::Type>(i));
|
||||||
|
if (index < list->GetTrackCount() && !list->GetTrackAt(index)->IsRangeFree(TimeRange(start_time, end_time))) {
|
||||||
|
empty = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place new sequence in this sequence
|
||||||
|
import_tool_->PlaceAt({nest}, start_time, false, meta_command, index);
|
||||||
|
|
||||||
|
Core::instance()->undo_stack()->push(meta_command);
|
||||||
|
}
|
||||||
|
|
||||||
void TimelineWidget::ClearTentativeSubtitleTrack()
|
void TimelineWidget::ClearTentativeSubtitleTrack()
|
||||||
{
|
{
|
||||||
if (subtitle_show_command_) {
|
if (subtitle_show_command_) {
|
||||||
@@ -1140,6 +1234,31 @@ void TimelineWidget::ShowContextMenu()
|
|||||||
QAction *reveal_in_project = menu.addAction(tr("Reveal in Project"));
|
QAction *reveal_in_project = menu.addAction(tr("Reveal in Project"));
|
||||||
reveal_in_project->setData(reinterpret_cast<quintptr>(clip->connected_viewer()));
|
reveal_in_project->setData(reinterpret_cast<quintptr>(clip->connected_viewer()));
|
||||||
connect(reveal_in_project, &QAction::triggered, this, &TimelineWidget::RevealInProject);
|
connect(reveal_in_project, &QAction::triggered, this, &TimelineWidget::RevealInProject);
|
||||||
|
|
||||||
|
/*if (Sequence *sequence = dynamic_cast<Sequence*>(clip->connected_viewer())) {
|
||||||
|
Menu *multicam_menu = new Menu(tr("Multi-Cam"), &menu);
|
||||||
|
menu.addMenu(multicam_menu);
|
||||||
|
|
||||||
|
QAction *multicam_enabled = multicam_menu->addAction(tr("Enabled"));
|
||||||
|
multicam_enabled->setCheckable(true);
|
||||||
|
|
||||||
|
auto mcn = sequence->FindOutputNode<MultiCamNode>();
|
||||||
|
multicam_enabled->setChecked(!mcn.empty());
|
||||||
|
|
||||||
|
multicam_menu->addSeparator();
|
||||||
|
|
||||||
|
QAction *multicam_update = multicam_menu->addAction(tr("Update"));
|
||||||
|
multicam_update->setEnabled(!mcn.empty());
|
||||||
|
|
||||||
|
if (!mcn.empty()) {
|
||||||
|
auto n = mcn.first();
|
||||||
|
multicam_enabled->setProperty("multicam", Node::PtrToValue(n));
|
||||||
|
multicam_update->setProperty("multicam", Node::PtrToValue(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(multicam_enabled, &QAction::triggered, this, &TimelineWidget::MulticamEnabledTriggered);
|
||||||
|
connect(multicam_update, &QAction::triggered, this, &TimelineWidget::MulticamUpdateTriggered);
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1371,6 +1490,20 @@ void TimelineWidget::CacheDiscard()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TimelineWidget::MulticamEnabledTriggered(bool e)
|
||||||
|
{
|
||||||
|
if (e) {
|
||||||
|
// Add multicam node
|
||||||
|
} else if (MultiCamNode *m = Node::ValueToPtr<MultiCamNode>(sender()->property("multicam"))) {
|
||||||
|
// Remove multicam node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimelineWidget::MulticamUpdateTriggered()
|
||||||
|
{
|
||||||
|
// Update multicam node
|
||||||
|
}
|
||||||
|
|
||||||
void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost)
|
void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost)
|
||||||
{
|
{
|
||||||
ghost_items_.append(ghost);
|
ghost_items_.append(ghost);
|
||||||
|
|||||||
@@ -112,6 +112,8 @@ public:
|
|||||||
|
|
||||||
void AddTentativeSubtitleTrack();
|
void AddTentativeSubtitleTrack();
|
||||||
|
|
||||||
|
void NestSelectedClips();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Timelines should always be connected to sequences
|
* @brief Timelines should always be connected to sequences
|
||||||
*/
|
*/
|
||||||
@@ -444,6 +446,9 @@ private slots:
|
|||||||
void CacheClipsInOut();
|
void CacheClipsInOut();
|
||||||
void CacheDiscard();
|
void CacheDiscard();
|
||||||
|
|
||||||
|
void MulticamEnabledTriggered(bool e);
|
||||||
|
void MulticamUpdateTriggered();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,7 +183,9 @@ void ImportTool::DragLeave(QDragLeaveEvent* event)
|
|||||||
void ImportTool::DragDrop(TimelineViewMouseEvent *event)
|
void ImportTool::DragDrop(TimelineViewMouseEvent *event)
|
||||||
{
|
{
|
||||||
if (!dragged_footage_.isEmpty()) {
|
if (!dragged_footage_.isEmpty()) {
|
||||||
DropGhosts(event->GetModifiers() & Qt::ControlModifier);
|
auto command = new MultiUndoCommand();
|
||||||
|
DropGhosts(event->GetModifiers() & Qt::ControlModifier, command);
|
||||||
|
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||||
|
|
||||||
event->accept();
|
event->accept();
|
||||||
} else {
|
} else {
|
||||||
@@ -191,7 +193,7 @@ void ImportTool::DragDrop(TimelineViewMouseEvent *event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational &start, bool insert, int track_offset)
|
void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational &start, bool insert, MultiUndoCommand *command, int track_offset)
|
||||||
{
|
{
|
||||||
DraggedFootageData refs;
|
DraggedFootageData refs;
|
||||||
|
|
||||||
@@ -199,10 +201,10 @@ void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational
|
|||||||
refs.append({f, f->GetEnabledStreamsAsReferences()});
|
refs.append({f, f->GetEnabledStreamsAsReferences()});
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaceAt(refs, start, insert, track_offset);
|
PlaceAt(refs, start, insert, command, track_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &start, bool insert, int track_offset)
|
void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &start, bool insert, MultiUndoCommand *command, int track_offset)
|
||||||
{
|
{
|
||||||
dragged_footage_ = footage;
|
dragged_footage_ = footage;
|
||||||
|
|
||||||
@@ -211,7 +213,7 @@ void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &star
|
|||||||
}
|
}
|
||||||
|
|
||||||
PrepGhosts(start, track_offset);
|
PrepGhosts(start, track_offset);
|
||||||
DropGhosts(insert);
|
DropGhosts(insert, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImportTool::FootageToGhosts(rational ghost_start, const DraggedFootageData &sorted, const rational& dest_tb, const int& track_start)
|
void ImportTool::FootageToGhosts(rational ghost_start, const DraggedFootageData &sorted, const rational& dest_tb, const int& track_start)
|
||||||
@@ -292,9 +294,9 @@ void ImportTool::PrepGhosts(const rational& frame, const int& track_index)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImportTool::DropGhosts(bool insert)
|
void ImportTool::DropGhosts(bool insert, MultiUndoCommand *parent_command)
|
||||||
{
|
{
|
||||||
MultiUndoCommand* command = new MultiUndoCommand();
|
auto command = new MultiUndoCommand();
|
||||||
|
|
||||||
if (MultiUndoCommand *c = parent()->TakeSubtitleSectionCommand()) {
|
if (MultiUndoCommand *c = parent()->TakeSubtitleSectionCommand()) {
|
||||||
command->add_child(c);
|
command->add_child(c);
|
||||||
@@ -500,7 +502,10 @@ void ImportTool::DropGhosts(bool insert)
|
|||||||
command->add_child(new OpenSequenceCommand(sequence));
|
command->add_child(new OpenSequenceCommand(sequence));
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
// Do command now because RequestInvalidatedFromConnected relies on track type, which will be
|
||||||
|
// "none" before this command is done because it won't be connected to any track
|
||||||
|
command->redo_now();
|
||||||
|
parent_command->add_child(command);
|
||||||
|
|
||||||
while (!imported_clips.empty()) {
|
while (!imported_clips.empty()) {
|
||||||
imported_clips.front()->RequestInvalidatedFromConnected();
|
imported_clips.front()->RequestInvalidatedFromConnected();
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ public:
|
|||||||
|
|
||||||
using DraggedFootageData = QVector<QPair<ViewerOutput*, QVector<Track::Reference> > >;
|
using DraggedFootageData = QVector<QPair<ViewerOutput*, QVector<Track::Reference> > >;
|
||||||
|
|
||||||
void PlaceAt(const QVector<ViewerOutput *> &footage, const rational& start, bool insert, int track_offset = 0);
|
void PlaceAt(const QVector<ViewerOutput *> &footage, const rational& start, bool insert, MultiUndoCommand *command, int track_offset = 0);
|
||||||
void PlaceAt(const DraggedFootageData &footage, const rational& start, bool insert, int track_offset = 0);
|
void PlaceAt(const DraggedFootageData &footage, const rational& start, bool insert, MultiUndoCommand *command, int track_offset = 0);
|
||||||
|
|
||||||
enum DropWithoutSequenceBehavior {
|
enum DropWithoutSequenceBehavior {
|
||||||
kDWSAsk,
|
kDWSAsk,
|
||||||
@@ -52,7 +52,7 @@ private:
|
|||||||
|
|
||||||
void PrepGhosts(const rational &frame, const int &track_index);
|
void PrepGhosts(const rational &frame, const int &track_index);
|
||||||
|
|
||||||
void DropGhosts(bool insert);
|
void DropGhosts(bool insert, MultiUndoCommand *parent_command);
|
||||||
|
|
||||||
TimelineViewGhostItem* CreateGhost(const TimeRange &range, const rational &media_in, const Track::Reference &track);
|
TimelineViewGhostItem* CreateGhost(const TimeRange &range, const rational &media_in, const Track::Reference &track);
|
||||||
|
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ bool TransitionTool::GetBlocksAtCoord(const TimelineCoordinate &coord, ClipBlock
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Block* block_at_time = t->BlockAtTime(coord.GetFrame());
|
Block* block_at_time = t->NearestBlockBeforeOrAt(coord.GetFrame());
|
||||||
if (!dynamic_cast<ClipBlock*>(block_at_time)) {
|
if (!dynamic_cast<ClipBlock*>(block_at_time)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,62 +97,68 @@ void BlockSplitCommand::undo()
|
|||||||
//
|
//
|
||||||
// BlockSplitPreservingLinksCommand
|
// BlockSplitPreservingLinksCommand
|
||||||
//
|
//
|
||||||
void BlockSplitPreservingLinksCommand::redo()
|
Block *BlockSplitPreservingLinksCommand::GetSplit(Block *original, int time_index) const
|
||||||
{
|
{
|
||||||
if (commands_.isEmpty()) {
|
if (time_index >= 0 && time_index < times_.size()) {
|
||||||
QVector< QVector<Block*> > split_blocks(times_.size());
|
int original_index = blocks_.indexOf(original);
|
||||||
|
if (original_index != -1) {
|
||||||
for (int i=0;i<times_.size();i++) {
|
return splits_.at(time_index).at(original_index);
|
||||||
const rational& time = times_.at(i);
|
|
||||||
|
|
||||||
// FIXME: I realize this isn't going to work if the times aren't ordered. I'm lazy so rather
|
|
||||||
// than writing in a sorting algorithm here, I'll just put an assert as a reminder
|
|
||||||
// if this ever becomes an issue.
|
|
||||||
Q_ASSERT(i == 0 || time > times_.at(i-1));
|
|
||||||
|
|
||||||
QVector<Block*> splits(blocks_.size());
|
|
||||||
|
|
||||||
for (int j=0;j<blocks_.size();j++) {
|
|
||||||
Block* b = blocks_.at(j);
|
|
||||||
|
|
||||||
if (b->in() < time && b->out() > time) {
|
|
||||||
BlockSplitCommand* split_command = new BlockSplitCommand(b, time);
|
|
||||||
split_command->redo_now();
|
|
||||||
splits.replace(j, split_command->new_block());
|
|
||||||
commands_.append(split_command);
|
|
||||||
} else {
|
|
||||||
splits.replace(j, nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
split_blocks.replace(i, splits);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Now that we've determined all the splits, we can relink everything
|
return nullptr;
|
||||||
for (int i=0;i<blocks_.size();i++) {
|
}
|
||||||
Block* a = blocks_.at(i);
|
|
||||||
|
|
||||||
for (int j=0;j<blocks_.size();j++) {
|
void BlockSplitPreservingLinksCommand::prepare()
|
||||||
if (i == j) {
|
{
|
||||||
continue;
|
splits_.resize(times_.size());
|
||||||
}
|
|
||||||
|
|
||||||
Block* b = blocks_.at(j);
|
for (int i=0;i<times_.size();i++) {
|
||||||
|
const rational& time = times_.at(i);
|
||||||
|
|
||||||
if (Block::AreLinked(a, b)) {
|
// FIXME: I realize this isn't going to work if the times aren't ordered. I'm lazy so rather
|
||||||
// These blocks are linked, ensure all the splits are linked too
|
// than writing in a sorting algorithm here, I'll just put an assert as a reminder
|
||||||
|
// if this ever becomes an issue.
|
||||||
|
Q_ASSERT(i == 0 || time > times_.at(i-1));
|
||||||
|
|
||||||
foreach (const QVector<Block*>& split_list, split_blocks) {
|
QVector<Block*> splits(blocks_.size());
|
||||||
NodeLinkCommand* blc = new NodeLinkCommand(split_list.at(i), split_list.at(j), true);
|
|
||||||
blc->redo_now();
|
for (int j=0;j<blocks_.size();j++) {
|
||||||
commands_.append(blc);
|
Block* b = blocks_.at(j);
|
||||||
}
|
|
||||||
}
|
if (b->in() < time && b->out() > time) {
|
||||||
|
BlockSplitCommand* split_command = new BlockSplitCommand(b, time);
|
||||||
|
split_command->redo_now();
|
||||||
|
splits.replace(j, split_command->new_block());
|
||||||
|
commands_.append(split_command);
|
||||||
|
} else {
|
||||||
|
splits.replace(j, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
for (int i=0; i<commands_.size(); i++) {
|
splits_.replace(i, splits);
|
||||||
commands_.at(i)->redo_now();
|
}
|
||||||
|
|
||||||
|
// Now that we've determined all the splits, we can relink everything
|
||||||
|
for (int i=0;i<blocks_.size();i++) {
|
||||||
|
Block* a = blocks_.at(i);
|
||||||
|
|
||||||
|
for (int j=0;j<blocks_.size();j++) {
|
||||||
|
if (i == j) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Block* b = blocks_.at(j);
|
||||||
|
|
||||||
|
if (Block::AreLinked(a, b)) {
|
||||||
|
// These blocks are linked, ensure all the splits are linked too
|
||||||
|
|
||||||
|
foreach (const QVector<Block*>& split_list, splits_) {
|
||||||
|
NodeLinkCommand* blc = new NodeLinkCommand(split_list.at(i), split_list.at(j), true);
|
||||||
|
blc->redo_now();
|
||||||
|
commands_.append(blc);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,8 +91,17 @@ public:
|
|||||||
return blocks_.first()->project();
|
return blocks_.first()->project();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Block *GetSplit(Block *original, int time_index) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void redo() override;
|
virtual void prepare() override;
|
||||||
|
|
||||||
|
virtual void redo() override
|
||||||
|
{
|
||||||
|
for (int i=0; i<commands_.size(); i++) {
|
||||||
|
commands_.at(i)->redo_now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
virtual void undo() override
|
virtual void undo() override
|
||||||
{
|
{
|
||||||
@@ -108,6 +117,8 @@ private:
|
|||||||
|
|
||||||
QVector<UndoCommand*> commands_;
|
QVector<UndoCommand*> commands_;
|
||||||
|
|
||||||
|
QVector< QVector<Block*> > splits_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class TrackSplitAtTimeCommand : public UndoCommand {
|
class TrackSplitAtTimeCommand : public UndoCommand {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ void AudioWaveformView::SetViewer(ViewerOutput *playback)
|
|||||||
pool_.clear();
|
pool_.clear();
|
||||||
pool_.waitForDone();
|
pool_.waitForDone();
|
||||||
|
|
||||||
disconnect(playback_, &ViewerOutput::ConnectedWaveformChanged, this, static_cast<void(AudioWaveformView::*)()>(&AudioWaveformView::update));
|
disconnect(playback_, &ViewerOutput::ConnectedWaveformChanged, viewport(), static_cast<void(QWidget::*)()>(&QWidget::update));
|
||||||
|
|
||||||
SetTimebase(0);
|
SetTimebase(0);
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ void AudioWaveformView::SetViewer(ViewerOutput *playback)
|
|||||||
playback_ = playback;
|
playback_ = playback;
|
||||||
|
|
||||||
if (playback_) {
|
if (playback_) {
|
||||||
connect(playback_, &ViewerOutput::ConnectedWaveformChanged, this, static_cast<void(AudioWaveformView::*)()>(&AudioWaveformView::update));
|
connect(playback_, &ViewerOutput::ConnectedWaveformChanged, viewport(), static_cast<void(QWidget::*)()>(&QWidget::update));
|
||||||
|
|
||||||
SetTimebase(playback_->GetAudioParams().sample_rate_as_time_base());
|
SetTimebase(playback_->GetAudioParams().sample_rate_as_time_base());
|
||||||
}
|
}
|
||||||
|
|||||||
+118
-11
@@ -40,10 +40,13 @@
|
|||||||
#include "node/block/gap/gap.h"
|
#include "node/block/gap/gap.h"
|
||||||
#include "node/generator/shape/shapenodebase.h"
|
#include "node/generator/shape/shapenodebase.h"
|
||||||
#include "node/project/project.h"
|
#include "node/project/project.h"
|
||||||
|
#include "panel/multicam/multicampanel.h"
|
||||||
|
#include "panel/panelmanager.h"
|
||||||
#include "render/rendermanager.h"
|
#include "render/rendermanager.h"
|
||||||
#include "viewerpreventsleep.h"
|
#include "viewerpreventsleep.h"
|
||||||
#include "widget/audiomonitor/audiomonitor.h"
|
#include "widget/audiomonitor/audiomonitor.h"
|
||||||
#include "widget/menu/menu.h"
|
#include "widget/menu/menu.h"
|
||||||
|
#include "widget/multicam/multicamdisplay.h"
|
||||||
#include "widget/nodeparamview/nodeparamviewundo.h"
|
#include "widget/nodeparamview/nodeparamviewundo.h"
|
||||||
#include "widget/timelinewidget/tool/add.h"
|
#include "widget/timelinewidget/tool/add.h"
|
||||||
#include "widget/timeruler/timeruler.h"
|
#include "widget/timeruler/timeruler.h"
|
||||||
@@ -62,7 +65,7 @@ const rational ViewerWidget::kAudioPlaybackInterval = rational(1, 4);
|
|||||||
|
|
||||||
const rational kVideoPlaybackInterval = rational(1, 2);
|
const rational kVideoPlaybackInterval = rational(1, 2);
|
||||||
|
|
||||||
ViewerWidget::ViewerWidget(QWidget *parent) :
|
ViewerWidget::ViewerWidget(ViewerDisplayWidget *display, QWidget *parent) :
|
||||||
super(false, true, parent),
|
super(false, true, parent),
|
||||||
playback_speed_(0),
|
playback_speed_(0),
|
||||||
color_menu_enabled_(true),
|
color_menu_enabled_(true),
|
||||||
@@ -74,7 +77,8 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
|
|||||||
first_requeue_watcher_(nullptr),
|
first_requeue_watcher_(nullptr),
|
||||||
enable_audio_scrubbing_(true),
|
enable_audio_scrubbing_(true),
|
||||||
waveform_mode_(kWFAutomatic),
|
waveform_mode_(kWFAutomatic),
|
||||||
ignore_scrub_(0)
|
ignore_scrub_(0),
|
||||||
|
multicam_panel_(nullptr)
|
||||||
{
|
{
|
||||||
// Set up main layout
|
// Set up main layout
|
||||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||||
@@ -82,10 +86,9 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
|
|||||||
|
|
||||||
// Create main OpenGL-based view and sizer
|
// Create main OpenGL-based view and sizer
|
||||||
sizer_ = new ViewerSizer();
|
sizer_ = new ViewerSizer();
|
||||||
sizer_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
||||||
layout->addWidget(sizer_);
|
layout->addWidget(sizer_);
|
||||||
|
|
||||||
display_widget_ = new ViewerDisplayWidget();
|
display_widget_ = display;
|
||||||
display_widget_->SetShowWidgetBackground(true);
|
display_widget_->SetShowWidgetBackground(true);
|
||||||
playback_devices_.append(display_widget_);
|
playback_devices_.append(display_widget_);
|
||||||
connect(display_widget_, &ViewerDisplayWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu);
|
connect(display_widget_, &ViewerDisplayWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu);
|
||||||
@@ -257,10 +260,16 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n)
|
|||||||
disconnect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedVideoRange);
|
disconnect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedVideoRange);
|
||||||
disconnect(n, &ViewerOutput::TextureInputChanged, this, &ViewerWidget::UpdateWaveformViewFromMode);
|
disconnect(n, &ViewerOutput::TextureInputChanged, this, &ViewerWidget::UpdateWaveformViewFromMode);
|
||||||
|
|
||||||
|
timeline_selected_blocks_.clear();
|
||||||
|
node_view_selected_.clear();
|
||||||
|
if (multicam_panel_) {
|
||||||
|
multicam_panel_->SetMulticamNode(nullptr, nullptr, nullptr, rational::NaN);
|
||||||
|
}
|
||||||
|
|
||||||
CloseAudioProcessor();
|
CloseAudioProcessor();
|
||||||
audio_scrub_watchers_.clear();
|
audio_scrub_watchers_.clear();
|
||||||
|
|
||||||
SetDisplayImage(QVariant());
|
SetDisplayImage(nullptr);
|
||||||
|
|
||||||
ruler()->SetPlaybackCache(nullptr);
|
ruler()->SetPlaybackCache(nullptr);
|
||||||
|
|
||||||
@@ -416,6 +425,11 @@ void ViewerWidget::StartCapture(TimelineWidget *source, const TimeRange &time, c
|
|||||||
recording_track_ = track;
|
recording_track_ = track;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ViewerWidget::ConnectMulticamWidget(MulticamWidget *p)
|
||||||
|
{
|
||||||
|
multicam_panel_ = p;
|
||||||
|
}
|
||||||
|
|
||||||
FramePtr ViewerWidget::DecodeCachedImage(const QString &cache_path, const QUuid &cache_id, const int64_t& time)
|
FramePtr ViewerWidget::DecodeCachedImage(const QString &cache_path, const QUuid &cache_id, const int64_t& time)
|
||||||
{
|
{
|
||||||
FramePtr frame = FrameHashCache::LoadCacheFrame(cache_path, cache_id, time);
|
FramePtr frame = FrameHashCache::LoadCacheFrame(cache_path, cache_id, time);
|
||||||
@@ -580,7 +594,7 @@ void ViewerWidget::RequestNextDryRun()
|
|||||||
} else {
|
} else {
|
||||||
RenderTicketWatcher *watcher = new RenderTicketWatcher(this);
|
RenderTicketWatcher *watcher = new RenderTicketWatcher(this);
|
||||||
connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::DryRunFinished);
|
connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::DryRunFinished);
|
||||||
watcher->SetTicket(auto_cacher_->GetSingleFrame(next_time, true));
|
watcher->SetTicket(GetSingleFrame(next_time, true));
|
||||||
dry_run_next_frame_ += playback_speed_;
|
dry_run_next_frame_ += playback_speed_;
|
||||||
dry_run_watchers_.append(watcher);
|
dry_run_watchers_.append(watcher);
|
||||||
}
|
}
|
||||||
@@ -604,6 +618,75 @@ void ViewerWidget::SetWaveformMode(WaveformMode wf)
|
|||||||
UpdateWaveformViewFromMode();
|
UpdateWaveformViewFromMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ViewerWidget::DetectMulticamNode(const rational &time)
|
||||||
|
{
|
||||||
|
// Look for multicam node
|
||||||
|
MultiCamNode *multicam = nullptr;
|
||||||
|
ClipBlock *clip = nullptr;
|
||||||
|
|
||||||
|
// Faster way to do this
|
||||||
|
if (multicam_panel_ && multicam_panel_->isVisible()) {
|
||||||
|
if (Sequence *s = dynamic_cast<Sequence*>(GetConnectedNode())) {
|
||||||
|
// Prefer selected nodes
|
||||||
|
for (Node *n : qAsConst(node_view_selected_)) {
|
||||||
|
if ((multicam = dynamic_cast<MultiCamNode*>(n))) {
|
||||||
|
// Found multicam, now try to find corresponding clip from selected timeline blocks
|
||||||
|
for (Block *b : qAsConst(timeline_selected_blocks_)) {
|
||||||
|
if (ClipBlock *c = dynamic_cast<ClipBlock*>(b)) {
|
||||||
|
if (c->range().Contains(time) && c->ContextContainsNode(multicam)) {
|
||||||
|
clip = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, prefer multicam from selected block
|
||||||
|
if (!multicam) {
|
||||||
|
for (Block *b : qAsConst(timeline_selected_blocks_)) {
|
||||||
|
if (b->range().Contains(time)) {
|
||||||
|
if ((clip = dynamic_cast<ClipBlock*>(b))) {
|
||||||
|
if ((multicam = clip->FindMulticam())) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!multicam) {
|
||||||
|
const QVector<Track*> &tracks = s->GetTracks();
|
||||||
|
for (Track *t : tracks) {
|
||||||
|
if (t->IsLocked()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Block *b = t->NearestBlockBeforeOrAt(time);
|
||||||
|
if ((clip = dynamic_cast<ClipBlock*>(b))) {
|
||||||
|
if ((multicam = clip->FindMulticam())) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (multicam) {
|
||||||
|
if (multicam_panel_) {
|
||||||
|
multicam_panel_->SetMulticamNode(GetConnectedNode(), multicam, clip, time);
|
||||||
|
}
|
||||||
|
auto_cacher()->SetMulticamNode(multicam);
|
||||||
|
} else {
|
||||||
|
auto_cacher()->SetMulticamNode(nullptr);
|
||||||
|
if (multicam_panel_) {
|
||||||
|
multicam_panel_->SetMulticamNode(nullptr, nullptr, nullptr, time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ViewerWidget::UpdateWaveformViewFromMode()
|
void ViewerWidget::UpdateWaveformViewFromMode()
|
||||||
{
|
{
|
||||||
bool prefer_waveform = ShouldForceWaveform();
|
bool prefer_waveform = ShouldForceWaveform();
|
||||||
@@ -790,6 +873,8 @@ void ViewerWidget::UpdateTextureFromNode()
|
|||||||
// Clear queue because we want this frame more than any others
|
// Clear queue because we want this frame more than any others
|
||||||
auto_cacher_->ClearSingleFrameRenders();
|
auto_cacher_->ClearSingleFrameRenders();
|
||||||
|
|
||||||
|
DetectMulticamNode(time);
|
||||||
|
|
||||||
watcher->SetTicket(GetFrame(time));
|
watcher->SetTicket(GetFrame(time));
|
||||||
} else {
|
} else {
|
||||||
// There is definitely no frame here, we can immediately flip to showing nothing
|
// There is definitely no frame here, we can immediately flip to showing nothing
|
||||||
@@ -998,10 +1083,18 @@ bool ViewerWidget::ViewerMightBeAStill()
|
|||||||
return GetConnectedNode() && GetConnectedNode()->GetConnectedTextureOutput() && GetConnectedNode()->GetVideoLength().isNull();
|
return GetConnectedNode() && GetConnectedNode()->GetConnectedTextureOutput() && GetConnectedNode()->GetVideoLength().isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewerWidget::SetDisplayImage(QVariant frame)
|
void ViewerWidget::SetDisplayImage(RenderTicketPtr ticket)
|
||||||
{
|
{
|
||||||
foreach (ViewerDisplayWidget *dw, playback_devices_) {
|
foreach (ViewerDisplayWidget *dw, playback_devices_) {
|
||||||
dw->SetImage(frame);
|
QVariant push;
|
||||||
|
if (ticket) {
|
||||||
|
if (dynamic_cast<MulticamDisplay*>(dw)) {
|
||||||
|
push = ticket->property("multicam_output");
|
||||||
|
} else {
|
||||||
|
push = ticket->Get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dw->SetImage(push);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1019,6 +1112,7 @@ RenderTicketWatcher *ViewerWidget::RequestNextFrameForQueue(bool increment)
|
|||||||
|
|
||||||
watcher = new RenderTicketWatcher();
|
watcher = new RenderTicketWatcher();
|
||||||
watcher->setProperty("time", QVariant::fromValue(next_time));
|
watcher->setProperty("time", QVariant::fromValue(next_time));
|
||||||
|
DetectMulticamNode(next_time);
|
||||||
connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrameForQueue);
|
connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrameForQueue);
|
||||||
queue_watchers_.append(watcher);
|
queue_watchers_.append(watcher);
|
||||||
watcher->SetTicket(GetFrame(next_time));
|
watcher->SetTicket(GetFrame(next_time));
|
||||||
@@ -1033,7 +1127,7 @@ RenderTicketPtr ViewerWidget::GetFrame(const rational &t)
|
|||||||
|
|
||||||
if (!QFileInfo::exists(cache_fn)) {
|
if (!QFileInfo::exists(cache_fn)) {
|
||||||
// Frame hasn't been cached, start render job
|
// Frame hasn't been cached, start render job
|
||||||
return auto_cacher_->GetSingleFrame(t);
|
return GetSingleFrame(t);
|
||||||
} else {
|
} else {
|
||||||
// Frame has been cached, grab the frame
|
// Frame has been cached, grab the frame
|
||||||
RenderTicketPtr ticket = std::make_shared<RenderTicket>();
|
RenderTicketPtr ticket = std::make_shared<RenderTicket>();
|
||||||
@@ -1160,7 +1254,7 @@ void ViewerWidget::RendererGeneratedFrame()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SetDisplayImage(ticket->Get());
|
SetDisplayImage(ticket->GetTicket());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1182,7 +1276,14 @@ void ViewerWidget::RendererGeneratedFrameForQueue()
|
|||||||
rational ts = watcher->property("time").value<rational>();
|
rational ts = watcher->property("time").value<rational>();
|
||||||
|
|
||||||
foreach (ViewerDisplayWidget *dw, playback_devices_) {
|
foreach (ViewerDisplayWidget *dw, playback_devices_) {
|
||||||
dw->queue()->AppendTimewise({ts, frame}, playback_speed_);
|
QVariant push;
|
||||||
|
if (dynamic_cast<MulticamDisplay*>(dw)) {
|
||||||
|
push = watcher->GetTicket()->property("multicam_output");
|
||||||
|
} else {
|
||||||
|
push = frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
dw->queue()->AppendTimewise({ts, push}, playback_speed_);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prequeuing_video_) {
|
if (prequeuing_video_) {
|
||||||
@@ -1678,7 +1779,13 @@ void ViewerWidget::UpdateRendererVideoParameters()
|
|||||||
|
|
||||||
void ViewerWidget::UpdateRendererAudioParameters()
|
void ViewerWidget::UpdateRendererAudioParameters()
|
||||||
{
|
{
|
||||||
|
AudioParams ap = GetConnectedNode()->GetAudioParams();
|
||||||
|
|
||||||
UpdateAudioProcessor();
|
UpdateAudioProcessor();
|
||||||
|
|
||||||
|
foreach (ViewerDisplayWidget *dw, playback_devices_) {
|
||||||
|
dw->SetAudioParams(ap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewerWidget::SetZoomFromMenu(QAction *action)
|
void ViewerWidget::SetZoomFromMenu(QAction *action)
|
||||||
|
|||||||
@@ -43,6 +43,8 @@
|
|||||||
|
|
||||||
namespace olive {
|
namespace olive {
|
||||||
|
|
||||||
|
class MulticamWidget;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief An OpenGL-based viewer widget with playback controls (a PlaybackControls widget).
|
* @brief An OpenGL-based viewer widget with playback controls (a PlaybackControls widget).
|
||||||
*/
|
*/
|
||||||
@@ -57,7 +59,9 @@ public:
|
|||||||
kWFViewerAndWaveform
|
kWFViewerAndWaveform
|
||||||
};
|
};
|
||||||
|
|
||||||
ViewerWidget(QWidget* parent = nullptr);
|
ViewerWidget(QWidget* parent = nullptr) :
|
||||||
|
ViewerWidget(new ViewerDisplayWidget(), parent)
|
||||||
|
{}
|
||||||
|
|
||||||
virtual ~ViewerWidget() override;
|
virtual ~ViewerWidget() override;
|
||||||
|
|
||||||
@@ -100,6 +104,37 @@ public:
|
|||||||
enable_audio_scrubbing_ = e;
|
enable_audio_scrubbing_ = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PreviewAutoCacher *GetCacher() const { return auto_cacher_; }
|
||||||
|
|
||||||
|
void AddPlaybackDevice(ViewerDisplayWidget *vw)
|
||||||
|
{
|
||||||
|
playback_devices_.push_back(vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetTimelineSelectedBlocks(const QVector<Block*> &b)
|
||||||
|
{
|
||||||
|
timeline_selected_blocks_ = b;
|
||||||
|
|
||||||
|
if (!IsPlaying()) {
|
||||||
|
// If is playing, this will happen by the next frame automatically
|
||||||
|
DetectMulticamNode(GetTime());
|
||||||
|
UpdateTextureFromNode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetNodeViewSelections(const QVector<Node*> &n)
|
||||||
|
{
|
||||||
|
node_view_selected_ = n;
|
||||||
|
|
||||||
|
if (!IsPlaying()) {
|
||||||
|
// If is playing, this will happen by the next frame automatically
|
||||||
|
DetectMulticamNode(GetTime());
|
||||||
|
UpdateTextureFromNode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConnectMulticamWidget(MulticamWidget *p);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void Play(bool in_to_out_only);
|
void Play(bool in_to_out_only);
|
||||||
|
|
||||||
@@ -157,6 +192,8 @@ signals:
|
|||||||
void ColorManagerChanged(ColorManager* color_manager);
|
void ColorManagerChanged(ColorManager* color_manager);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
ViewerWidget(ViewerDisplayWidget *display, QWidget* parent = nullptr);
|
||||||
|
|
||||||
virtual void TimebaseChangedEvent(const rational &) override;
|
virtual void TimebaseChangedEvent(const rational &) override;
|
||||||
virtual void TimeChangedEvent(const rational &time) override;
|
virtual void TimeChangedEvent(const rational &time) override;
|
||||||
|
|
||||||
@@ -182,6 +219,13 @@ protected:
|
|||||||
ignore_scrub_++;
|
ignore_scrub_++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual RenderTicketPtr GetSingleFrame(const rational &t, bool dry = false)
|
||||||
|
{
|
||||||
|
return auto_cacher_->GetSingleFrame(t, dry);
|
||||||
|
}
|
||||||
|
|
||||||
|
PreviewAutoCacher *auto_cacher() const { return auto_cacher_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int64_t GetTimestamp() const
|
int64_t GetTimestamp() const
|
||||||
{
|
{
|
||||||
@@ -206,7 +250,7 @@ private:
|
|||||||
|
|
||||||
bool ViewerMightBeAStill();
|
bool ViewerMightBeAStill();
|
||||||
|
|
||||||
void SetDisplayImage(QVariant frame);
|
void SetDisplayImage(RenderTicketPtr ticket);
|
||||||
|
|
||||||
RenderTicketWatcher *RequestNextFrameForQueue(bool increment = true);
|
RenderTicketWatcher *RequestNextFrameForQueue(bool increment = true);
|
||||||
|
|
||||||
@@ -236,6 +280,8 @@ private:
|
|||||||
|
|
||||||
void SetWaveformMode(WaveformMode wf);
|
void SetWaveformMode(WaveformMode wf);
|
||||||
|
|
||||||
|
void DetectMulticamNode(const rational &time);
|
||||||
|
|
||||||
ViewerSizer* sizer_;
|
ViewerSizer* sizer_;
|
||||||
|
|
||||||
int playback_speed_;
|
int playback_speed_;
|
||||||
@@ -304,6 +350,11 @@ private:
|
|||||||
|
|
||||||
int ignore_scrub_;
|
int ignore_scrub_;
|
||||||
|
|
||||||
|
QVector<Block*> timeline_selected_blocks_;
|
||||||
|
QVector<Node*> node_view_selected_;
|
||||||
|
|
||||||
|
MulticamWidget *multicam_panel_;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void PlaybackTimerUpdate();
|
void PlaybackTimerUpdate();
|
||||||
|
|
||||||
|
|||||||
@@ -191,6 +191,15 @@ void ViewerDisplayWidget::SetVideoParams(const VideoParams ¶ms)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ViewerDisplayWidget::SetAudioParams(const AudioParams ¶ms)
|
||||||
|
{
|
||||||
|
gizmo_audio_params_ = params;
|
||||||
|
|
||||||
|
if (gizmos_) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ViewerDisplayWidget::SetTime(const rational &time)
|
void ViewerDisplayWidget::SetTime(const rational &time)
|
||||||
{
|
{
|
||||||
time_ = time;
|
time_ = time;
|
||||||
@@ -361,10 +370,7 @@ void ViewerDisplayWidget::OnPaint()
|
|||||||
// We only draw if we have a pipeline
|
// We only draw if we have a pipeline
|
||||||
if (push_mode_ != kPushNull) {
|
if (push_mode_ != kPushNull) {
|
||||||
// Draw texture through color transform
|
// Draw texture through color transform
|
||||||
int device_width = width() * devicePixelRatioF();
|
VideoParams device_params = GetViewportParams();
|
||||||
int device_height = height() * devicePixelRatioF();
|
|
||||||
VideoParams::Format device_format = static_cast<VideoParams::Format>(OLIVE_CONFIG("OfflinePixelFormat").toInt());
|
|
||||||
VideoParams device_params(device_width, device_height, device_format, VideoParams::kInternalChannelCount);
|
|
||||||
|
|
||||||
if (push_mode_ == kPushBlank) {
|
if (push_mode_ == kPushBlank) {
|
||||||
if (blank_shader_.isNull()) {
|
if (blank_shader_.isNull()) {
|
||||||
@@ -392,6 +398,8 @@ void ViewerDisplayWidget::OnPaint()
|
|||||||
} else if (TexturePtr texture = load_frame_.value<TexturePtr>()) {
|
} else if (TexturePtr texture = load_frame_.value<TexturePtr>()) {
|
||||||
// This is a GPU texture, switch to it directly
|
// This is a GPU texture, switch to it directly
|
||||||
texture_ = texture;
|
texture_ = texture;
|
||||||
|
} else {
|
||||||
|
texture_ = LoadCustomTextureFromFrame(load_frame_);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit TextureChanged(texture_);
|
emit TextureChanged(texture_);
|
||||||
@@ -434,17 +442,13 @@ void ViewerDisplayWidget::OnPaint()
|
|||||||
|
|
||||||
// Draw gizmos if we have any
|
// Draw gizmos if we have any
|
||||||
if (gizmos_) {
|
if (gizmos_) {
|
||||||
NodeTraverser gt;
|
|
||||||
gt.SetCacheVideoParams(gizmo_params_);
|
|
||||||
|
|
||||||
TimeRange range = GenerateGizmoTime();
|
|
||||||
gizmo_db_ = gt.GenerateRow(gizmos_, range);
|
|
||||||
|
|
||||||
QPainter p(paint_device());
|
QPainter p(paint_device());
|
||||||
gizmo_last_draw_transform_ = GenerateGizmoTransform(gt, range);
|
|
||||||
|
GenerateGizmoTransforms();
|
||||||
|
|
||||||
p.setWorldTransform(gizmo_last_draw_transform_);
|
p.setWorldTransform(gizmo_last_draw_transform_);
|
||||||
|
|
||||||
gizmos_->UpdateGizmoPositions(gizmo_db_, NodeTraverser::GenerateGlobals(gizmo_params_, range));
|
gizmos_->UpdateGizmoPositions(gizmo_db_, NodeTraverser::GenerateGlobals(gizmo_params_, gizmo_audio_params_, gizmo_draw_time_));
|
||||||
foreach (NodeGizmo *gizmo, gizmos_->GetGizmos()) {
|
foreach (NodeGizmo *gizmo, gizmos_->GetGizmos()) {
|
||||||
if (gizmo->IsVisible()) {
|
if (gizmo->IsVisible()) {
|
||||||
gizmo->Draw(&p);
|
gizmo->Draw(&p);
|
||||||
@@ -815,18 +819,17 @@ bool ViewerDisplayWidget::OnMousePress(QMouseEvent *event)
|
|||||||
add_band_ = true;
|
add_band_ = true;
|
||||||
|
|
||||||
} else if (gizmos_
|
} else if (gizmos_
|
||||||
&& (gizmo_last_draw_transform_inverted_ = gizmo_last_draw_transform_.inverted(),
|
&& (current_gizmo_ = TryGizmoPress(gizmo_db_, gizmo_last_draw_transform_inverted_.map(event->pos())))) {
|
||||||
current_gizmo_ = TryGizmoPress(gizmo_db_, gizmo_last_draw_transform_inverted_.map(event->pos())))) {
|
|
||||||
|
|
||||||
// Handle gizmo click
|
// Handle gizmo click
|
||||||
gizmo_start_drag_ = event->pos();
|
gizmo_start_drag_ = event->pos();
|
||||||
gizmo_last_drag_ = gizmo_start_drag_;
|
gizmo_last_drag_ = gizmo_start_drag_;
|
||||||
current_gizmo_->SetGlobals(NodeTraverser::GenerateGlobals(gizmo_params_, GenerateGizmoTime()));
|
current_gizmo_->SetGlobals(NodeTraverser::GenerateGlobals(gizmo_params_, gizmo_audio_params_, GenerateGizmoTime()));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Handle standard drag
|
// Handle standard drag
|
||||||
emit DragStarted();
|
emit DragStarted(event->pos());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -874,28 +877,29 @@ bool ViewerDisplayWidget::OnMouseMove(QMouseEvent *event)
|
|||||||
// Signal movement
|
// Signal movement
|
||||||
if (DraggableGizmo *draggable = dynamic_cast<DraggableGizmo*>(current_gizmo_)) {
|
if (DraggableGizmo *draggable = dynamic_cast<DraggableGizmo*>(current_gizmo_)) {
|
||||||
if (!gizmo_drag_started_) {
|
if (!gizmo_drag_started_) {
|
||||||
QPointF start = gizmo_start_drag_ * gizmo_last_draw_transform_inverted_;
|
QPointF start = ScreenToScenePoint(gizmo_start_drag_);
|
||||||
|
|
||||||
rational gizmo_time = GetGizmoTime();
|
rational gizmo_time = GetGizmoTime();
|
||||||
NodeTraverser t;
|
NodeTraverser t;
|
||||||
t.SetCacheVideoParams(gizmo_params_);
|
t.SetCacheVideoParams(gizmo_params_);
|
||||||
|
t.SetCacheAudioParams(gizmo_audio_params_);
|
||||||
NodeValueRow row = t.GenerateRow(gizmos_, TimeRange(gizmo_time, gizmo_time + gizmo_params_.frame_rate_as_time_base()));
|
NodeValueRow row = t.GenerateRow(gizmos_, TimeRange(gizmo_time, gizmo_time + gizmo_params_.frame_rate_as_time_base()));
|
||||||
|
|
||||||
draggable->DragStart(row, start.x(), start.y(), gizmo_time);
|
draggable->DragStart(row, start.x(), start.y(), gizmo_time);
|
||||||
gizmo_drag_started_ = true;
|
gizmo_drag_started_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPointF v = event->pos() * gizmo_last_draw_transform_inverted_;
|
QPointF v = ScreenToScenePoint(event->pos());
|
||||||
switch (draggable->GetDragValueBehavior()) {
|
switch (draggable->GetDragValueBehavior()) {
|
||||||
case DraggableGizmo::kAbsolute:
|
case DraggableGizmo::kAbsolute:
|
||||||
// Above value is correct
|
// Above value is correct
|
||||||
break;
|
break;
|
||||||
case DraggableGizmo::kDeltaFromPrevious:
|
case DraggableGizmo::kDeltaFromPrevious:
|
||||||
v -= gizmo_last_drag_ * gizmo_last_draw_transform_inverted_;
|
v -= ScreenToScenePoint(gizmo_last_drag_);
|
||||||
gizmo_last_drag_ = event->pos();
|
gizmo_last_drag_ = event->pos();
|
||||||
break;
|
break;
|
||||||
case DraggableGizmo::kDeltaFromStart:
|
case DraggableGizmo::kDeltaFromStart:
|
||||||
v -= gizmo_start_drag_ * gizmo_last_draw_transform_inverted_;
|
v -= ScreenToScenePoint(gizmo_start_drag_);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1056,7 +1060,7 @@ void ViewerDisplayWidget::DrawSubtitleTracks()
|
|||||||
for (int j=subtitle_tracklist.size()-1; j>=0; j--) {
|
for (int j=subtitle_tracklist.size()-1; j>=0; j--) {
|
||||||
Track *sub_track = subtitle_tracklist.at(j);
|
Track *sub_track = subtitle_tracklist.at(j);
|
||||||
if (!sub_track->IsMuted()) {
|
if (!sub_track->IsMuted()) {
|
||||||
if (SubtitleBlock *sub = dynamic_cast<SubtitleBlock*>(sub_track->BlockAtTime(time_))) {
|
if (SubtitleBlock *sub = dynamic_cast<SubtitleBlock*>(sub_track->VisibleBlockAtTime(time_))) {
|
||||||
// Split into lines
|
// Split into lines
|
||||||
QStringList list = QtUtils::WordWrapString(sub->GetText(), fm, bounding_box.width());
|
QStringList list = QtUtils::WordWrapString(sub->GetText(), fm, bounding_box.width());
|
||||||
|
|
||||||
@@ -1185,6 +1189,22 @@ void ViewerDisplayWidget::CloseTextEditor()
|
|||||||
text_edit_ = nullptr;
|
text_edit_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ViewerDisplayWidget::GenerateGizmoTransforms()
|
||||||
|
{
|
||||||
|
NodeTraverser gt;
|
||||||
|
gt.SetCacheVideoParams(gizmo_params_);
|
||||||
|
gt.SetCacheAudioParams(gizmo_audio_params_);
|
||||||
|
|
||||||
|
gizmo_draw_time_ = GenerateGizmoTime();
|
||||||
|
|
||||||
|
if (gizmos_) {
|
||||||
|
gizmo_db_ = gt.GenerateRow(gizmos_, gizmo_draw_time_);
|
||||||
|
}
|
||||||
|
|
||||||
|
gizmo_last_draw_transform_ = GenerateGizmoTransform(gt, gizmo_draw_time_);
|
||||||
|
gizmo_last_draw_transform_inverted_ = gizmo_last_draw_transform_.inverted();
|
||||||
|
}
|
||||||
|
|
||||||
void ViewerDisplayWidget::SetShowFPS(bool e)
|
void ViewerDisplayWidget::SetShowFPS(bool e)
|
||||||
{
|
{
|
||||||
show_fps_ = e;
|
show_fps_ = e;
|
||||||
@@ -1224,6 +1244,15 @@ void ViewerDisplayWidget::Pause()
|
|||||||
queue_starved_ = false;
|
queue_starved_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPointF ViewerDisplayWidget::ScreenToScenePoint(const QPoint &p)
|
||||||
|
{
|
||||||
|
if (gizmo_last_draw_transform_.isIdentity()) {
|
||||||
|
GenerateGizmoTransforms();
|
||||||
|
}
|
||||||
|
|
||||||
|
return p * gizmo_last_draw_transform_inverted_;
|
||||||
|
}
|
||||||
|
|
||||||
void ViewerDisplayWidget::UpdateFromQueue()
|
void ViewerDisplayWidget::UpdateFromQueue()
|
||||||
{
|
{
|
||||||
int64_t t = timer_.GetTimestampNow();
|
int64_t t = timer_.GetTimestampNow();
|
||||||
|
|||||||
@@ -74,7 +74,13 @@ public:
|
|||||||
void SetSafeMargins(const ViewerSafeMarginInfo& safe_margin);
|
void SetSafeMargins(const ViewerSafeMarginInfo& safe_margin);
|
||||||
|
|
||||||
void SetGizmos(Node* node);
|
void SetGizmos(Node* node);
|
||||||
|
|
||||||
|
const VideoParams &GetVideoParams() const { return gizmo_params_; }
|
||||||
void SetVideoParams(const VideoParams ¶ms);
|
void SetVideoParams(const VideoParams ¶ms);
|
||||||
|
|
||||||
|
const AudioParams &GetAudioParams() const { return gizmo_audio_params_; }
|
||||||
|
void SetAudioParams(const AudioParams &p);
|
||||||
|
|
||||||
void SetTime(const rational& time);
|
void SetTime(const rational& time);
|
||||||
void SetSubtitleTracks(Sequence *list);
|
void SetSubtitleTracks(Sequence *list);
|
||||||
|
|
||||||
@@ -131,6 +137,8 @@ public:
|
|||||||
return &timer_;
|
return &timer_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPointF ScreenToScenePoint(const QPoint &p);
|
||||||
|
|
||||||
virtual bool eventFilter(QObject *o, QEvent *e) override;
|
virtual bool eventFilter(QObject *o, QEvent *e) override;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
@@ -182,7 +190,7 @@ signals:
|
|||||||
/**
|
/**
|
||||||
* @brief Signal emitted when the user starts dragging from the viewer
|
* @brief Signal emitted when the user starts dragging from the viewer
|
||||||
*/
|
*/
|
||||||
void DragStarted();
|
void DragStarted(const QPoint &p);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Signal emitted when a hand drag starts
|
* @brief Signal emitted when a hand drag starts
|
||||||
@@ -218,6 +226,30 @@ signals:
|
|||||||
|
|
||||||
void CreateAddableAt(const QRectF &rect);
|
void CreateAddableAt(const QRectF &rect);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QTransform GenerateWorldTransform();
|
||||||
|
|
||||||
|
QTransform GenerateDisplayTransform();
|
||||||
|
|
||||||
|
QTransform GenerateGizmoTransform(NodeTraverser >, const TimeRange &range);
|
||||||
|
QTransform GenerateGizmoTransform()
|
||||||
|
{
|
||||||
|
NodeTraverser t;
|
||||||
|
t.SetCacheVideoParams(gizmo_params_);
|
||||||
|
return GenerateGizmoTransform(t, GenerateGizmoTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeRange GenerateGizmoTime()
|
||||||
|
{
|
||||||
|
rational node_time = GetGizmoTime();
|
||||||
|
return TimeRange(node_time, node_time + gizmo_params_.frame_rate_as_time_base());
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual TexturePtr LoadCustomTextureFromFrame(const QVariant &v)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
/**
|
/**
|
||||||
* @brief Paint function to display the texture (received in SetTexture()) on screen.
|
* @brief Paint function to display the texture (received in SetTexture()) on screen.
|
||||||
@@ -241,24 +273,6 @@ private:
|
|||||||
|
|
||||||
void UpdateMatrix();
|
void UpdateMatrix();
|
||||||
|
|
||||||
QTransform GenerateWorldTransform();
|
|
||||||
|
|
||||||
QTransform GenerateDisplayTransform();
|
|
||||||
|
|
||||||
QTransform GenerateGizmoTransform(NodeTraverser >, const TimeRange &range);
|
|
||||||
QTransform GenerateGizmoTransform()
|
|
||||||
{
|
|
||||||
NodeTraverser t;
|
|
||||||
t.SetCacheVideoParams(gizmo_params_);
|
|
||||||
return GenerateGizmoTransform(t, GenerateGizmoTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
TimeRange GenerateGizmoTime()
|
|
||||||
{
|
|
||||||
rational node_time = GetGizmoTime();
|
|
||||||
return TimeRange(node_time, node_time + gizmo_params_.frame_rate_as_time_base());
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeGizmo *TryGizmoPress(const NodeValueRow &row, const QPointF &p);
|
NodeGizmo *TryGizmoPress(const NodeValueRow &row, const QPointF &p);
|
||||||
|
|
||||||
void OpenTextGizmo(TextGizmo *text, QMouseEvent *event = nullptr);
|
void OpenTextGizmo(TextGizmo *text, QMouseEvent *event = nullptr);
|
||||||
@@ -290,6 +304,8 @@ private:
|
|||||||
|
|
||||||
void CloseTextEditor();
|
void CloseTextEditor();
|
||||||
|
|
||||||
|
void GenerateGizmoTransforms();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL().
|
* @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL().
|
||||||
*/
|
*/
|
||||||
@@ -338,8 +354,10 @@ private:
|
|||||||
Node* gizmos_;
|
Node* gizmos_;
|
||||||
NodeValueRow gizmo_db_;
|
NodeValueRow gizmo_db_;
|
||||||
VideoParams gizmo_params_;
|
VideoParams gizmo_params_;
|
||||||
|
AudioParams gizmo_audio_params_;
|
||||||
QPoint gizmo_start_drag_;
|
QPoint gizmo_start_drag_;
|
||||||
QPoint gizmo_last_drag_;
|
QPoint gizmo_last_drag_;
|
||||||
|
TimeRange gizmo_draw_time_;
|
||||||
NodeGizmo *current_gizmo_;
|
NodeGizmo *current_gizmo_;
|
||||||
bool gizmo_drag_started_;
|
bool gizmo_drag_started_;
|
||||||
QTransform gizmo_last_draw_transform_;
|
QTransform gizmo_last_draw_transform_;
|
||||||
|
|||||||
@@ -27,12 +27,14 @@
|
|||||||
|
|
||||||
namespace olive {
|
namespace olive {
|
||||||
|
|
||||||
struct ViewerPlaybackFrame {
|
struct ViewerPlaybackFrame
|
||||||
|
{
|
||||||
rational timestamp;
|
rational timestamp;
|
||||||
QVariant frame;
|
QVariant frame;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ViewerQueue : public std::list<ViewerPlaybackFrame> {
|
class ViewerQueue : public std::list<ViewerPlaybackFrame>
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
ViewerQueue() = default;
|
ViewerQueue() = default;
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ ViewerSizer::ViewerSizer(QWidget *parent) :
|
|||||||
vert_scrollbar_ = new QScrollBar(Qt::Vertical, this);
|
vert_scrollbar_ = new QScrollBar(Qt::Vertical, this);
|
||||||
vert_scrollbar_->setVisible(false);
|
vert_scrollbar_->setVisible(false);
|
||||||
connect(vert_scrollbar_, &QScrollBar::valueChanged, this, &ViewerSizer::ScrollBarMoved);
|
connect(vert_scrollbar_, &QScrollBar::valueChanged, this, &ViewerSizer::ScrollBarMoved);
|
||||||
|
|
||||||
|
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewerSizer::SetWidget(QWidget *widget)
|
void ViewerSizer::SetWidget(QWidget *widget)
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
void SetWidget(QWidget* widget);
|
void SetWidget(QWidget* widget);
|
||||||
|
|
||||||
|
public slots:
|
||||||
/**
|
/**
|
||||||
* @brief Set resolution to use
|
* @brief Set resolution to use
|
||||||
*
|
*
|
||||||
@@ -70,7 +71,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
void SetZoom(int percent);
|
void SetZoom(int percent);
|
||||||
|
|
||||||
public slots:
|
|
||||||
void HandDragMove(int x, int y);
|
void HandDragMove(int x, int y);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||||||
param_panel_ = new ParamPanel(this);
|
param_panel_ = new ParamPanel(this);
|
||||||
curve_panel_ = new CurvePanel(this);
|
curve_panel_ = new CurvePanel(this);
|
||||||
sequence_viewer_panel_ = new SequenceViewerPanel(this);
|
sequence_viewer_panel_ = new SequenceViewerPanel(this);
|
||||||
|
multicam_panel_ = new MulticamPanel(this);
|
||||||
pixel_sampler_panel_ = new PixelSamplerPanel(this);
|
pixel_sampler_panel_ = new PixelSamplerPanel(this);
|
||||||
AppendProjectPanel();
|
AppendProjectPanel();
|
||||||
tool_panel_ = new ToolPanel(this);
|
tool_panel_ = new ToolPanel(this);
|
||||||
@@ -104,18 +105,22 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||||||
connect(param_panel_, &ParamPanel::FocusedNodeChanged, curve_panel_, &CurvePanel::SetNode);
|
connect(param_panel_, &ParamPanel::FocusedNodeChanged, curve_panel_, &CurvePanel::SetNode);
|
||||||
connect(param_panel_, &ParamPanel::SelectedNodesChanged, node_panel_, &NodePanel::Select);
|
connect(param_panel_, &ParamPanel::SelectedNodesChanged, node_panel_, &NodePanel::Select);
|
||||||
|
|
||||||
// Connect time signals together
|
connect(node_panel_, &NodePanel::NodeSelectionChanged, sequence_viewer_panel_, &ViewerPanel::SetNodeViewSelections);
|
||||||
connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, param_panel_, &ParamPanel::SetTime);
|
|
||||||
connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, curve_panel_, &NodeTablePanel::SetTime);
|
|
||||||
connect(param_panel_, &ParamPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime);
|
|
||||||
connect(param_panel_, &ParamPanel::TimeChanged, curve_panel_, &NodeTablePanel::SetTime);
|
|
||||||
connect(curve_panel_, &ParamPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime);
|
|
||||||
connect(curve_panel_, &ParamPanel::TimeChanged, param_panel_, &NodeTablePanel::SetTime);
|
|
||||||
|
|
||||||
connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &MainWindow::FocusedPanelChanged);
|
// Connect time signals together
|
||||||
|
AddMainTimePanel(multicam_panel_);
|
||||||
|
AddMainTimePanel(curve_panel_);
|
||||||
|
AddMainTimePanel(param_panel_);
|
||||||
|
AddMainTimePanel(sequence_viewer_panel_);
|
||||||
|
|
||||||
sequence_viewer_panel_->ConnectTimeBasedPanel(param_panel_);
|
sequence_viewer_panel_->ConnectTimeBasedPanel(param_panel_);
|
||||||
sequence_viewer_panel_->ConnectTimeBasedPanel(curve_panel_);
|
sequence_viewer_panel_->ConnectTimeBasedPanel(curve_panel_);
|
||||||
|
sequence_viewer_panel_->ConnectTimeBasedPanel(multicam_panel_);
|
||||||
|
|
||||||
|
connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &MainWindow::FocusedPanelChanged);
|
||||||
|
|
||||||
|
sequence_viewer_panel_->AddPlaybackDevice(multicam_panel_->GetMulticamWidget()->GetDisplayWidget());
|
||||||
|
sequence_viewer_panel_->ConnectMulticamWidget(multicam_panel_->GetMulticamWidget());
|
||||||
|
|
||||||
scope_panel_->SetViewerPanel(sequence_viewer_panel_);
|
scope_panel_->SetViewerPanel(sequence_viewer_panel_);
|
||||||
|
|
||||||
@@ -489,6 +494,7 @@ void MainWindow::TimelinePanelSelectionChanged(const QVector<Block *> &blocks)
|
|||||||
|
|
||||||
if (PanelManager::instance()->CurrentlyFocused(false) == panel) {
|
if (PanelManager::instance()->CurrentlyFocused(false) == panel) {
|
||||||
UpdateNodePanelContextFromTimelinePanel(panel);
|
UpdateNodePanelContextFromTimelinePanel(panel);
|
||||||
|
sequence_viewer_panel_->SetTimelineSelectedBlocks(blocks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,21 +597,32 @@ void MainWindow::FloatingPanelCloseRequested()
|
|||||||
panel->deleteLater();
|
panel->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::AddMainTimePanel(TimeBasedPanel *p)
|
||||||
|
{
|
||||||
|
main_time_panels_.append(p);
|
||||||
|
connect(p, &TimeBasedPanel::TimeChanged, this, &MainWindow::UpdateMainTimePanels);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::UpdateMainTimePanels(const rational &r)
|
||||||
|
{
|
||||||
|
for (TimeBasedPanel *p : main_time_panels_) {
|
||||||
|
if (p != sender()) {
|
||||||
|
p->SetTime(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TimelinePanel* MainWindow::AppendTimelinePanel()
|
TimelinePanel* MainWindow::AppendTimelinePanel()
|
||||||
{
|
{
|
||||||
TimelinePanel* panel = AppendPanelInternal<TimelinePanel>(timeline_panels_);
|
TimelinePanel* panel = AppendPanelInternal<TimelinePanel>(timeline_panels_);
|
||||||
|
|
||||||
connect(panel, &PanelWidget::CloseRequested, this, &MainWindow::TimelineCloseRequested);
|
connect(panel, &PanelWidget::CloseRequested, this, &MainWindow::TimelineCloseRequested);
|
||||||
connect(panel, &TimelinePanel::TimeChanged, curve_panel_, &ParamPanel::SetTime);
|
|
||||||
connect(panel, &TimelinePanel::TimeChanged, param_panel_, &ParamPanel::SetTime);
|
|
||||||
connect(panel, &TimelinePanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime);
|
|
||||||
connect(panel, &TimelinePanel::RequestCaptureStart, sequence_viewer_panel_, &SequenceViewerPanel::StartCapture);
|
connect(panel, &TimelinePanel::RequestCaptureStart, sequence_viewer_panel_, &SequenceViewerPanel::StartCapture);
|
||||||
connect(panel, &TimelinePanel::BlockSelectionChanged, this, &MainWindow::TimelinePanelSelectionChanged);
|
connect(panel, &TimelinePanel::BlockSelectionChanged, this, &MainWindow::TimelinePanelSelectionChanged);
|
||||||
connect(panel, &TimelinePanel::RevealViewerInProject, this, &MainWindow::RevealViewerInProject);
|
connect(panel, &TimelinePanel::RevealViewerInProject, this, &MainWindow::RevealViewerInProject);
|
||||||
connect(panel, &TimelinePanel::RevealViewerInFootageViewer, this, &MainWindow::RevealViewerInFootageViewer);
|
connect(panel, &TimelinePanel::RevealViewerInFootageViewer, this, &MainWindow::RevealViewerInFootageViewer);
|
||||||
connect(param_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime);
|
|
||||||
connect(curve_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime);
|
AddMainTimePanel(panel);
|
||||||
connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, panel, &TimelinePanel::SetTime);
|
|
||||||
|
|
||||||
sequence_viewer_panel_->ConnectTimeBasedPanel(panel);
|
sequence_viewer_panel_->ConnectTimeBasedPanel(panel);
|
||||||
|
|
||||||
@@ -647,6 +664,7 @@ void MainWindow::RemoveProjectPanel(ProjectPanel *panel)
|
|||||||
void MainWindow::TimelineFocused(ViewerOutput* viewer)
|
void MainWindow::TimelineFocused(ViewerOutput* viewer)
|
||||||
{
|
{
|
||||||
sequence_viewer_panel_->ConnectViewerNode(viewer);
|
sequence_viewer_panel_->ConnectViewerNode(viewer);
|
||||||
|
multicam_panel_->ConnectViewerNode(viewer);
|
||||||
param_panel_->ConnectViewerNode(viewer);
|
param_panel_->ConnectViewerNode(viewer);
|
||||||
curve_panel_->ConnectViewerNode(viewer);
|
curve_panel_->ConnectViewerNode(viewer);
|
||||||
}
|
}
|
||||||
@@ -837,6 +855,10 @@ void MainWindow::SetDefaultLayout()
|
|||||||
scope_panel_->setFloating(true);
|
scope_panel_->setFloating(true);
|
||||||
addDockWidget(Qt::TopDockWidgetArea, scope_panel_);
|
addDockWidget(Qt::TopDockWidgetArea, scope_panel_);
|
||||||
|
|
||||||
|
multicam_panel_->hide();
|
||||||
|
multicam_panel_->setFloating(true);
|
||||||
|
addDockWidget(Qt::TopDockWidgetArea, multicam_panel_);
|
||||||
|
|
||||||
sequence_viewer_panel_->show();
|
sequence_viewer_panel_->show();
|
||||||
addDockWidget(Qt::TopDockWidgetArea, sequence_viewer_panel_);
|
addDockWidget(Qt::TopDockWidgetArea, sequence_viewer_panel_);
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include "mainwindowlayoutinfo.h"
|
#include "mainwindowlayoutinfo.h"
|
||||||
#include "node/project/project.h"
|
#include "node/project/project.h"
|
||||||
|
#include "panel/multicam/multicampanel.h"
|
||||||
#include "panel/panelmanager.h"
|
#include "panel/panelmanager.h"
|
||||||
#include "panel/audiomonitor/audiomonitor.h"
|
#include "panel/audiomonitor/audiomonitor.h"
|
||||||
#include "panel/curve/curve.h"
|
#include "panel/curve/curve.h"
|
||||||
@@ -146,6 +147,8 @@ private:
|
|||||||
|
|
||||||
void SelectFootageForProjectPanel(const QVector<Footage*> &e, ProjectPanel *p);
|
void SelectFootageForProjectPanel(const QVector<Footage*> &e, ProjectPanel *p);
|
||||||
|
|
||||||
|
void AddMainTimePanel(TimeBasedPanel *p);
|
||||||
|
|
||||||
QByteArray premaximized_state_;
|
QByteArray premaximized_state_;
|
||||||
|
|
||||||
// Standard panels
|
// Standard panels
|
||||||
@@ -163,6 +166,7 @@ private:
|
|||||||
PixelSamplerPanel* pixel_sampler_panel_;
|
PixelSamplerPanel* pixel_sampler_panel_;
|
||||||
ScopePanel* scope_panel_;
|
ScopePanel* scope_panel_;
|
||||||
QMap<ViewerOutput*, ViewerPanel*> viewer_panels_;
|
QMap<ViewerOutput*, ViewerPanel*> viewer_panels_;
|
||||||
|
MulticamPanel *multicam_panel_;
|
||||||
|
|
||||||
#ifdef Q_OS_WINDOWS
|
#ifdef Q_OS_WINDOWS
|
||||||
unsigned int taskbar_btn_id_;
|
unsigned int taskbar_btn_id_;
|
||||||
@@ -172,6 +176,8 @@ private:
|
|||||||
|
|
||||||
bool first_show_;
|
bool first_show_;
|
||||||
|
|
||||||
|
QVector<TimeBasedPanel*> main_time_panels_;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void FocusedPanelChanged(PanelWidget* panel);
|
void FocusedPanelChanged(PanelWidget* panel);
|
||||||
|
|
||||||
@@ -202,6 +208,8 @@ private slots:
|
|||||||
void RevealViewerInProject(ViewerOutput *r);
|
void RevealViewerInProject(ViewerOutput *r);
|
||||||
void RevealViewerInFootageViewer(ViewerOutput *r, const TimeRange &range);
|
void RevealViewerInFootageViewer(ViewerOutput *r, const TimeRange &range);
|
||||||
|
|
||||||
|
void UpdateMainTimePanels(const rational &r);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user