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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!channels_) {
|
||||
@@ -97,18 +106,12 @@ void AudioVisualWaveform::OverwriteSamples(const SampleBuffer &samples, int samp
|
||||
return;
|
||||
}
|
||||
|
||||
// Old less optimized code. Keeping this around as a reference, but the below code is at least
|
||||
// 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);
|
||||
// }
|
||||
ValidateVirtualStart(start);
|
||||
|
||||
// Process the largest mipmap directly for the samples
|
||||
auto current_mipmap = mipmapped_data_.rbegin();
|
||||
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) {
|
||||
// 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(),
|
||||
input_start, input_length, start, current_mipmap->first.toDouble(),
|
||||
input_start, input_length, start - virtual_start_, current_mipmap->first.toDouble(),
|
||||
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)
|
||||
{
|
||||
ValidateVirtualStart(dest);
|
||||
|
||||
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
||||
rational rate = it->first;
|
||||
|
||||
@@ -139,7 +144,7 @@ void AudioVisualWaveform::OverwriteSums(const AudioVisualWaveform &sums, const r
|
||||
double rate_dbl = rate.toDouble();
|
||||
|
||||
// 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
|
||||
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)
|
||||
{
|
||||
ValidateVirtualStart(start);
|
||||
|
||||
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
||||
rational rate = it->first;
|
||||
|
||||
@@ -180,7 +187,7 @@ void AudioVisualWaveform::OverwriteSilence(const rational &start, const rational
|
||||
double rate_dbl = rate.toDouble();
|
||||
|
||||
// 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_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));
|
||||
}
|
||||
|
||||
length_ = qMax(length_, start + length);
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::TrimIn(rational length)
|
||||
@@ -198,6 +207,8 @@ void AudioVisualWaveform::TrimIn(rational length)
|
||||
return;
|
||||
}
|
||||
|
||||
virtual_start_ += length;
|
||||
|
||||
bool negative = (length < 0);
|
||||
if (negative) {
|
||||
length = -length;
|
||||
@@ -225,9 +236,9 @@ void AudioVisualWaveform::TrimIn(rational length)
|
||||
|
||||
AudioVisualWaveform AudioVisualWaveform::Mid(const rational &offset) const
|
||||
{
|
||||
AudioVisualWaveform mid = *this;
|
||||
AudioVisualWaveform mid = *this;
|
||||
|
||||
mid.TrimIn(offset);
|
||||
mid.TrimIn(offset - virtual_start_);
|
||||
|
||||
return mid;
|
||||
}
|
||||
@@ -236,7 +247,7 @@ AudioVisualWaveform AudioVisualWaveform::Mid(const rational &offset, const ratio
|
||||
{
|
||||
AudioVisualWaveform mid = *this;
|
||||
|
||||
mid.TrimRange(offset, length);
|
||||
mid.TrimRange(offset - virtual_start_, length);
|
||||
|
||||
return mid;
|
||||
}
|
||||
@@ -273,7 +284,7 @@ AudioVisualWaveform::Sample AudioVisualWaveform::GetSummaryFromTime(const ration
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
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()) {
|
||||
return;
|
||||
|
||||
@@ -123,6 +123,10 @@ private:
|
||||
|
||||
std::map<rational, Sample>::const_iterator GetMipmapForScale(double scale) const;
|
||||
|
||||
void ValidateVirtualStart(const rational &new_start);
|
||||
|
||||
rational virtual_start_;
|
||||
|
||||
int channels_;
|
||||
|
||||
std::map<rational, Sample> mipmapped_data_;
|
||||
|
||||
@@ -126,6 +126,17 @@ public:
|
||||
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
|
||||
{
|
||||
return array_.isEmpty();
|
||||
|
||||
+2
-2
@@ -1490,7 +1490,7 @@ bool Core::LabelNodes(const QVector<Node *> &nodes, MultiUndoCommand *parent)
|
||||
return false;
|
||||
}
|
||||
|
||||
Sequence *Core::CreateNewSequenceForProject(Project* project) const
|
||||
Sequence *Core::CreateNewSequenceForProject(const QString &format, Project* project)
|
||||
{
|
||||
Sequence* new_sequence = new Sequence();
|
||||
|
||||
@@ -1498,7 +1498,7 @@ Sequence *Core::CreateNewSequenceForProject(Project* project) const
|
||||
int sequence_number = 1;
|
||||
QString sequence_name;
|
||||
do {
|
||||
sequence_name = tr("Sequence %1").arg(sequence_number);
|
||||
sequence_name = format.arg(sequence_number);
|
||||
sequence_number++;
|
||||
} while (project->root()->ChildExistsWithName(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
|
||||
*/
|
||||
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
|
||||
|
||||
@@ -232,7 +232,7 @@ void ClipBlock::RequestRangeFromConnected(const TimeRange &range)
|
||||
{
|
||||
TimeRange thumb_range = range.Intersected(max_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) {
|
||||
emit cache->Request(r);
|
||||
cache->Request(r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,4 +541,14 @@ TimeRange ClipBlock::media_range() const
|
||||
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 "codec/decoder.h"
|
||||
#include "node/block/block.h"
|
||||
#include "node/input/multicam/multicamnode.h"
|
||||
#include "node/output/track/track.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -197,6 +198,8 @@ public:
|
||||
SetStandardValue(kLoopModeInput, int(l));
|
||||
}
|
||||
|
||||
MultiCamNode *FindMulticam();
|
||||
|
||||
static const QString kBufferIn;
|
||||
static const QString kMediaInInput;
|
||||
static const QString kSpeedInput;
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
#include "generator/text/textv1.h"
|
||||
#include "generator/text/textv2.h"
|
||||
#include "generator/text/textv3.h"
|
||||
#include "input/multicam/multicamnode.h"
|
||||
#include "input/time/timeinput.h"
|
||||
#include "input/value/valuenode.h"
|
||||
#include "keying/chromakey/chromakey.h"
|
||||
@@ -306,6 +307,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
|
||||
return new SwirlDistortNode();
|
||||
case kRippleDistort:
|
||||
return new RippleDistortNode();
|
||||
case kMulticamNode:
|
||||
return new MultiCamNode();
|
||||
|
||||
case kInternalNodeCount:
|
||||
break;
|
||||
|
||||
@@ -80,6 +80,7 @@ public:
|
||||
kRippleDistort,
|
||||
kTileDistort,
|
||||
kSwirlDistort,
|
||||
kMulticamNode,
|
||||
|
||||
// Count value
|
||||
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);
|
||||
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);
|
||||
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;
|
||||
|
||||
QVector<NodeValue> points = row[kPointsInput].value< QVector<NodeValue> >();
|
||||
auto points = row[kPointsInput].toArray();
|
||||
|
||||
int current_pos_sz = gizmo_position_handles_.size();
|
||||
|
||||
@@ -200,8 +200,9 @@ void PolygonGenerator::UpdateGizmoPositions(const NodeValueRow &row, const NodeG
|
||||
bez_gizmo2->SetSmaller(true);
|
||||
}
|
||||
|
||||
if (!points.isEmpty()) {
|
||||
for (int i=0; i<points.size(); i++) {
|
||||
int pts_sz = InputArraySize(kPointsInput);
|
||||
if (!points.empty()) {
|
||||
for (int i=0; i<pts_sz; i++) {
|
||||
const Bezier &pt = points.at(i).toBezier();
|
||||
|
||||
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
|
||||
@@ -250,19 +251,19 @@ void PolygonGenerator::AddPointToPath(QPainterPath *path, const Bezier &before,
|
||||
after.ToPointF());
|
||||
}
|
||||
|
||||
QPainterPath PolygonGenerator::GeneratePath(const QVector<NodeValue> &points)
|
||||
QPainterPath PolygonGenerator::GeneratePath(const NodeValueArray &points, int size)
|
||||
{
|
||||
QPainterPath path;
|
||||
|
||||
if (!points.isEmpty()) {
|
||||
const Bezier &first_pt = points.first().toBezier();
|
||||
if (!points.empty()) {
|
||||
const Bezier &first_pt = points.at(0).toBezier();
|
||||
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.last().toBezier(), first_pt);
|
||||
AddPointToPath(&path, points.at(size-1).toBezier(), first_pt);
|
||||
}
|
||||
|
||||
return path;
|
||||
|
||||
@@ -68,7 +68,7 @@ protected slots:
|
||||
private:
|
||||
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>
|
||||
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.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);
|
||||
} else {
|
||||
|
||||
@@ -105,7 +105,7 @@ void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &global
|
||||
if (!args.empty()) {
|
||||
QStringList list;
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -24,6 +24,7 @@
|
||||
#include <QVector2D>
|
||||
|
||||
#include "common/timerange.h"
|
||||
#include "render/audioparams.h"
|
||||
#include "render/videoparams.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -33,19 +34,22 @@ class NodeGlobals
|
||||
public:
|
||||
NodeGlobals(){}
|
||||
|
||||
NodeGlobals(const VideoParams &vparam, const TimeRange &time) :
|
||||
NodeGlobals(const VideoParams &vparam, const AudioParams &aparam, const TimeRange &time) :
|
||||
video_params_(vparam),
|
||||
audio_params_(aparam),
|
||||
time_(time)
|
||||
{
|
||||
}
|
||||
|
||||
QVector2D square_resolution() const { return video_params_.square_resolution(); }
|
||||
QVector2D nonsquare_resolution() const { return video_params_.resolution(); }
|
||||
const AudioParams &aparams() const { return audio_params_; }
|
||||
const VideoParams &vparams() const { return video_params_; }
|
||||
const TimeRange &time() const { return time_; }
|
||||
|
||||
private:
|
||||
VideoParams video_params_;
|
||||
AudioParams audio_params_;
|
||||
TimeRange time_;
|
||||
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(multicam)
|
||||
add_subdirectory(time)
|
||||
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"
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super Node
|
||||
|
||||
const QString MultiCamNode::kCurrentInput = QStringLiteral("current_in");
|
||||
const QString MultiCamNode::kSourcesInput = QStringLiteral("sources_in");
|
||||
|
||||
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
|
||||
#define MULTICAMNODE_H
|
||||
|
||||
#include "node/node.h"
|
||||
|
||||
class MultiCamNode
|
||||
namespace olive {
|
||||
|
||||
class MultiCamNode : public Node
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
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
|
||||
|
||||
+36
-39
@@ -27,11 +27,8 @@
|
||||
|
||||
#include "common/bezier.h"
|
||||
#include "common/lerp.h"
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "common/xmlutils.h"
|
||||
#include "core.h"
|
||||
#include "config/config.h"
|
||||
#include "node/project/footage/footage.h"
|
||||
#include "project/project.h"
|
||||
#include "ui/colorcoding.h"
|
||||
#include "ui/icons/icons.h"
|
||||
@@ -251,7 +248,7 @@ QString Node::GetInputName(const QString &id) const
|
||||
if (i) {
|
||||
return i->human_name;
|
||||
} else {
|
||||
ReportInvalidInput("get name of", id);
|
||||
ReportInvalidInput("get name of", id, -1);
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
@@ -278,7 +275,7 @@ bool Node::IsInputKeyframing(const QString &input, int element) const
|
||||
if (imm) {
|
||||
return imm->is_keyframing();
|
||||
} else {
|
||||
ReportInvalidInput("get keyframing state of", input);
|
||||
ReportInvalidInput("get keyframing state of", input, element);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -297,7 +294,7 @@ void Node::SetInputIsKeyframing(const QString &input, bool e, int element)
|
||||
|
||||
emit KeyframeEnableChanged(NodeInput(this, input, element), e);
|
||||
} 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) {
|
||||
return imm->is_using_standard_value(track);
|
||||
} else {
|
||||
ReportInvalidInput("determine whether using standard value in", input);
|
||||
ReportInvalidInput("determine whether using standard value in", input, element);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -336,7 +333,7 @@ NodeValue::Type Node::GetInputDataType(const QString &id) const
|
||||
if (i) {
|
||||
return i->type;
|
||||
} else {
|
||||
ReportInvalidInput("get data type of", id);
|
||||
ReportInvalidInput("get data type of", id, -1);
|
||||
return NodeValue::kNone;
|
||||
}
|
||||
}
|
||||
@@ -355,7 +352,7 @@ void Node::SetInputDataType(const QString &id, const NodeValue::Type &type)
|
||||
|
||||
emit InputDataTypeChanged(id, type);
|
||||
} 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) {
|
||||
return i->properties.contains(name);
|
||||
} else {
|
||||
ReportInvalidInput("get property of", id);
|
||||
ReportInvalidInput("get property of", id, -1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -378,7 +375,7 @@ QHash<QString, QVariant> Node::GetInputProperties(const QString &id) const
|
||||
if (i) {
|
||||
return i->properties;
|
||||
} else {
|
||||
ReportInvalidInput("get property table of", id);
|
||||
ReportInvalidInput("get property table of", id, -1);
|
||||
return QHash<QString, QVariant>();
|
||||
}
|
||||
}
|
||||
@@ -390,7 +387,7 @@ QVariant Node::GetInputProperty(const QString &id, const QString &name) const
|
||||
if (i) {
|
||||
return i->properties.value(name);
|
||||
} else {
|
||||
ReportInvalidInput("get property of", id);
|
||||
ReportInvalidInput("get property of", id, -1);
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
@@ -404,7 +401,7 @@ void Node::SetInputProperty(const QString &id, const QString &name, const QVaria
|
||||
|
||||
emit InputPropertyChanged(id, name, value);
|
||||
} 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) {
|
||||
return i->default_value;
|
||||
} else {
|
||||
ReportInvalidInput("retrieve default value of", input);
|
||||
ReportInvalidInput("retrieve default value of", input, -1);
|
||||
return SplitValue();
|
||||
}
|
||||
}
|
||||
@@ -577,7 +574,7 @@ void Node::SetSplitDefaultValue(const QString &input, const SplitValue &val)
|
||||
if (i) {
|
||||
i->default_value = val;
|
||||
} 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;
|
||||
}
|
||||
} 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) {
|
||||
return imm->get_keyframe_at_time(time);
|
||||
} else {
|
||||
ReportInvalidInput("get keyframes at time from", input);
|
||||
ReportInvalidInput("get keyframes at time from", input, element);
|
||||
return QVector<NodeKeyframe*>();
|
||||
}
|
||||
}
|
||||
@@ -618,7 +615,7 @@ NodeKeyframe *Node::GetKeyframeAtTimeOnTrack(const QString &input, const rationa
|
||||
if (imm) {
|
||||
return imm->get_keyframe_at_time_on_track(time, track);
|
||||
} else {
|
||||
ReportInvalidInput("get keyframe at time on track from", input);
|
||||
ReportInvalidInput("get keyframe at time on track from", input, element);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -630,7 +627,7 @@ NodeKeyframe::Type Node::GetBestKeyframeTypeForTimeOnTrack(const QString &input,
|
||||
if (imm) {
|
||||
return imm->get_best_keyframe_type_for_time(time, track);
|
||||
} else {
|
||||
ReportInvalidInput("get closest keyframe before a time from", input);
|
||||
ReportInvalidInput("get closest keyframe before a time from", input, element);
|
||||
return NodeKeyframe::kDefaultType;
|
||||
}
|
||||
}
|
||||
@@ -647,7 +644,7 @@ NodeKeyframe *Node::GetEarliestKeyframe(const QString &id, int element) const
|
||||
if (imm) {
|
||||
return imm->get_earliest_keyframe();
|
||||
} else {
|
||||
ReportInvalidInput("get earliest keyframe from", id);
|
||||
ReportInvalidInput("get earliest keyframe from", id, element);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -659,7 +656,7 @@ NodeKeyframe *Node::GetLatestKeyframe(const QString &id, int element) const
|
||||
if (imm) {
|
||||
return imm->get_latest_keyframe();
|
||||
} else {
|
||||
ReportInvalidInput("get latest keyframe from", id);
|
||||
ReportInvalidInput("get latest keyframe from", id, element);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -671,7 +668,7 @@ NodeKeyframe *Node::GetClosestKeyframeBeforeTime(const QString &id, const ration
|
||||
if (imm) {
|
||||
return imm->get_closest_keyframe_before_time(time);
|
||||
} else {
|
||||
ReportInvalidInput("get closest keyframe before a time from", id);
|
||||
ReportInvalidInput("get closest keyframe before a time from", id, element);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -683,7 +680,7 @@ NodeKeyframe *Node::GetClosestKeyframeAfterTime(const QString &id, const rationa
|
||||
if (imm) {
|
||||
return imm->get_closest_keyframe_after_time(time);
|
||||
} else {
|
||||
ReportInvalidInput("get closest keyframe after a time from", id);
|
||||
ReportInvalidInput("get closest keyframe after a time from", id, element);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -695,7 +692,7 @@ bool Node::HasKeyframeAtTime(const QString &id, const rational &time, int elemen
|
||||
if (imm) {
|
||||
return imm->has_keyframe_at_time(time);
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
@@ -719,7 +716,7 @@ SplitValue Node::GetSplitStandardValue(const QString &id, int element) const
|
||||
if (imm) {
|
||||
return imm->get_split_standard_value();
|
||||
} else {
|
||||
ReportInvalidInput("get standard value of", id);
|
||||
ReportInvalidInput("get standard value of", id, element);
|
||||
return SplitValue();
|
||||
}
|
||||
}
|
||||
@@ -731,7 +728,7 @@ QVariant Node::GetSplitStandardValueOnTrack(const QString &input, int track, int
|
||||
if (imm) {
|
||||
return imm->get_split_standard_value_on_track(track);
|
||||
} else {
|
||||
ReportInvalidInput("get standard value of", input);
|
||||
ReportInvalidInput("get standard value of", input, element);
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
@@ -758,7 +755,7 @@ void Node::SetSplitStandardValue(const QString &id, const SplitValue &value, int
|
||||
}
|
||||
}
|
||||
} 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));
|
||||
}
|
||||
} 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) {
|
||||
return i->array_size;
|
||||
} else {
|
||||
ReportInvalidInput("retrieve array size of", id);
|
||||
ReportInvalidInput("retrieve array size of", id, -1);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -914,7 +911,7 @@ InputFlags Node::GetInputFlags(const QString &input) const
|
||||
if (i) {
|
||||
return i->flags;
|
||||
} else {
|
||||
ReportInvalidInput("retrieve flags of", input);
|
||||
ReportInvalidInput("retrieve flags of", input, -1);
|
||||
return InputFlags(kInputFlagNormal);
|
||||
}
|
||||
}
|
||||
@@ -927,7 +924,7 @@ void Node::SetInputFlags(const QString &input, const InputFlags &f)
|
||||
i->flags = f;
|
||||
emit InputFlagsChanged(input, i->flags);
|
||||
} 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);
|
||||
|
||||
if (index == -1) {
|
||||
ReportInvalidInput("remove", id);
|
||||
ReportInvalidInput("remove", id, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1237,9 +1234,9 @@ void Node::RemoveInput(const QString &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";
|
||||
}
|
||||
|
||||
@@ -1250,7 +1247,7 @@ NodeInputImmediate *Node::CreateImmediate(const QString &input)
|
||||
if (i) {
|
||||
return new NodeInputImmediate(i->type, i->default_value);
|
||||
} else {
|
||||
ReportInvalidInput("create immediate", input);
|
||||
ReportInvalidInput("create immediate", input, -1);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -1260,7 +1257,7 @@ void Node::ArrayResizeInternal(const QString &id, int size)
|
||||
Input* imm = GetInternalInputData(id);
|
||||
|
||||
if (!imm) {
|
||||
ReportInvalidInput("set array size", id);
|
||||
ReportInvalidInput("set array size", id, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1299,7 +1296,7 @@ void Node::SetInputName(const QString &id, const QString &name)
|
||||
|
||||
emit InputNameChanged(id, name);
|
||||
} 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()) {
|
||||
foreach (NodeKeyframe* key, track) {
|
||||
for (const NodeKeyframeTrack& track : src->GetImmediate(input, src_element)->keyframe_tracks()) {
|
||||
for (NodeKeyframe* key : track) {
|
||||
NodeKeyframe *copy = key->copy(dst_element, command ? nullptr : dst);
|
||||
if (command) {
|
||||
command->add_child(new NodeParamInsertKeyframeCommand(dst, copy));
|
||||
|
||||
+35
-1
@@ -212,6 +212,40 @@ public:
|
||||
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
|
||||
{
|
||||
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);
|
||||
|
||||
|
||||
+187
-43
@@ -24,8 +24,10 @@
|
||||
#include <QDebug>
|
||||
#include <QFontMetrics>
|
||||
|
||||
#include "audio/audioprocessor.h"
|
||||
#include "node/block/clip/clip.h"
|
||||
#include "node/block/gap/gap.h"
|
||||
#include "node/graph.h"
|
||||
#include "node/block/transition/transition.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -95,6 +97,55 @@ QString Track::Description() const
|
||||
"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
|
||||
{
|
||||
if (input == kBlockInput && element >= 0) {
|
||||
@@ -352,56 +403,31 @@ Block *Track::NearestBlockAfter(const rational &time) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Block *Track::BlockAtTime(const rational &time) const
|
||||
bool Track::IsRangeFree(const TimeRange &range) const
|
||||
{
|
||||
if (IsMuted() || time > track_length() || blocks_.isEmpty()) {
|
||||
return nullptr;
|
||||
Block *b = NearestBlockBeforeOrAt(range.in());
|
||||
if (!b) {
|
||||
// No block here, assume track is empty here
|
||||
return true;
|
||||
}
|
||||
|
||||
// Use binary search to find block at time
|
||||
Block* using_block = nullptr;
|
||||
if (!dynamic_cast<GapBlock*>(b)) {
|
||||
// There's a block at or around the start point that isn't a gap, range is not free
|
||||
return false;
|
||||
}
|
||||
|
||||
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) {
|
||||
using_block = block;
|
||||
while ((b = b->next())) {
|
||||
if (b->in() >= range.out()) {
|
||||
// This block is after the range, no longer relevant
|
||||
break;
|
||||
} else if (block->out() <= time) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
high = mid - 1;
|
||||
} else if (!dynamic_cast<GapBlock*>(b)) {
|
||||
// Found a block in this range, range is not free
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (using_block && !using_block->is_enabled()) {
|
||||
using_block = nullptr;
|
||||
}
|
||||
|
||||
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;
|
||||
// If we get here, we couldn't find anything in the way of this range
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
// Assumes sender is a Block
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#define TRACK_H
|
||||
|
||||
#include "node/block/block.h"
|
||||
#include "timeline/timelinecommon.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -55,6 +54,9 @@ public:
|
||||
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 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;
|
||||
@@ -314,29 +316,10 @@ public:
|
||||
*/
|
||||
Block* NearestBlockAfter(const rational& time) const;
|
||||
|
||||
/**
|
||||
* @brief Returns the block that should be rendered/visible at a given time
|
||||
*
|
||||
* 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.
|
||||
/*
|
||||
* @brief Returns whether a time range is empty or only has a gap
|
||||
*/
|
||||
Block* BlockAtTime(const rational& time) 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;
|
||||
bool IsRangeFree(const TimeRange &range) 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;
|
||||
|
||||
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
|
||||
*/
|
||||
@@ -467,6 +456,10 @@ private:
|
||||
|
||||
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_;
|
||||
|
||||
QVector<Block*> blocks_;
|
||||
|
||||
@@ -224,16 +224,16 @@ void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from,
|
||||
|
||||
if (Node *connected = GetConnectedOutput(from, element)) {
|
||||
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_) {
|
||||
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) {
|
||||
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_) {
|
||||
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()
|
||||
{
|
||||
if (Node *connected = GetConnectedOutput(kSamplesInput)) {
|
||||
if (Node *connected = this->GetConnectedSampleOutput()) {
|
||||
TimeRange max_range = InputTimeAdjustment(kSamplesInput, -1, TimeRange(0, GetAudioLength()));
|
||||
TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range);
|
||||
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()) {
|
||||
// Resolve each element of array
|
||||
QVector<NodeValueTable> tables = value.value<QVector<NodeValueTable> >();
|
||||
QVector<NodeValue> output(tables.size());
|
||||
NodeValueTableArray tables = value.value<NodeValueTableArray>();
|
||||
NodeValueArray output;
|
||||
|
||||
for (int i=0; i<tables.size(); i++) {
|
||||
output[i] = GenerateRowValueElement(node, input, i, &tables[i], time);
|
||||
for (auto it=tables.begin(); it!=tables.end(); it++) {
|
||||
output[it->first] = GenerateRowValueElement(node, input, it->first, &it->second, time);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -205,18 +205,17 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu
|
||||
if (is_array) {
|
||||
|
||||
// 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++) {
|
||||
NodeValueTable& sub_tbl = array_tbl[i];
|
||||
TimeRange adjusted_range = node->InputTimeAdjustment(input, i, range);
|
||||
|
||||
if (node->IsInputConnected(input, i)) {
|
||||
Node *output = node->GetConnectedOutput(input, i);
|
||||
sub_tbl = GenerateTable(output, adjusted_range, node);
|
||||
} else {
|
||||
QVariant input_value = node->GetValueAtTime(input, adjusted_range.in(), i);
|
||||
sub_tbl.Push(node->GetInputDataType(input), input_value, node);
|
||||
Node::ActiveElements a = node->GetActiveElementsAtTime(input, range);
|
||||
if (a.mode() == Node::ActiveElements::kAllElements) {
|
||||
int sz = node->InputArraySize(input);
|
||||
for (int i=0; i<sz; i++) {
|
||||
ProcessInputElement(array_tbl, node, input, i, range);
|
||||
}
|
||||
} else if (a.mode() == Node::ActiveElements::kSpecified) {
|
||||
for (int ele : a.elements()) {
|
||||
ProcessInputElement(array_tbl, node, input, ele, range);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() :
|
||||
cancel_(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.
|
||||
//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
|
||||
if (value_cache_.contains(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();
|
||||
|
||||
// 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);
|
||||
|
||||
// `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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return nullptr;
|
||||
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
|
||||
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(const Node *node, const TimeRange &range);
|
||||
@@ -54,10 +54,10 @@ public:
|
||||
|
||||
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 ¶ms, const rational &time)
|
||||
static NodeGlobals GenerateGlobals(const VideoParams &vparams, const AudioParams &aparams, const TimeRange &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
|
||||
@@ -83,7 +83,7 @@ public:
|
||||
protected:
|
||||
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){}
|
||||
|
||||
|
||||
+6
-2
@@ -31,11 +31,15 @@
|
||||
#include "node/splitvalue.h"
|
||||
#include "render/color.h"
|
||||
#include "render/texture.h"
|
||||
#include "undo/undocommand.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class Node;
|
||||
class NodeValue;
|
||||
class NodeValueTable;
|
||||
|
||||
using NodeValueArray = std::map<int, NodeValue>;
|
||||
using NodeValueTableArray = std::map<int, NodeValueTable>;
|
||||
|
||||
class NodeValue
|
||||
{
|
||||
@@ -344,7 +348,7 @@ public:
|
||||
QVector3D toVec3() const { return value<QVector3D>(); }
|
||||
QVector4D toVec4() const { return value<QVector4D>(); }
|
||||
Bezier toBezier() const { return value<Bezier>(); }
|
||||
QVector<NodeValue> toArray() const { return value<QVector<NodeValue> >(); }
|
||||
NodeValueArray toArray() const { return value<NodeValueArray>(); }
|
||||
|
||||
private:
|
||||
Type type_;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
add_subdirectory(audiomonitor)
|
||||
add_subdirectory(curve)
|
||||
add_subdirectory(footageviewer)
|
||||
add_subdirectory(multicam)
|
||||
add_subdirectory(node)
|
||||
add_subdirectory(param)
|
||||
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;
|
||||
|
||||
TimeBasedWidget* GetTimeBasedWidget() const { return widget_; }
|
||||
|
||||
public slots:
|
||||
void SetTimebase(const rational& timebase);
|
||||
|
||||
@@ -127,11 +129,6 @@ signals:
|
||||
void ShuttleRightRequested();
|
||||
|
||||
protected:
|
||||
TimeBasedWidget* GetTimeBasedWidget() const
|
||||
{
|
||||
return widget_;
|
||||
}
|
||||
|
||||
void SetTimeBasedWidget(TimeBasedWidget* widget);
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
@@ -98,6 +98,11 @@ public:
|
||||
timeline_widget()->ShowSpeedDurationDialogForSelectedClips();
|
||||
}
|
||||
|
||||
void NestSelectedClips()
|
||||
{
|
||||
timeline_widget()->NestSelectedClips();
|
||||
}
|
||||
|
||||
void InsertFootageAtPlayhead(const QVector<ViewerOutput *> &footage);
|
||||
|
||||
void OverwriteFootageAtPlayhead(const QVector<ViewerOutput *> &footage);
|
||||
@@ -107,6 +112,11 @@ public:
|
||||
return timeline_widget()->GetSelectedBlocks();
|
||||
}
|
||||
|
||||
Sequence *GetSequence() const
|
||||
{
|
||||
return dynamic_cast<Sequence*>(GetConnectedViewer());
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void Retranslate() override;
|
||||
|
||||
|
||||
@@ -24,35 +24,37 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super TimeBasedPanel
|
||||
|
||||
ViewerPanelBase::ViewerPanelBase(const QString& object_name, QWidget *parent) :
|
||||
TimeBasedPanel(object_name, parent)
|
||||
super(object_name, parent)
|
||||
{
|
||||
connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &ViewerPanelBase::FocusedPanelChanged);
|
||||
}
|
||||
|
||||
void ViewerPanelBase::PlayPause()
|
||||
{
|
||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->TogglePlayPause();
|
||||
GetViewerWidget()->TogglePlayPause();
|
||||
}
|
||||
|
||||
void ViewerPanelBase::PlayInToOut()
|
||||
{
|
||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->Play(true);
|
||||
GetViewerWidget()->Play(true);
|
||||
}
|
||||
|
||||
void ViewerPanelBase::ShuttleLeft()
|
||||
{
|
||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->ShuttleLeft();
|
||||
GetViewerWidget()->ShuttleLeft();
|
||||
}
|
||||
|
||||
void ViewerPanelBase::ShuttleStop()
|
||||
{
|
||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->ShuttleStop();
|
||||
GetViewerWidget()->ShuttleStop();
|
||||
}
|
||||
|
||||
void ViewerPanelBase::ShuttleRight()
|
||||
{
|
||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->ShuttleRight();
|
||||
GetViewerWidget()->ShuttleRight();
|
||||
}
|
||||
|
||||
void ViewerPanelBase::ConnectTimeBasedPanel(TimeBasedPanel *panel)
|
||||
@@ -75,22 +77,22 @@ void ViewerPanelBase::DisconnectTimeBasedPanel(TimeBasedPanel *panel)
|
||||
|
||||
void ViewerPanelBase::SetFullScreen(QScreen *screen)
|
||||
{
|
||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->SetFullScreen(screen);
|
||||
GetViewerWidget()->SetFullScreen(screen);
|
||||
}
|
||||
|
||||
void ViewerPanelBase::SetGizmos(Node *node)
|
||||
{
|
||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->SetGizmos(node);
|
||||
GetViewerWidget()->SetGizmos(node);
|
||||
}
|
||||
|
||||
void ViewerPanelBase::CacheEntireSequence()
|
||||
{
|
||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->CacheEntireSequence();
|
||||
GetViewerWidget()->CacheEntireSequence();
|
||||
}
|
||||
|
||||
void ViewerPanelBase::CacheSequenceInOut()
|
||||
{
|
||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->CacheSequenceInOut();
|
||||
GetViewerWidget()->CacheSequenceInOut();
|
||||
}
|
||||
|
||||
void ViewerPanelBase::SetViewerWidget(ViewerWidget *vw)
|
||||
@@ -104,9 +106,11 @@ void ViewerPanelBase::SetViewerWidget(ViewerWidget *vw)
|
||||
|
||||
void ViewerPanelBase::FocusedPanelChanged(PanelWidget *panel)
|
||||
{
|
||||
auto vw = static_cast<ViewerWidget*>(GetTimeBasedWidget());
|
||||
if (vw->IsPlaying() && panel != this) {
|
||||
vw->Pause();
|
||||
if (dynamic_cast<ViewerPanelBase*>(panel)) {
|
||||
auto vw = GetViewerWidget();
|
||||
if (vw->IsPlaying() && panel != this) {
|
||||
vw->Pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,11 @@ class ViewerPanelBase : public TimeBasedPanel
|
||||
public:
|
||||
ViewerPanelBase(const QString& object_name, QWidget* parent = nullptr);
|
||||
|
||||
ViewerWidget *GetViewerWidget() const
|
||||
{
|
||||
return static_cast<ViewerWidget*>(GetTimeBasedWidget());
|
||||
}
|
||||
|
||||
virtual void PlayPause() override;
|
||||
|
||||
virtual void PlayInToOut() override;
|
||||
@@ -54,12 +59,32 @@ public:
|
||||
|
||||
ColorManager *GetColorManager()
|
||||
{
|
||||
return static_cast<ViewerWidget*>(GetTimeBasedWidget())->color_manager();
|
||||
return GetViewerWidget()->color_manager();
|
||||
}
|
||||
|
||||
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:
|
||||
@@ -71,7 +96,7 @@ public slots:
|
||||
|
||||
void RequestStartEditingText()
|
||||
{
|
||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->RequestStartEditingText();
|
||||
GetViewerWidget()->RequestStartEditingText();
|
||||
}
|
||||
|
||||
signals:
|
||||
|
||||
@@ -22,113 +22,94 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super PlaybackCache
|
||||
|
||||
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)
|
||||
{
|
||||
// Write each valid range to the segments
|
||||
foreach (const TimeRange& r, valid_ranges) {
|
||||
#ifdef AVW_USE_LIST
|
||||
// Write visual
|
||||
TimeRangeList::util_remove(&waveforms_, r);
|
||||
|
||||
if (waveform) {
|
||||
TimeRangeWithWaveform wv = r;
|
||||
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);
|
||||
waveforms_->OverwriteSums(*waveform, r.in(), r.in() - range.in(), r.length());
|
||||
}
|
||||
#else
|
||||
if (waveform) {
|
||||
waveforms_.OverwriteSums(*waveform, r.in(), r.in() - range.in(), r.length());
|
||||
}
|
||||
#endif
|
||||
|
||||
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
|
||||
{
|
||||
rational end = start_time + rational::fromDouble(rect.width() / scale);
|
||||
TimeRange draw_range(start_time, end);
|
||||
if (!passthroughs_.empty()) {
|
||||
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
|
||||
foreach (const TimeRangeWithWaveform &wv, waveforms_) {
|
||||
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);
|
||||
// Remove this range
|
||||
draw_range.remove(p);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
#ifdef AVW_USE_LIST
|
||||
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
|
||||
return waveforms_->GetSummaryFromTime(start, length);
|
||||
}
|
||||
|
||||
rational AudioWaveformCache::length() const
|
||||
{
|
||||
#ifdef AVW_USE_LIST
|
||||
rational len = 0;
|
||||
|
||||
foreach (const TimeRangeWithWaveform &wv, waveforms_) {
|
||||
len = std::max(len, wv.out());
|
||||
}
|
||||
|
||||
return len;
|
||||
#else
|
||||
return waveforms_.length();
|
||||
#endif
|
||||
return waveforms_->length();
|
||||
}
|
||||
|
||||
void AudioWaveformCache::SetPassthrough(PlaybackCache *cache)
|
||||
{
|
||||
AudioWaveformCache *c = static_cast<AudioWaveformCache*>(cache);
|
||||
waveforms_ = c->waveforms_;
|
||||
|
||||
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());
|
||||
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 "playbackcache.h"
|
||||
|
||||
//#define AVW_USE_LIST
|
||||
|
||||
namespace olive {
|
||||
|
||||
class AudioWaveformCache : public PlaybackCache
|
||||
@@ -40,7 +38,7 @@ public:
|
||||
void SetParameters(const AudioParams &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;
|
||||
@@ -51,45 +49,28 @@ public:
|
||||
|
||||
virtual void SetPassthrough(PlaybackCache *cache) override;
|
||||
|
||||
protected:
|
||||
virtual void InvalidateEvent(const TimeRange& range) override;
|
||||
|
||||
private:
|
||||
#ifdef AVW_USE_LIST
|
||||
class TimeRangeWithWaveform : public TimeRange
|
||||
{
|
||||
public:
|
||||
TimeRangeWithWaveform() = default;
|
||||
TimeRangeWithWaveform(const TimeRange &r) :
|
||||
TimeRange(r)
|
||||
{
|
||||
}
|
||||
using WaveformPtr = std::shared_ptr<AudioVisualWaveform>;
|
||||
|
||||
void set_in(const rational& in)
|
||||
{
|
||||
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
|
||||
WaveformPtr waveforms_;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
void PlaybackCache::Request(const TimeRange &r)
|
||||
{
|
||||
requested_.insert(r);
|
||||
|
||||
emit Requested(r);
|
||||
}
|
||||
|
||||
void PlaybackCache::Validate(const TimeRange &r, bool signal)
|
||||
{
|
||||
validated_.insert(r);
|
||||
|
||||
@@ -98,15 +98,29 @@ public:
|
||||
|
||||
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:
|
||||
void InvalidateAll();
|
||||
|
||||
void Request(const olive::TimeRange &r);
|
||||
|
||||
signals:
|
||||
void Invalidated(const olive::TimeRange& r);
|
||||
|
||||
void Validated(const olive::TimeRange& r);
|
||||
|
||||
void Request(const olive::TimeRange& r);
|
||||
void Requested(const olive::TimeRange& r);
|
||||
|
||||
void CancelAll();
|
||||
|
||||
@@ -124,6 +138,8 @@ protected:
|
||||
private:
|
||||
TimeRangeList validated_;
|
||||
|
||||
TimeRangeList requested_;
|
||||
|
||||
QUuid uuid_;
|
||||
|
||||
bool saving_enabled_;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
#include "codec/conformmanager.h"
|
||||
#include "node/input/multicam/multicamnode.h"
|
||||
#include "node/inputdragger.h"
|
||||
#include "node/project/project.h"
|
||||
#include "render/diskmanager.h"
|
||||
@@ -41,7 +42,9 @@ PreviewAutoCacher::PreviewAutoCacher(QObject *parent) :
|
||||
use_custom_range_(false),
|
||||
pause_renders_(false),
|
||||
single_frame_render_(nullptr),
|
||||
display_color_processor_(nullptr)
|
||||
display_color_processor_(nullptr),
|
||||
multicam_(nullptr),
|
||||
ignore_cache_requests_(false)
|
||||
{
|
||||
// Set defaults
|
||||
SetPlayhead(0);
|
||||
@@ -62,6 +65,11 @@ PreviewAutoCacher::~PreviewAutoCacher()
|
||||
}
|
||||
|
||||
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
|
||||
CancelQueuedSingleFrameRender();
|
||||
@@ -71,6 +79,7 @@ RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, bool dry)
|
||||
sfr->Start();
|
||||
sfr->setProperty("time", QVariant::fromValue(t));
|
||||
sfr->setProperty("dry", dry);
|
||||
sfr->setProperty("node", Node::PtrToValue(n));
|
||||
|
||||
// Queue it and try to render
|
||||
single_frame_render_ = sfr;
|
||||
@@ -100,6 +109,8 @@ void PreviewAutoCacher::VideoInvalidatedFromCache(const TimeRange &range)
|
||||
{
|
||||
PlaybackCache *cache = static_cast<PlaybackCache*>(sender());
|
||||
|
||||
cache->ClearRequestRange(range);
|
||||
|
||||
VideoInvalidatedFromNode(cache, range);
|
||||
}
|
||||
|
||||
@@ -107,6 +118,8 @@ void PreviewAutoCacher::AudioInvalidatedFromCache(const TimeRange &range)
|
||||
{
|
||||
PlaybackCache *cache = static_cast<PlaybackCache*>(sender());
|
||||
|
||||
cache->ClearRequestRange(range);
|
||||
|
||||
AudioInvalidatedFromNode(cache, range);
|
||||
}
|
||||
|
||||
@@ -207,6 +220,7 @@ void PreviewAutoCacher::VideoRendered()
|
||||
QVector<RenderTicketPtr> tickets = video_immediate_passthroughs_.take(watcher);
|
||||
foreach (RenderTicketPtr t, tickets) {
|
||||
if (watcher->HasResult()) {
|
||||
t->setProperty("multicam_output", watcher->GetTicket()->property("multicam_output"));
|
||||
t->Finish(watcher->Get());
|
||||
} else {
|
||||
t->Finish();
|
||||
@@ -240,7 +254,10 @@ void PreviewAutoCacher::VideoRendered()
|
||||
void PreviewAutoCacher::ProcessUpdateQueue()
|
||||
{
|
||||
// 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) {
|
||||
case QueuedJob::kNodeAdded:
|
||||
AddNode(job.node);
|
||||
@@ -262,7 +279,6 @@ void PreviewAutoCacher::ProcessUpdateQueue()
|
||||
break;
|
||||
}
|
||||
}
|
||||
graph_update_queue_.clear();
|
||||
|
||||
// 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
|
||||
@@ -362,28 +378,30 @@ void PreviewAutoCacher::InsertIntoCopyMap(Node *node, Node *copy)
|
||||
Node::CopyInputs(node, copy, false);
|
||||
|
||||
// Connect to node's cache
|
||||
ConnectToNodeCache(node);
|
||||
if (!ignore_cache_requests_) {
|
||||
ConnectToNodeCache(node);
|
||||
}
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::ConnectToNodeCache(Node *node)
|
||||
{
|
||||
connect(node->video_frame_cache(),
|
||||
&PlaybackCache::Request,
|
||||
&PlaybackCache::Requested,
|
||||
this,
|
||||
&PreviewAutoCacher::VideoInvalidatedFromCache);
|
||||
|
||||
connect(node->thumbnail_cache(),
|
||||
&PlaybackCache::Request,
|
||||
&PlaybackCache::Requested,
|
||||
this,
|
||||
&PreviewAutoCacher::VideoInvalidatedFromCache);
|
||||
|
||||
connect(node->audio_playback_cache(),
|
||||
&PlaybackCache::Request,
|
||||
&PlaybackCache::Requested,
|
||||
this,
|
||||
&PreviewAutoCacher::AudioInvalidatedFromCache);
|
||||
|
||||
connect(node->waveform_cache(),
|
||||
&PlaybackCache::Request,
|
||||
&PlaybackCache::Requested,
|
||||
this,
|
||||
&PreviewAutoCacher::AudioInvalidatedFromCache);
|
||||
|
||||
@@ -396,27 +414,32 @@ void PreviewAutoCacher::ConnectToNodeCache(Node *node)
|
||||
&PlaybackCache::CancelAll,
|
||||
this,
|
||||
&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)
|
||||
{
|
||||
disconnect(node->video_frame_cache(),
|
||||
&PlaybackCache::Request,
|
||||
&PlaybackCache::Requested,
|
||||
this,
|
||||
&PreviewAutoCacher::VideoInvalidatedFromCache);
|
||||
|
||||
disconnect(node->thumbnail_cache(),
|
||||
&PlaybackCache::Request,
|
||||
&PlaybackCache::Requested,
|
||||
this,
|
||||
&PreviewAutoCacher::VideoInvalidatedFromCache);
|
||||
|
||||
disconnect(node->audio_playback_cache(),
|
||||
&PlaybackCache::Request,
|
||||
&PlaybackCache::Requested,
|
||||
this,
|
||||
&PreviewAutoCacher::AudioInvalidatedFromCache);
|
||||
|
||||
disconnect(node->waveform_cache(),
|
||||
&PlaybackCache::Request,
|
||||
&PlaybackCache::Requested,
|
||||
this,
|
||||
&PreviewAutoCacher::AudioInvalidatedFromCache);
|
||||
|
||||
@@ -466,6 +489,8 @@ void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeR
|
||||
using_tb = viewer_node_->GetVideoParams().frame_rate_as_time_base();
|
||||
}
|
||||
|
||||
cache->ClearRequestRange(range);
|
||||
|
||||
TimeRangeListFrameIterator iterator({range}, using_tb);
|
||||
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_);
|
||||
@@ -475,6 +500,9 @@ void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeR
|
||||
void PreviewAutoCacher::StartCachingAudioRange(PlaybackCache *cache, const TimeRange &range)
|
||||
{
|
||||
Node *node = cache->parent();
|
||||
|
||||
cache->ClearRequestRange(range);
|
||||
|
||||
pending_audio_jobs_.push_back({node, cache, range});
|
||||
audio_cache_data_[cache].job_tracker.insert(range, graph_changed_time_);
|
||||
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
|
||||
//CancelVideoTasks(node);
|
||||
|
||||
cache->ClearRequestRange(range);
|
||||
|
||||
// If auto-cache is enabled and a slider is not being dragged, queue up to hash these frames
|
||||
if (!NodeInputDragger::IsInputBeingDragged()) {
|
||||
StartCachingVideoRange(cache, range);
|
||||
@@ -498,6 +528,8 @@ void PreviewAutoCacher::AudioInvalidatedFromNode(PlaybackCache *cache, const Tim
|
||||
// cancelled, so some areas may end up unrendered forever
|
||||
// ClearAudioQueue();
|
||||
|
||||
cache->ClearRequestRange(range);
|
||||
|
||||
// If we're auto-caching audio or require realtime waveforms, we'll have to render this
|
||||
StartCachingAudioRange(cache, range);
|
||||
}
|
||||
@@ -553,37 +585,37 @@ void PreviewAutoCacher::SetRendersPaused(bool e)
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -591,7 +623,7 @@ void PreviewAutoCacher::TryRender()
|
||||
{
|
||||
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
|
||||
// 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.
|
||||
@@ -611,11 +643,19 @@ void PreviewAutoCacher::TryRender()
|
||||
single_frame_render_ = nullptr;
|
||||
|
||||
// Check if already caching this
|
||||
RenderTicketWatcher *watcher = RenderFrame(copied_viewer_node_->GetConnectedTextureOutput(),
|
||||
t->property("time").value<rational>(),
|
||||
nullptr,
|
||||
t->property("dry").toBool());
|
||||
video_immediate_passthroughs_[watcher].append(t);
|
||||
Node *n = Node::ValueToPtr<Node>(t->property("node"));
|
||||
Node *copy = copy_map_.value(n);
|
||||
|
||||
if (copy) {
|
||||
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_) {
|
||||
@@ -716,6 +756,9 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational&
|
||||
// Allow using cached images for this render job
|
||||
rvp.use_cache = true;
|
||||
|
||||
// Multicam
|
||||
rvp.multicam = static_cast<MultiCamNode*>(copy_map_.value(multicam_));
|
||||
|
||||
watcher->SetTicket(RenderManager::instance()->RenderFrame(rvp));
|
||||
|
||||
return watcher;
|
||||
@@ -822,6 +865,9 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node)
|
||||
video_cache_data_.clear();
|
||||
audio_cache_data_.clear();
|
||||
|
||||
// Clear multicam reference
|
||||
multicam_ = nullptr;
|
||||
|
||||
// Disconnect signals for future node additions/deletions
|
||||
NodeGraph* graph = viewer_node_->parent();
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ public:
|
||||
virtual ~PreviewAutoCacher() override;
|
||||
|
||||
RenderTicketPtr GetSingleFrame(const rational& t, bool dry = false);
|
||||
RenderTicketPtr GetSingleFrame(Node *n, const rational& t, bool dry = false);
|
||||
|
||||
RenderTicketPtr GetRangeOfAudio(TimeRange range);
|
||||
|
||||
@@ -89,6 +90,10 @@ public:
|
||||
|
||||
void SetRendersPaused(bool e);
|
||||
|
||||
void SetMulticamNode(MultiCamNode *n) { multicam_ = n; }
|
||||
|
||||
void SetIgnoreCacheRequests(bool e) { ignore_cache_requests_ = e; }
|
||||
|
||||
public slots:
|
||||
void SetDisplayColorProcessor(ColorProcessorPtr processor)
|
||||
{
|
||||
@@ -160,7 +165,7 @@ private:
|
||||
|
||||
Project copied_project_;
|
||||
|
||||
QVector<QueuedJob> graph_update_queue_;
|
||||
std::list<QueuedJob> graph_update_queue_;
|
||||
QHash<Node*, Node*> copy_map_;
|
||||
QHash<NodeGraph*, NodeGraph*> graph_map_;
|
||||
ViewerOutput* copied_viewer_node_;
|
||||
@@ -217,6 +222,10 @@ private:
|
||||
|
||||
ColorProcessorPtr display_color_processor_;
|
||||
|
||||
MultiCamNode *multicam_;
|
||||
|
||||
bool ignore_cache_requests_;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @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_);
|
||||
dry_run_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);
|
||||
@@ -111,6 +115,7 @@ RenderTicketPtr RenderManager::RenderFrame(const RenderVideoParams ¶ms)
|
||||
ticket->setProperty("cache", params.cache_dir);
|
||||
ticket->setProperty("cachetimebase", QVariant::fromValue(params.cache_timebase));
|
||||
ticket->setProperty("cacheid", QVariant::fromValue(params.cache_id));
|
||||
ticket->setProperty("multicam", Node::PtrToValue(params.multicam));
|
||||
|
||||
if (params.return_type == ReturnType::kNull) {
|
||||
dry_run_thread_->AddTicket(ticket);
|
||||
@@ -135,7 +140,10 @@ RenderTicketPtr RenderManager::RenderAudio(const RenderAudioParams ¶ms)
|
||||
ticket->setProperty("mode", params.mode);
|
||||
|
||||
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 {
|
||||
audio_thread_->AddTicket(ticket);
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@ public:
|
||||
force_size = QSize(0, 0);
|
||||
force_channel_count = 0;
|
||||
mode = m;
|
||||
multicam = nullptr;
|
||||
}
|
||||
|
||||
void AddCache(FrameHashCache *cache)
|
||||
@@ -134,6 +135,7 @@ public:
|
||||
bool use_cache;
|
||||
ReturnType return_type;
|
||||
RenderMode::Mode mode;
|
||||
MultiCamNode *multicam;
|
||||
|
||||
QString cache_dir;
|
||||
rational cache_timebase;
|
||||
@@ -230,7 +232,9 @@ private:
|
||||
RenderThread *video_thread_;
|
||||
RenderThread *dry_run_thread_;
|
||||
RenderThread *audio_thread_;
|
||||
RenderThread *waveform_thread_;
|
||||
|
||||
std::vector<RenderThread *> waveform_threads_;
|
||||
size_t last_waveform_thread_;
|
||||
|
||||
std::list<RenderThread *> render_threads_;
|
||||
|
||||
|
||||
+24
-106
@@ -294,118 +294,36 @@ DecoderPtr RenderProcessor::ResolveDecoderFromInput(const QString& decoder_id, c
|
||||
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)
|
||||
{
|
||||
RenderProcessor p(ticket, render_ctx, decoder_cache, shader_cache);
|
||||
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)
|
||||
{
|
||||
if (ticket_->property("type").value<RenderManager::TicketType>() != RenderManager::kTypeVideo) {
|
||||
|
||||
@@ -32,6 +32,8 @@ namespace olive {
|
||||
class RenderProcessor : public NodeTraverser
|
||||
{
|
||||
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);
|
||||
|
||||
struct RenderedWaveform {
|
||||
@@ -42,8 +44,6 @@ public:
|
||||
};
|
||||
|
||||
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 ProcessAudioFootage(SampleBuffer &destination, const FootageJob *stream, const TimeRange &input_time) override;
|
||||
|
||||
@@ -30,6 +30,7 @@ add_subdirectory(handmovableview)
|
||||
add_subdirectory(keyframeview)
|
||||
add_subdirectory(manageddisplay)
|
||||
add_subdirectory(menu)
|
||||
add_subdirectory(multicam)
|
||||
add_subdirectory(nodecombobox)
|
||||
add_subdirectory(nodeparamview)
|
||||
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()
|
||||
{
|
||||
if (RenderManager::instance()->backend() == RenderManager::kOpenGL) {
|
||||
|
||||
@@ -58,7 +58,8 @@ protected:
|
||||
virtual void initializeGL() override
|
||||
{
|
||||
connect(context(), &QOpenGLContext::aboutToBeDestroyed,
|
||||
this, &ManagedDisplayWidgetOpenGL::OnDestroy);
|
||||
this, &ManagedDisplayWidgetOpenGL::DestroyListener,
|
||||
Qt::DirectConnection);
|
||||
|
||||
emit OnInit();
|
||||
}
|
||||
@@ -211,6 +212,8 @@ protected:
|
||||
return wrapper_ ? wrapper_->rect() : QRect();
|
||||
}
|
||||
|
||||
VideoParams GetViewportParams() const;
|
||||
|
||||
protected slots:
|
||||
/**
|
||||
* @brief Called whenever the internal rendering context has been created
|
||||
|
||||
@@ -293,7 +293,7 @@ void MenuShared::EnableDisableTriggered()
|
||||
|
||||
void MenuShared::NestTriggered()
|
||||
{
|
||||
qDebug() << "FIXME: Stub";
|
||||
PanelManager::instance()->MostRecentlyFocused<TimelinePanel>()->NestSelectedClips();
|
||||
}
|
||||
|
||||
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
|
||||
context_items_.resize(Track::kCount + 1);
|
||||
for (int i=0; i<context_items_.size(); i++) {
|
||||
NodeParamViewContext *c = new NodeParamViewContext;
|
||||
NodeParamViewContext *c = new NodeParamViewContext(param_widget_area_);
|
||||
c->setVisible(false);
|
||||
connect(c, &NodeParamViewContext::AboutToDeleteItem, this, &NodeParamView::ItemAboutToBeRemoved, Qt::DirectConnection);
|
||||
|
||||
@@ -245,8 +245,6 @@ void NodeParamView::DeselectNodes(const QVector<Node *> &nodes)
|
||||
|
||||
void NodeParamView::UpdateContexts()
|
||||
{
|
||||
//TIME_THIS_FUNCTION;
|
||||
|
||||
bool changes_made = false;
|
||||
|
||||
foreach (Node *ctx, current_contexts_) {
|
||||
@@ -735,7 +733,7 @@ void NodeParamView::AddNode(Node *n, Node *ctx, NodeParamViewContext *context)
|
||||
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::RequestSelectNode, this, &NodeParamView::SelectNodeFromConnectedLink);
|
||||
|
||||
@@ -50,13 +50,13 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input,
|
||||
label_layout->setMargin(0);
|
||||
layout->addLayout(label_layout);
|
||||
|
||||
CollapseButton *collapse_btn = new CollapseButton();
|
||||
CollapseButton *collapse_btn = new CollapseButton(this);
|
||||
collapse_btn->setChecked(false);
|
||||
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_->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
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::InputDisconnected, this, &NodeParamViewConnectedLabel::InputDisconnected);
|
||||
|
||||
// Set up table area
|
||||
value_tree_ = new NodeValueTree();
|
||||
value_tree_->setVisible(false);
|
||||
layout->addWidget(value_tree_);
|
||||
// Creating the tree is expensive, hold off until the user specifically requests it
|
||||
value_tree_ = nullptr;
|
||||
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)
|
||||
{
|
||||
time_ = time;
|
||||
|
||||
if (value_tree_->isVisible()) {
|
||||
if (value_tree_ && value_tree_->isVisible()) {
|
||||
UpdateValueTree();
|
||||
}
|
||||
}
|
||||
@@ -154,14 +159,22 @@ void NodeParamViewConnectedLabel::UpdateLabel()
|
||||
|
||||
void NodeParamViewConnectedLabel::UpdateValueTree()
|
||||
{
|
||||
value_tree_->SetNode(input_, time_);
|
||||
if (value_tree_) {
|
||||
value_tree_->SetNode(input_, time_);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewConnectedLabel::SetValueTreeVisible(bool e)
|
||||
{
|
||||
value_tree_->setVisible(e);
|
||||
if (value_tree_) {
|
||||
value_tree_->setVisible(e);
|
||||
}
|
||||
|
||||
if (e) {
|
||||
if (!value_tree_) {
|
||||
CreateTree();
|
||||
}
|
||||
|
||||
UpdateValueTree();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ private:
|
||||
|
||||
void UpdateValueTree();
|
||||
|
||||
void CreateTree();
|
||||
|
||||
ClickableLabel* connected_to_lbl_;
|
||||
|
||||
NodeInput input_;
|
||||
|
||||
@@ -88,7 +88,7 @@ void NodeParamViewItem::RecreateBody()
|
||||
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::RequestSetTime, this, &NodeParamViewItem::RequestSetTime);
|
||||
connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged);
|
||||
@@ -148,7 +148,7 @@ NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBe
|
||||
|
||||
if (n->InputIsArray(input)) {
|
||||
// Insert here
|
||||
QWidget* array_widget = new QWidget();
|
||||
QWidget* array_widget = new QWidget(this);
|
||||
|
||||
QGridLayout* array_layout = new QGridLayout(array_widget);
|
||||
array_layout->setContentsMargins(QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral(" ")), 0, 0, 0);
|
||||
@@ -160,7 +160,7 @@ NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBe
|
||||
int arr_sz = 0;
|
||||
|
||||
// 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);
|
||||
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
|
||||
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);
|
||||
layout->addWidget(ui_objects.optional_checkbox, row, kOptionalCheckBox);
|
||||
|
||||
@@ -196,7 +196,7 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
|
||||
}
|
||||
|
||||
// Add descriptor label
|
||||
ui_objects.main_label = new QLabel();
|
||||
ui_objects.main_label = new QLabel(this);
|
||||
|
||||
// Create input label
|
||||
layout->addWidget(ui_objects.main_label, row, kLabelColumn);
|
||||
@@ -205,7 +205,7 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
|
||||
if (element == -1) {
|
||||
|
||||
// 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
|
||||
array_collapse_btn->setChecked(false);
|
||||
@@ -220,8 +220,8 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
|
||||
|
||||
} else {
|
||||
|
||||
NodeParamViewArrayButton* insert_element_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kAdd);
|
||||
NodeParamViewArrayButton* remove_element_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kRemove);
|
||||
NodeParamViewArrayButton* insert_element_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kAdd, this);
|
||||
NodeParamViewArrayButton* remove_element_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kRemove, this);
|
||||
|
||||
layout->addWidget(insert_element_btn, row, kArrayInsertColumn);
|
||||
layout->addWidget(remove_element_btn, row, kArrayRemoveColumn);
|
||||
@@ -249,14 +249,14 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
|
||||
|
||||
if (node->IsInputConnectable(input)) {
|
||||
// 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);
|
||||
layout->addWidget(ui_objects.connected_label, row, kWidgetStartColumn, 1, kKeyControlColumn - kWidgetStartColumn);
|
||||
}
|
||||
|
||||
// Add keyframe control to this layout if parameter is keyframable
|
||||
if (node->IsInputKeyframable(input)) {
|
||||
ui_objects.key_control = new NodeParamViewKeyframeControl();
|
||||
ui_objects.key_control = new NodeParamViewKeyframeControl(this);
|
||||
ui_objects.key_control->SetInput(resolved);
|
||||
layout->addWidget(ui_objects.key_control, row, kKeyControlColumn);
|
||||
connect(ui_objects.key_control, &NodeParamViewKeyframeControl::RequestSetTime, this, &NodeParamViewItemBody::RequestSetTime);
|
||||
|
||||
@@ -33,31 +33,31 @@ NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) :
|
||||
{
|
||||
QHBoxLayout* layout = new QHBoxLayout(this);
|
||||
|
||||
collapse_btn_ = new CollapseButton();
|
||||
collapse_btn_ = new CollapseButton(this);
|
||||
connect(collapse_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::ExpandedStateChanged);
|
||||
layout->addWidget(collapse_btn_);
|
||||
|
||||
lbl_ = new QLabel();
|
||||
lbl_ = new QLabel(this);
|
||||
layout->addWidget(lbl_);
|
||||
|
||||
// Place next buttons on the far side
|
||||
layout->addStretch();
|
||||
|
||||
add_fx_btn_ = new QPushButton();
|
||||
add_fx_btn_ = new QPushButton(this);
|
||||
add_fx_btn_->setIcon(icon::AddEffect);
|
||||
add_fx_btn_->setFixedSize(add_fx_btn_->sizeHint().height(), add_fx_btn_->sizeHint().height());
|
||||
add_fx_btn_->setVisible(false);
|
||||
layout->addWidget(add_fx_btn_);
|
||||
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_->setFixedSize(pin_btn_->sizeHint().height(), pin_btn_->sizeHint().height());
|
||||
pin_btn_->setVisible(false);
|
||||
layout->addWidget(pin_btn_);
|
||||
connect(pin_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::PinToggled);
|
||||
|
||||
enabled_checkbox_ = new QCheckBox();
|
||||
enabled_checkbox_ = new QCheckBox(this);
|
||||
enabled_checkbox_->setVisible(false);
|
||||
layout->addWidget(enabled_checkbox_);
|
||||
connect(enabled_checkbox_, &QCheckBox::clicked, this, &NodeParamViewItemTitleBar::EnabledCheckBoxClicked);
|
||||
|
||||
@@ -33,7 +33,11 @@ class NodeParamViewKeyframeControl : public QWidget, public TimeTargetObject
|
||||
{
|
||||
Q_OBJECT
|
||||
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
|
||||
{
|
||||
|
||||
@@ -71,9 +71,11 @@ int GetSliderCount(NodeValue::Type type)
|
||||
|
||||
void NodeParamViewWidgetBridge::CreateWidgets()
|
||||
{
|
||||
QWidget *parent = dynamic_cast<QWidget*>(this->parent());
|
||||
|
||||
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);
|
||||
widgets_.append(w);
|
||||
|
||||
@@ -94,12 +96,12 @@ void NodeParamViewWidgetBridge::CreateWidgets()
|
||||
break;
|
||||
case NodeValue::kInt:
|
||||
{
|
||||
CreateSliders<IntegerSlider>(1);
|
||||
CreateSliders<IntegerSlider>(1, parent);
|
||||
break;
|
||||
}
|
||||
case NodeValue::kRational:
|
||||
{
|
||||
CreateSliders<RationalSlider>(1);
|
||||
CreateSliders<RationalSlider>(1, parent);
|
||||
break;
|
||||
}
|
||||
case NodeValue::kFloat:
|
||||
@@ -107,12 +109,12 @@ void NodeParamViewWidgetBridge::CreateWidgets()
|
||||
case NodeValue::kVec3:
|
||||
case NodeValue::kVec4:
|
||||
{
|
||||
CreateSliders<FloatSlider>(GetSliderCount(t));
|
||||
CreateSliders<FloatSlider>(GetSliderCount(t), parent);
|
||||
break;
|
||||
}
|
||||
case NodeValue::kCombo:
|
||||
{
|
||||
QComboBox* combobox = new QComboBox();
|
||||
QComboBox* combobox = new QComboBox(parent);
|
||||
|
||||
QStringList items = GetInnerInput().GetComboBoxStrings();
|
||||
foreach (const QString& s, items) {
|
||||
@@ -125,21 +127,21 @@ void NodeParamViewWidgetBridge::CreateWidgets()
|
||||
}
|
||||
case NodeValue::kFile:
|
||||
{
|
||||
FileField* file_field = new FileField();
|
||||
FileField* file_field = new FileField(parent);
|
||||
widgets_.append(file_field);
|
||||
connect(file_field, &FileField::FilenameChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||
break;
|
||||
}
|
||||
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);
|
||||
connect(color_button, &ColorButton::ColorChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||
break;
|
||||
}
|
||||
case NodeValue::kText:
|
||||
{
|
||||
NodeParamViewTextEdit* line_edit = new NodeParamViewTextEdit();
|
||||
NodeParamViewTextEdit* line_edit = new NodeParamViewTextEdit(parent);
|
||||
widgets_.append(line_edit);
|
||||
connect(line_edit, &NodeParamViewTextEdit::textEdited, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||
connect(line_edit, &NodeParamViewTextEdit::RequestEditInViewer, this, &NodeParamViewWidgetBridge::RequestEditTextInViewer);
|
||||
@@ -147,21 +149,21 @@ void NodeParamViewWidgetBridge::CreateWidgets()
|
||||
}
|
||||
case NodeValue::kBoolean:
|
||||
{
|
||||
QCheckBox* check_box = new QCheckBox();
|
||||
QCheckBox* check_box = new QCheckBox(parent);
|
||||
widgets_.append(check_box);
|
||||
connect(check_box, &QCheckBox::clicked, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||
break;
|
||||
}
|
||||
case NodeValue::kFont:
|
||||
{
|
||||
QFontComboBox* font_combobox = new QFontComboBox();
|
||||
QFontComboBox* font_combobox = new QFontComboBox(parent);
|
||||
widgets_.append(font_combobox);
|
||||
connect(font_combobox, &QFontComboBox::currentFontChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||
break;
|
||||
}
|
||||
case NodeValue::kBezier:
|
||||
{
|
||||
BezierWidget *bezier = new BezierWidget();
|
||||
BezierWidget *bezier = new BezierWidget(parent);
|
||||
widgets_.append(bezier);
|
||||
|
||||
connect(bezier->x_slider(), &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||
@@ -384,10 +386,10 @@ void NodeParamViewWidgetBridge::WidgetCallback()
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void NodeParamViewWidgetBridge::CreateSliders(int count)
|
||||
void NodeParamViewWidgetBridge::CreateSliders(int count, QWidget *parent)
|
||||
{
|
||||
for (int i=0;i<count;i++) {
|
||||
T* fs = new T();
|
||||
T* fs = new T(parent);
|
||||
fs->SliderBase::SetDefaultValue(GetInnerInput().GetSplitDefaultValueForTrack(i));
|
||||
fs->SetLadderElementCount(2);
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ private:
|
||||
void SetProperty(const QString &key, const QVariant &value);
|
||||
|
||||
template <typename T>
|
||||
void CreateSliders(int count);
|
||||
void CreateSliders(int count, QWidget *parent);
|
||||
|
||||
void UpdateWidgetValues();
|
||||
|
||||
|
||||
@@ -47,10 +47,10 @@ void HistogramScope::OnInit()
|
||||
|
||||
void HistogramScope::OnDestroy()
|
||||
{
|
||||
super::OnDestroy();
|
||||
|
||||
pipeline_secondary_.clear();
|
||||
texture_row_sums_ = nullptr;
|
||||
|
||||
super::OnDestroy();
|
||||
}
|
||||
|
||||
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)));
|
||||
|
||||
renderer()->Blit(pipeline, job, VideoParams(width(), height(),
|
||||
static_cast<VideoParams::Format>(OLIVE_CONFIG("OfflinePixelFormat").toInt()),
|
||||
VideoParams::kInternalChannelCount));
|
||||
renderer()->Blit(pipeline, job, GetViewportParams());
|
||||
}
|
||||
|
||||
void ScopeBase::OnInit()
|
||||
@@ -89,11 +87,11 @@ void ScopeBase::OnPaint()
|
||||
|
||||
void ScopeBase::OnDestroy()
|
||||
{
|
||||
super::OnDestroy();
|
||||
|
||||
managed_tex_ = nullptr;
|
||||
texture_ = nullptr;
|
||||
pipeline_.clear();
|
||||
|
||||
super::OnDestroy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -72,9 +72,7 @@ void WaveformScope::DrawScope(TexturePtr managed_tex, QVariant pipeline)
|
||||
job.Insert(QStringLiteral("ove_maintex"),
|
||||
NodeValue(NodeValue::kTexture, QVariant::fromValue(managed_tex)));
|
||||
|
||||
renderer()->Blit(pipeline, job, VideoParams(width(), height(),
|
||||
static_cast<VideoParams::Format>(OLIVE_CONFIG("OfflinePixelFormat").toInt()),
|
||||
VideoParams::kInternalChannelCount));
|
||||
renderer()->Blit(pipeline, job, GetViewportParams());
|
||||
|
||||
float waveform_dim_x = ceil((width() - 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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()
|
||||
@@ -788,13 +792,14 @@ void TimelineWidget::RecordingCallback(const QString &filename, const TimeRange
|
||||
task.Start();
|
||||
|
||||
MultiUndoCommand *import_command = task.GetCommand();
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(import_command);
|
||||
|
||||
if (task.GetImportedFootage().empty()) {
|
||||
qCritical() << "Failed to import recorded audio file" << filename;
|
||||
} 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)
|
||||
@@ -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()
|
||||
{
|
||||
if (subtitle_show_command_) {
|
||||
@@ -1140,6 +1234,31 @@ void TimelineWidget::ShowContextMenu()
|
||||
QAction *reveal_in_project = menu.addAction(tr("Reveal in Project"));
|
||||
reveal_in_project->setData(reinterpret_cast<quintptr>(clip->connected_viewer()));
|
||||
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)
|
||||
{
|
||||
ghost_items_.append(ghost);
|
||||
|
||||
@@ -112,6 +112,8 @@ public:
|
||||
|
||||
void AddTentativeSubtitleTrack();
|
||||
|
||||
void NestSelectedClips();
|
||||
|
||||
/**
|
||||
* @brief Timelines should always be connected to sequences
|
||||
*/
|
||||
@@ -444,6 +446,9 @@ private slots:
|
||||
void CacheClipsInOut();
|
||||
void CacheDiscard();
|
||||
|
||||
void MulticamEnabledTriggered(bool e);
|
||||
void MulticamUpdateTriggered();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -183,7 +183,9 @@ void ImportTool::DragLeave(QDragLeaveEvent* event)
|
||||
void ImportTool::DragDrop(TimelineViewMouseEvent *event)
|
||||
{
|
||||
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();
|
||||
} 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;
|
||||
|
||||
@@ -199,10 +201,10 @@ void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational
|
||||
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;
|
||||
|
||||
@@ -211,7 +213,7 @@ void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &star
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -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()) {
|
||||
command->add_child(c);
|
||||
@@ -500,7 +502,10 @@ void ImportTool::DropGhosts(bool insert)
|
||||
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()) {
|
||||
imported_clips.front()->RequestInvalidatedFromConnected();
|
||||
|
||||
@@ -37,8 +37,8 @@ public:
|
||||
|
||||
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 DraggedFootageData &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, MultiUndoCommand *command, int track_offset = 0);
|
||||
|
||||
enum DropWithoutSequenceBehavior {
|
||||
kDWSAsk,
|
||||
@@ -52,7 +52,7 @@ private:
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ bool TransitionTool::GetBlocksAtCoord(const TimelineCoordinate &coord, ClipBlock
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -97,62 +97,68 @@ void BlockSplitCommand::undo()
|
||||
//
|
||||
// BlockSplitPreservingLinksCommand
|
||||
//
|
||||
void BlockSplitPreservingLinksCommand::redo()
|
||||
Block *BlockSplitPreservingLinksCommand::GetSplit(Block *original, int time_index) const
|
||||
{
|
||||
if (commands_.isEmpty()) {
|
||||
QVector< QVector<Block*> > split_blocks(times_.size());
|
||||
|
||||
for (int i=0;i<times_.size();i++) {
|
||||
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);
|
||||
if (time_index >= 0 && time_index < times_.size()) {
|
||||
int original_index = blocks_.indexOf(original);
|
||||
if (original_index != -1) {
|
||||
return splits_.at(time_index).at(original_index);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (int j=0;j<blocks_.size();j++) {
|
||||
if (i == j) {
|
||||
continue;
|
||||
}
|
||||
void BlockSplitPreservingLinksCommand::prepare()
|
||||
{
|
||||
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)) {
|
||||
// These blocks are linked, ensure all the splits are linked too
|
||||
// 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));
|
||||
|
||||
foreach (const QVector<Block*>& split_list, split_blocks) {
|
||||
NodeLinkCommand* blc = new NodeLinkCommand(split_list.at(i), split_list.at(j), true);
|
||||
blc->redo_now();
|
||||
commands_.append(blc);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i=0; i<commands_.size(); i++) {
|
||||
commands_.at(i)->redo_now();
|
||||
|
||||
splits_.replace(i, splits);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
Block *GetSplit(Block *original, int time_index) const;
|
||||
|
||||
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
|
||||
{
|
||||
@@ -108,6 +117,8 @@ private:
|
||||
|
||||
QVector<UndoCommand*> commands_;
|
||||
|
||||
QVector< QVector<Block*> > splits_;
|
||||
|
||||
};
|
||||
|
||||
class TrackSplitAtTimeCommand : public UndoCommand {
|
||||
|
||||
@@ -53,7 +53,7 @@ void AudioWaveformView::SetViewer(ViewerOutput *playback)
|
||||
pool_.clear();
|
||||
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);
|
||||
}
|
||||
@@ -61,7 +61,7 @@ void AudioWaveformView::SetViewer(ViewerOutput *playback)
|
||||
playback_ = 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());
|
||||
}
|
||||
|
||||
+118
-11
@@ -40,10 +40,13 @@
|
||||
#include "node/block/gap/gap.h"
|
||||
#include "node/generator/shape/shapenodebase.h"
|
||||
#include "node/project/project.h"
|
||||
#include "panel/multicam/multicampanel.h"
|
||||
#include "panel/panelmanager.h"
|
||||
#include "render/rendermanager.h"
|
||||
#include "viewerpreventsleep.h"
|
||||
#include "widget/audiomonitor/audiomonitor.h"
|
||||
#include "widget/menu/menu.h"
|
||||
#include "widget/multicam/multicamdisplay.h"
|
||||
#include "widget/nodeparamview/nodeparamviewundo.h"
|
||||
#include "widget/timelinewidget/tool/add.h"
|
||||
#include "widget/timeruler/timeruler.h"
|
||||
@@ -62,7 +65,7 @@ const rational ViewerWidget::kAudioPlaybackInterval = rational(1, 4);
|
||||
|
||||
const rational kVideoPlaybackInterval = rational(1, 2);
|
||||
|
||||
ViewerWidget::ViewerWidget(QWidget *parent) :
|
||||
ViewerWidget::ViewerWidget(ViewerDisplayWidget *display, QWidget *parent) :
|
||||
super(false, true, parent),
|
||||
playback_speed_(0),
|
||||
color_menu_enabled_(true),
|
||||
@@ -74,7 +77,8 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
|
||||
first_requeue_watcher_(nullptr),
|
||||
enable_audio_scrubbing_(true),
|
||||
waveform_mode_(kWFAutomatic),
|
||||
ignore_scrub_(0)
|
||||
ignore_scrub_(0),
|
||||
multicam_panel_(nullptr)
|
||||
{
|
||||
// Set up main layout
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
@@ -82,10 +86,9 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
|
||||
|
||||
// Create main OpenGL-based view and sizer
|
||||
sizer_ = new ViewerSizer();
|
||||
sizer_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
layout->addWidget(sizer_);
|
||||
|
||||
display_widget_ = new ViewerDisplayWidget();
|
||||
display_widget_ = display;
|
||||
display_widget_->SetShowWidgetBackground(true);
|
||||
playback_devices_.append(display_widget_);
|
||||
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, &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();
|
||||
audio_scrub_watchers_.clear();
|
||||
|
||||
SetDisplayImage(QVariant());
|
||||
SetDisplayImage(nullptr);
|
||||
|
||||
ruler()->SetPlaybackCache(nullptr);
|
||||
|
||||
@@ -416,6 +425,11 @@ void ViewerWidget::StartCapture(TimelineWidget *source, const TimeRange &time, c
|
||||
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 frame = FrameHashCache::LoadCacheFrame(cache_path, cache_id, time);
|
||||
@@ -580,7 +594,7 @@ void ViewerWidget::RequestNextDryRun()
|
||||
} else {
|
||||
RenderTicketWatcher *watcher = new RenderTicketWatcher(this);
|
||||
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_watchers_.append(watcher);
|
||||
}
|
||||
@@ -604,6 +618,75 @@ void ViewerWidget::SetWaveformMode(WaveformMode wf)
|
||||
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()
|
||||
{
|
||||
bool prefer_waveform = ShouldForceWaveform();
|
||||
@@ -790,6 +873,8 @@ void ViewerWidget::UpdateTextureFromNode()
|
||||
// Clear queue because we want this frame more than any others
|
||||
auto_cacher_->ClearSingleFrameRenders();
|
||||
|
||||
DetectMulticamNode(time);
|
||||
|
||||
watcher->SetTicket(GetFrame(time));
|
||||
} else {
|
||||
// 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();
|
||||
}
|
||||
|
||||
void ViewerWidget::SetDisplayImage(QVariant frame)
|
||||
void ViewerWidget::SetDisplayImage(RenderTicketPtr ticket)
|
||||
{
|
||||
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->setProperty("time", QVariant::fromValue(next_time));
|
||||
DetectMulticamNode(next_time);
|
||||
connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrameForQueue);
|
||||
queue_watchers_.append(watcher);
|
||||
watcher->SetTicket(GetFrame(next_time));
|
||||
@@ -1033,7 +1127,7 @@ RenderTicketPtr ViewerWidget::GetFrame(const rational &t)
|
||||
|
||||
if (!QFileInfo::exists(cache_fn)) {
|
||||
// Frame hasn't been cached, start render job
|
||||
return auto_cacher_->GetSingleFrame(t);
|
||||
return GetSingleFrame(t);
|
||||
} else {
|
||||
// Frame has been cached, grab the frame
|
||||
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>();
|
||||
|
||||
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_) {
|
||||
@@ -1678,7 +1779,13 @@ void ViewerWidget::UpdateRendererVideoParameters()
|
||||
|
||||
void ViewerWidget::UpdateRendererAudioParameters()
|
||||
{
|
||||
AudioParams ap = GetConnectedNode()->GetAudioParams();
|
||||
|
||||
UpdateAudioProcessor();
|
||||
|
||||
foreach (ViewerDisplayWidget *dw, playback_devices_) {
|
||||
dw->SetAudioParams(ap);
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerWidget::SetZoomFromMenu(QAction *action)
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
class MulticamWidget;
|
||||
|
||||
/**
|
||||
* @brief An OpenGL-based viewer widget with playback controls (a PlaybackControls widget).
|
||||
*/
|
||||
@@ -57,7 +59,9 @@ public:
|
||||
kWFViewerAndWaveform
|
||||
};
|
||||
|
||||
ViewerWidget(QWidget* parent = nullptr);
|
||||
ViewerWidget(QWidget* parent = nullptr) :
|
||||
ViewerWidget(new ViewerDisplayWidget(), parent)
|
||||
{}
|
||||
|
||||
virtual ~ViewerWidget() override;
|
||||
|
||||
@@ -100,6 +104,37 @@ public:
|
||||
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:
|
||||
void Play(bool in_to_out_only);
|
||||
|
||||
@@ -157,6 +192,8 @@ signals:
|
||||
void ColorManagerChanged(ColorManager* color_manager);
|
||||
|
||||
protected:
|
||||
ViewerWidget(ViewerDisplayWidget *display, QWidget* parent = nullptr);
|
||||
|
||||
virtual void TimebaseChangedEvent(const rational &) override;
|
||||
virtual void TimeChangedEvent(const rational &time) override;
|
||||
|
||||
@@ -182,6 +219,13 @@ protected:
|
||||
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:
|
||||
int64_t GetTimestamp() const
|
||||
{
|
||||
@@ -206,7 +250,7 @@ private:
|
||||
|
||||
bool ViewerMightBeAStill();
|
||||
|
||||
void SetDisplayImage(QVariant frame);
|
||||
void SetDisplayImage(RenderTicketPtr ticket);
|
||||
|
||||
RenderTicketWatcher *RequestNextFrameForQueue(bool increment = true);
|
||||
|
||||
@@ -236,6 +280,8 @@ private:
|
||||
|
||||
void SetWaveformMode(WaveformMode wf);
|
||||
|
||||
void DetectMulticamNode(const rational &time);
|
||||
|
||||
ViewerSizer* sizer_;
|
||||
|
||||
int playback_speed_;
|
||||
@@ -304,6 +350,11 @@ private:
|
||||
|
||||
int ignore_scrub_;
|
||||
|
||||
QVector<Block*> timeline_selected_blocks_;
|
||||
QVector<Node*> node_view_selected_;
|
||||
|
||||
MulticamWidget *multicam_panel_;
|
||||
|
||||
private slots:
|
||||
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)
|
||||
{
|
||||
time_ = time;
|
||||
@@ -361,10 +370,7 @@ void ViewerDisplayWidget::OnPaint()
|
||||
// We only draw if we have a pipeline
|
||||
if (push_mode_ != kPushNull) {
|
||||
// Draw texture through color transform
|
||||
int device_width = width() * devicePixelRatioF();
|
||||
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);
|
||||
VideoParams device_params = GetViewportParams();
|
||||
|
||||
if (push_mode_ == kPushBlank) {
|
||||
if (blank_shader_.isNull()) {
|
||||
@@ -392,6 +398,8 @@ void ViewerDisplayWidget::OnPaint()
|
||||
} else if (TexturePtr texture = load_frame_.value<TexturePtr>()) {
|
||||
// This is a GPU texture, switch to it directly
|
||||
texture_ = texture;
|
||||
} else {
|
||||
texture_ = LoadCustomTextureFromFrame(load_frame_);
|
||||
}
|
||||
|
||||
emit TextureChanged(texture_);
|
||||
@@ -434,17 +442,13 @@ void ViewerDisplayWidget::OnPaint()
|
||||
|
||||
// Draw gizmos if we have any
|
||||
if (gizmos_) {
|
||||
NodeTraverser gt;
|
||||
gt.SetCacheVideoParams(gizmo_params_);
|
||||
|
||||
TimeRange range = GenerateGizmoTime();
|
||||
gizmo_db_ = gt.GenerateRow(gizmos_, range);
|
||||
|
||||
QPainter p(paint_device());
|
||||
gizmo_last_draw_transform_ = GenerateGizmoTransform(gt, range);
|
||||
|
||||
GenerateGizmoTransforms();
|
||||
|
||||
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()) {
|
||||
if (gizmo->IsVisible()) {
|
||||
gizmo->Draw(&p);
|
||||
@@ -815,18 +819,17 @@ bool ViewerDisplayWidget::OnMousePress(QMouseEvent *event)
|
||||
add_band_ = true;
|
||||
|
||||
} 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
|
||||
gizmo_start_drag_ = event->pos();
|
||||
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 {
|
||||
|
||||
// Handle standard drag
|
||||
emit DragStarted();
|
||||
emit DragStarted(event->pos());
|
||||
|
||||
}
|
||||
|
||||
@@ -874,28 +877,29 @@ bool ViewerDisplayWidget::OnMouseMove(QMouseEvent *event)
|
||||
// Signal movement
|
||||
if (DraggableGizmo *draggable = dynamic_cast<DraggableGizmo*>(current_gizmo_)) {
|
||||
if (!gizmo_drag_started_) {
|
||||
QPointF start = gizmo_start_drag_ * gizmo_last_draw_transform_inverted_;
|
||||
QPointF start = ScreenToScenePoint(gizmo_start_drag_);
|
||||
|
||||
rational gizmo_time = GetGizmoTime();
|
||||
NodeTraverser t;
|
||||
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()));
|
||||
|
||||
draggable->DragStart(row, start.x(), start.y(), gizmo_time);
|
||||
gizmo_drag_started_ = true;
|
||||
}
|
||||
|
||||
QPointF v = event->pos() * gizmo_last_draw_transform_inverted_;
|
||||
QPointF v = ScreenToScenePoint(event->pos());
|
||||
switch (draggable->GetDragValueBehavior()) {
|
||||
case DraggableGizmo::kAbsolute:
|
||||
// Above value is correct
|
||||
break;
|
||||
case DraggableGizmo::kDeltaFromPrevious:
|
||||
v -= gizmo_last_drag_ * gizmo_last_draw_transform_inverted_;
|
||||
v -= ScreenToScenePoint(gizmo_last_drag_);
|
||||
gizmo_last_drag_ = event->pos();
|
||||
break;
|
||||
case DraggableGizmo::kDeltaFromStart:
|
||||
v -= gizmo_start_drag_ * gizmo_last_draw_transform_inverted_;
|
||||
v -= ScreenToScenePoint(gizmo_start_drag_);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1056,7 +1060,7 @@ void ViewerDisplayWidget::DrawSubtitleTracks()
|
||||
for (int j=subtitle_tracklist.size()-1; j>=0; j--) {
|
||||
Track *sub_track = subtitle_tracklist.at(j);
|
||||
if (!sub_track->IsMuted()) {
|
||||
if (SubtitleBlock *sub = dynamic_cast<SubtitleBlock*>(sub_track->BlockAtTime(time_))) {
|
||||
if (SubtitleBlock *sub = dynamic_cast<SubtitleBlock*>(sub_track->VisibleBlockAtTime(time_))) {
|
||||
// Split into lines
|
||||
QStringList list = QtUtils::WordWrapString(sub->GetText(), fm, bounding_box.width());
|
||||
|
||||
@@ -1185,6 +1189,22 @@ void ViewerDisplayWidget::CloseTextEditor()
|
||||
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)
|
||||
{
|
||||
show_fps_ = e;
|
||||
@@ -1224,6 +1244,15 @@ void ViewerDisplayWidget::Pause()
|
||||
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()
|
||||
{
|
||||
int64_t t = timer_.GetTimestampNow();
|
||||
|
||||
@@ -74,7 +74,13 @@ public:
|
||||
void SetSafeMargins(const ViewerSafeMarginInfo& safe_margin);
|
||||
|
||||
void SetGizmos(Node* node);
|
||||
|
||||
const VideoParams &GetVideoParams() const { return gizmo_params_; }
|
||||
void SetVideoParams(const VideoParams ¶ms);
|
||||
|
||||
const AudioParams &GetAudioParams() const { return gizmo_audio_params_; }
|
||||
void SetAudioParams(const AudioParams &p);
|
||||
|
||||
void SetTime(const rational& time);
|
||||
void SetSubtitleTracks(Sequence *list);
|
||||
|
||||
@@ -131,6 +137,8 @@ public:
|
||||
return &timer_;
|
||||
}
|
||||
|
||||
QPointF ScreenToScenePoint(const QPoint &p);
|
||||
|
||||
virtual bool eventFilter(QObject *o, QEvent *e) override;
|
||||
|
||||
public slots:
|
||||
@@ -182,7 +190,7 @@ signals:
|
||||
/**
|
||||
* @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
|
||||
@@ -218,6 +226,30 @@ signals:
|
||||
|
||||
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:
|
||||
/**
|
||||
* @brief Paint function to display the texture (received in SetTexture()) on screen.
|
||||
@@ -241,24 +273,6 @@ private:
|
||||
|
||||
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);
|
||||
|
||||
void OpenTextGizmo(TextGizmo *text, QMouseEvent *event = nullptr);
|
||||
@@ -290,6 +304,8 @@ private:
|
||||
|
||||
void CloseTextEditor();
|
||||
|
||||
void GenerateGizmoTransforms();
|
||||
|
||||
/**
|
||||
* @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL().
|
||||
*/
|
||||
@@ -338,8 +354,10 @@ private:
|
||||
Node* gizmos_;
|
||||
NodeValueRow gizmo_db_;
|
||||
VideoParams gizmo_params_;
|
||||
AudioParams gizmo_audio_params_;
|
||||
QPoint gizmo_start_drag_;
|
||||
QPoint gizmo_last_drag_;
|
||||
TimeRange gizmo_draw_time_;
|
||||
NodeGizmo *current_gizmo_;
|
||||
bool gizmo_drag_started_;
|
||||
QTransform gizmo_last_draw_transform_;
|
||||
|
||||
@@ -27,12 +27,14 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
struct ViewerPlaybackFrame {
|
||||
struct ViewerPlaybackFrame
|
||||
{
|
||||
rational timestamp;
|
||||
QVariant frame;
|
||||
};
|
||||
|
||||
class ViewerQueue : public std::list<ViewerPlaybackFrame> {
|
||||
class ViewerQueue : public std::list<ViewerPlaybackFrame>
|
||||
{
|
||||
public:
|
||||
ViewerQueue() = default;
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ ViewerSizer::ViewerSizer(QWidget *parent) :
|
||||
vert_scrollbar_ = new QScrollBar(Qt::Vertical, this);
|
||||
vert_scrollbar_->setVisible(false);
|
||||
connect(vert_scrollbar_, &QScrollBar::valueChanged, this, &ViewerSizer::ScrollBarMoved);
|
||||
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
}
|
||||
|
||||
void ViewerSizer::SetWidget(QWidget *widget)
|
||||
|
||||
@@ -51,6 +51,7 @@ public:
|
||||
*/
|
||||
void SetWidget(QWidget* widget);
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Set resolution to use
|
||||
*
|
||||
@@ -70,7 +71,6 @@ public:
|
||||
*/
|
||||
void SetZoom(int percent);
|
||||
|
||||
public slots:
|
||||
void HandDragMove(int x, int y);
|
||||
|
||||
signals:
|
||||
|
||||
@@ -87,6 +87,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
param_panel_ = new ParamPanel(this);
|
||||
curve_panel_ = new CurvePanel(this);
|
||||
sequence_viewer_panel_ = new SequenceViewerPanel(this);
|
||||
multicam_panel_ = new MulticamPanel(this);
|
||||
pixel_sampler_panel_ = new PixelSamplerPanel(this);
|
||||
AppendProjectPanel();
|
||||
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::SelectedNodesChanged, node_panel_, &NodePanel::Select);
|
||||
|
||||
// Connect time signals together
|
||||
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(node_panel_, &NodePanel::NodeSelectionChanged, sequence_viewer_panel_, &ViewerPanel::SetNodeViewSelections);
|
||||
|
||||
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(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_);
|
||||
|
||||
@@ -489,6 +494,7 @@ void MainWindow::TimelinePanelSelectionChanged(const QVector<Block *> &blocks)
|
||||
|
||||
if (PanelManager::instance()->CurrentlyFocused(false) == panel) {
|
||||
UpdateNodePanelContextFromTimelinePanel(panel);
|
||||
sequence_viewer_panel_->SetTimelineSelectedBlocks(blocks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,21 +597,32 @@ void MainWindow::FloatingPanelCloseRequested()
|
||||
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* panel = AppendPanelInternal<TimelinePanel>(timeline_panels_);
|
||||
|
||||
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::BlockSelectionChanged, this, &MainWindow::TimelinePanelSelectionChanged);
|
||||
connect(panel, &TimelinePanel::RevealViewerInProject, this, &MainWindow::RevealViewerInProject);
|
||||
connect(panel, &TimelinePanel::RevealViewerInFootageViewer, this, &MainWindow::RevealViewerInFootageViewer);
|
||||
connect(param_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime);
|
||||
connect(curve_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime);
|
||||
connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, panel, &TimelinePanel::SetTime);
|
||||
|
||||
AddMainTimePanel(panel);
|
||||
|
||||
sequence_viewer_panel_->ConnectTimeBasedPanel(panel);
|
||||
|
||||
@@ -647,6 +664,7 @@ void MainWindow::RemoveProjectPanel(ProjectPanel *panel)
|
||||
void MainWindow::TimelineFocused(ViewerOutput* viewer)
|
||||
{
|
||||
sequence_viewer_panel_->ConnectViewerNode(viewer);
|
||||
multicam_panel_->ConnectViewerNode(viewer);
|
||||
param_panel_->ConnectViewerNode(viewer);
|
||||
curve_panel_->ConnectViewerNode(viewer);
|
||||
}
|
||||
@@ -837,6 +855,10 @@ void MainWindow::SetDefaultLayout()
|
||||
scope_panel_->setFloating(true);
|
||||
addDockWidget(Qt::TopDockWidgetArea, scope_panel_);
|
||||
|
||||
multicam_panel_->hide();
|
||||
multicam_panel_->setFloating(true);
|
||||
addDockWidget(Qt::TopDockWidgetArea, multicam_panel_);
|
||||
|
||||
sequence_viewer_panel_->show();
|
||||
addDockWidget(Qt::TopDockWidgetArea, sequence_viewer_panel_);
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "mainwindowlayoutinfo.h"
|
||||
#include "node/project/project.h"
|
||||
#include "panel/multicam/multicampanel.h"
|
||||
#include "panel/panelmanager.h"
|
||||
#include "panel/audiomonitor/audiomonitor.h"
|
||||
#include "panel/curve/curve.h"
|
||||
@@ -146,6 +147,8 @@ private:
|
||||
|
||||
void SelectFootageForProjectPanel(const QVector<Footage*> &e, ProjectPanel *p);
|
||||
|
||||
void AddMainTimePanel(TimeBasedPanel *p);
|
||||
|
||||
QByteArray premaximized_state_;
|
||||
|
||||
// Standard panels
|
||||
@@ -163,6 +166,7 @@ private:
|
||||
PixelSamplerPanel* pixel_sampler_panel_;
|
||||
ScopePanel* scope_panel_;
|
||||
QMap<ViewerOutput*, ViewerPanel*> viewer_panels_;
|
||||
MulticamPanel *multicam_panel_;
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
unsigned int taskbar_btn_id_;
|
||||
@@ -172,6 +176,8 @@ private:
|
||||
|
||||
bool first_show_;
|
||||
|
||||
QVector<TimeBasedPanel*> main_time_panels_;
|
||||
|
||||
private slots:
|
||||
void FocusedPanelChanged(PanelWidget* panel);
|
||||
|
||||
@@ -202,6 +208,8 @@ private slots:
|
||||
void RevealViewerInProject(ViewerOutput *r);
|
||||
void RevealViewerInFootageViewer(ViewerOutput *r, const TimeRange &range);
|
||||
|
||||
void UpdateMainTimePanels(const rational &r);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user