Merge branch 'olive-editor:master' into av1

This commit is contained in:
jazztickets
2022-10-03 11:57:12 -06:00
committed by GitHub
81 changed files with 2035 additions and 653 deletions
+27 -16
View File
@@ -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;
+4
View File
@@ -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_;
+11
View File
@@ -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
View File
@@ -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
View File
@@ -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
+12 -2
View File
@@ -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();
}
}
}
+3
View File
@@ -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;
+3
View File
@@ -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;
+1
View File
@@ -80,6 +80,7 @@ public:
kRippleDistort,
kTileDistort,
kSwirlDistort,
kMulticamNode,
// Count value
kInternalNodeCount
+12 -11
View File
@@ -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;
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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
View File
@@ -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_;
};
+1
View File
@@ -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)
+22
View File
@@ -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
)
+92
View File
@@ -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++;
}
}
}
}
+47 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+16 -23
View File
@@ -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_;
+6 -6
View File
@@ -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
View File
@@ -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 &params, 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;
+5 -5
View File
@@ -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 &params, const TimeRange &time);
static NodeGlobals GenerateGlobals(const VideoParams &params, 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
View File
@@ -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_;
+1
View File
@@ -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)
+22
View File
@@ -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
)
+22
View File
@@ -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"));
}
}
+24
View File
@@ -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
+2 -5
View File
@@ -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;
+10
View File
@@ -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;
+17 -13
View File
@@ -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();
}
}
}
+28 -3
View File
@@ -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:
+51 -70
View File
@@ -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);
}
}
+18 -37
View File
@@ -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_;
};
}
+7
View File
@@ -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);
+17 -1
View File
@@ -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_;
+70 -24
View File
@@ -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();
+10 -1
View File
@@ -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
+10 -2
View File
@@ -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 &params)
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 &params)
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);
}
+5 -1
View File
@@ -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
View File
@@ -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) {
+2 -2
View File
@@ -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;
+1
View File
@@ -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) {
+4 -1
View File
@@ -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
+1 -1
View File
@@ -293,7 +293,7 @@ void MenuShared::EnableDisableTriggered()
void MenuShared::NestTriggered()
{
qDebug() << "FIXME: Stub";
PanelManager::instance()->MostRecentlyFocused<TimelinePanel>()->NestSelectedClips();
}
void MenuShared::DefaultTransitionTriggered()
+24
View File
@@ -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
)
+172
View File
@@ -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;
}
}
+57
View File
@@ -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
+177
View File
@@ -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);
}
}
+75
View File
@@ -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
+2 -4
View File
@@ -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_;
+10 -10
View File
@@ -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();
+2 -2
View File
@@ -47,10 +47,10 @@ void HistogramScope::OnInit()
void HistogramScope::OnDestroy()
{
super::OnDestroy();
pipeline_secondary_.clear();
texture_row_sums_ = nullptr;
super::OnDestroy();
}
ShaderCode HistogramScope::GenerateShaderCode()
+3 -5
View File
@@ -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();
}
}
+1 -3
View File
@@ -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);
+137 -4
View File
@@ -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();
};
}
+13 -8
View File
@@ -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();
+3 -3
View File
@@ -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 {
+2 -2
View File
@@ -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
View File
@@ -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)
+53 -2
View File
@@ -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();
+50 -21
View File
@@ -191,6 +191,15 @@ void ViewerDisplayWidget::SetVideoParams(const VideoParams &params)
}
}
void ViewerDisplayWidget::SetAudioParams(const AudioParams &params)
{
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();
+37 -19
View File
@@ -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 &params);
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 &gt, 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 &gt, 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_;
+4 -2
View File
@@ -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;
+2
View File
@@ -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)
+1 -1
View File
@@ -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:
+36 -14
View File
@@ -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_);
+8
View File
@@ -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);
};
}