Merge branch 'olive-editor:master' into av1

This commit is contained in:
jazztickets
2022-10-12 09:11:27 -06:00
committed by GitHub
34 changed files with 527 additions and 230 deletions
+11 -2
View File
@@ -376,7 +376,7 @@ jobs:
ctest -C ${{ matrix.build-type }} -V ctest -C ${{ matrix.build-type }} -V
if: matrix.os-arch == 'x86_64' # ARM64 tests naturally won't be able to run on x86_64 runners if: matrix.os-arch == 'x86_64' # ARM64 tests naturally won't be able to run on x86_64 runners
- name: Create Package - name: Bundle Application
working-directory: ${{ runner.workspace }}/build working-directory: ${{ runner.workspace }}/build
shell: bash shell: bash
run: | run: |
@@ -444,9 +444,18 @@ jobs:
# Sign application # Sign application
codesign --deep --sign "Developer ID Application: Olive Studios LLC" $BUNDLE_NAME codesign --deep --sign "Developer ID Application: Olive Studios LLC" $BUNDLE_NAME
- name: Deploy
shell: bash
working-directory: ${{ runner.workspace }}/build/deploy
run: |
ln -s /Applications Applications
cd ..
hdiutil create img.dmg -volname Olive -fs HFS+ -srcfolder deploy
hdiutil convert img.dmg -format UDZO -o $PKGNAME.dmg
- name: Upload Artifact to GitHub - name: Upload Artifact to GitHub
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
continue-on-error: true continue-on-error: true
with: with:
name: ${{ env.PKGNAME }} name: ${{ env.PKGNAME }}
path: ${{ runner.workspace }}/build/deploy path: ${{ runner.workspace }}/build/${{ env.PKGNAME }}.dmg
+4
View File
@@ -90,6 +90,10 @@ CMakeLists.txt.user*
# QtCreator 4.8< compilation database # QtCreator 4.8< compilation database
compile_commands.json compile_commands.json
# Hand-written compilation database listing compilation flags for clangd to use when parsing the code, similarly to the compilation database above
# https://clangd.llvm.org/design/compile-commands#where-do-compile-commands-come-from
compile_flags.txt
# QtCreator local machine specific files for imported projects # QtCreator local machine specific files for imported projects
*creator.user* *creator.user*
+4 -16
View File
@@ -45,7 +45,6 @@ Decoder::Decoder() :
void Decoder::IncrementAccessTime(qint64 t) void Decoder::IncrementAccessTime(qint64 t)
{ {
QMutexLocker locker(&mutex_);
last_accessed_ += t; last_accessed_ += t;
} }
@@ -156,7 +155,6 @@ Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBuffer &dest, const Ti
qint64 Decoder::GetLastAccessedTime() qint64 Decoder::GetLastAccessedTime()
{ {
QMutexLocker locker(&mutex_);
return last_accessed_; return last_accessed_;
} }
@@ -215,19 +213,6 @@ DecoderPtr Decoder::CreateFromID(const QString &id)
return nullptr; return nullptr;
} }
int64_t Decoder::GetTimeInTimebaseUnits(const rational &time, const rational &timebase, int64_t start_time)
{
int64_t t = Timecode::time_to_timestamp(time, timebase);
t += start_time;
return t;
}
rational Decoder::GetTimestampInTimeUnits(int64_t time, const rational &timebase, int64_t start_time)
{
time -= start_time;
return Timecode::timestamp_to_time(time, timebase);
}
void Decoder::SignalProcessingProgress(int64_t ts, int64_t duration) void Decoder::SignalProcessingProgress(int64_t ts, int64_t duration)
{ {
if (duration != AV_NOPTS_VALUE && duration != 0) { if (duration != AV_NOPTS_VALUE && duration != 0) {
@@ -294,10 +279,13 @@ bool Decoder::ConformAudioInternal(const QVector<QString> &filenames, const Audi
return false; return false;
} }
bool Decoder::RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector<QString> &conform_filenames, const TimeRange& range, LoopMode loop_mode, const AudioParams &input_params) bool Decoder::RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector<QString> &conform_filenames, TimeRange range, LoopMode loop_mode, const AudioParams &input_params)
{ {
PlanarFileDevice input; PlanarFileDevice input;
if (input.open(conform_filenames, QFile::ReadOnly)) { if (input.open(conform_filenames, QFile::ReadOnly)) {
// Offset range by audio start offset
range -= GetAudioStartOffset();
qint64 read_index = input_params.time_to_bytes(range.in()) / input_params.channel_count(); qint64 read_index = input_params.time_to_bytes(range.in()) / input_params.channel_count();
qint64 write_index = 0; qint64 write_index = 0;
+3 -4
View File
@@ -294,8 +294,7 @@ protected:
return stream_; return stream_;
} }
static int64_t GetTimeInTimebaseUnits(const rational& time, const rational& timebase, int64_t start_time); virtual rational GetAudioStartOffset() const { return 0; }
static rational GetTimestampInTimeUnits(int64_t time, const rational& timebase, int64_t start_time);
signals: signals:
/** /**
@@ -307,13 +306,13 @@ signals:
private: private:
void UpdateLastAccessed(); void UpdateLastAccessed();
bool RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector<QString> &conform_filenames, const TimeRange &range, LoopMode loop_mode, const AudioParams &params); bool RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector<QString> &conform_filenames, TimeRange range, LoopMode loop_mode, const AudioParams &params);
CodecStream stream_; CodecStream stream_;
QMutex mutex_; QMutex mutex_;
qint64 last_accessed_; std::atomic_int64_t last_accessed_;
TexturePtr cached_texture_; TexturePtr cached_texture_;
rational cached_time_; rational cached_time_;
+16
View File
@@ -214,6 +214,7 @@ void EncodingParams::Save(QXmlStreamWriter *writer) const
writer->writeTextElement(QStringLiteral("width"), QString::number(video_params_.width())); writer->writeTextElement(QStringLiteral("width"), QString::number(video_params_.width()));
writer->writeTextElement(QStringLiteral("height"), QString::number(video_params_.height())); writer->writeTextElement(QStringLiteral("height"), QString::number(video_params_.height()));
writer->writeTextElement(QStringLiteral("format"), QString::number(video_params_.format())); writer->writeTextElement(QStringLiteral("format"), QString::number(video_params_.format()));
writer->writeTextElement(QStringLiteral("pixelaspect"), video_params_.pixel_aspect_ratio().toString());
writer->writeTextElement(QStringLiteral("timebase"), video_params_.time_base().toString()); writer->writeTextElement(QStringLiteral("timebase"), video_params_.time_base().toString());
writer->writeTextElement(QStringLiteral("divider"), QString::number(video_params_.divider())); writer->writeTextElement(QStringLiteral("divider"), QString::number(video_params_.divider()));
writer->writeTextElement(QStringLiteral("bitrate"), QString::number(video_bit_rate_)); writer->writeTextElement(QStringLiteral("bitrate"), QString::number(video_bit_rate_));
@@ -258,6 +259,7 @@ void EncodingParams::Save(QXmlStreamWriter *writer) const
writer->writeTextElement(QStringLiteral("samplerate"), QString::number(audio_params_.sample_rate())); writer->writeTextElement(QStringLiteral("samplerate"), QString::number(audio_params_.sample_rate()));
writer->writeTextElement(QStringLiteral("channellayout"), QString::number(audio_params_.channel_layout())); writer->writeTextElement(QStringLiteral("channellayout"), QString::number(audio_params_.channel_layout()));
writer->writeTextElement(QStringLiteral("format"), QString::number(audio_params_.format())); writer->writeTextElement(QStringLiteral("format"), QString::number(audio_params_.format()));
writer->writeTextElement(QStringLiteral("bitrate"), QString::number(audio_bit_rate_));
} }
writer->writeStartElement(QStringLiteral("subtitles")); writer->writeStartElement(QStringLiteral("subtitles"));
@@ -398,6 +400,8 @@ bool EncodingParams::LoadV1(QXmlStreamReader *reader)
video_params_.set_height(reader->readElementText().toInt()); video_params_.set_height(reader->readElementText().toInt());
} else if (reader->name() == QStringLiteral("format")) { } else if (reader->name() == QStringLiteral("format")) {
video_params_.set_format(static_cast<VideoParams::Format>(reader->readElementText().toInt())); video_params_.set_format(static_cast<VideoParams::Format>(reader->readElementText().toInt()));
} else if (reader->name() == QStringLiteral("pixelaspect")) {
video_params_.set_pixel_aspect_ratio(rational::fromString(reader->readElementText()));
} else if (reader->name() == QStringLiteral("timebase")) { } else if (reader->name() == QStringLiteral("timebase")) {
video_params_.set_time_base(rational::fromString(reader->readElementText())); video_params_.set_time_base(rational::fromString(reader->readElementText()));
} else if (reader->name() == QStringLiteral("divider")) { } else if (reader->name() == QStringLiteral("divider")) {
@@ -448,6 +452,11 @@ bool EncodingParams::LoadV1(QXmlStreamReader *reader)
reader->skipCurrentElement(); reader->skipCurrentElement();
} }
} }
// HACK: Resolve bug where I forgot to serialize pixel aspect ratio
if (video_params_.pixel_aspect_ratio().isNull()) {
video_params_.set_pixel_aspect_ratio(1);
}
} else if (reader->name() == QStringLiteral("audio")) { } else if (reader->name() == QStringLiteral("audio")) {
XMLAttributeLoop(reader, attr) { XMLAttributeLoop(reader, attr) {
if (attr.name() == QStringLiteral("enabled")) { if (attr.name() == QStringLiteral("enabled")) {
@@ -464,10 +473,17 @@ bool EncodingParams::LoadV1(QXmlStreamReader *reader)
audio_params_.set_channel_layout(reader->readElementText().toULongLong()); audio_params_.set_channel_layout(reader->readElementText().toULongLong());
} else if (reader->name() == QStringLiteral("format")) { } else if (reader->name() == QStringLiteral("format")) {
audio_params_.set_format(static_cast<AudioParams::Format>(reader->readElementText().toInt())); audio_params_.set_format(static_cast<AudioParams::Format>(reader->readElementText().toInt()));
} else if (reader->name() == QStringLiteral("bitrate")) {
audio_bit_rate_ = reader->readElementText().toLongLong();
} else { } else {
reader->skipCurrentElement(); reader->skipCurrentElement();
} }
} }
// HACK: Resolve bug where I forgot to serialize the audio bit rate
if (!audio_bit_rate_) {
audio_bit_rate_ = 320000;
}
} else if (reader->name() == QStringLiteral("subtitles")) { } else if (reader->name() == QStringLiteral("subtitles")) {
XMLAttributeLoop(reader, attr) { XMLAttributeLoop(reader, attr) {
if (attr.name() == QStringLiteral("enabled")) { if (attr.name() == QStringLiteral("enabled")) {
+37 -18
View File
@@ -40,12 +40,9 @@ extern "C" {
#include <QtConcurrent/QtConcurrent> #include <QtConcurrent/QtConcurrent>
#include "codec/planarfiledevice.h" #include "codec/planarfiledevice.h"
#include "common/define.h"
#include "common/ffmpegutils.h" #include "common/ffmpegutils.h"
#include "common/filefunctions.h" #include "common/filefunctions.h"
#include "common/timecodefunctions.h" #include "common/timecodefunctions.h"
#include "render/framehashcache.h"
#include "render/diskmanager.h"
#include "render/renderer.h" #include "render/renderer.h"
#include "render/subtitleparams.h" #include "render/subtitleparams.h"
@@ -143,7 +140,7 @@ bool FFmpegDecoder::OpenInternal()
TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p)
{ {
if (AVFramePtr f = RetrieveFrame(p.time, p.cancelled)) { if (AVFramePtr f = RetrieveFrame(p.time, p.src_interlacing, p.cancelled)) {
if (p.cancelled && p.cancelled->IsCancelled()) { if (p.cancelled && p.cancelled->IsCancelled()) {
return nullptr; return nullptr;
} }
@@ -235,9 +232,9 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p)
job.Insert(QStringLiteral("u_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(u_plane))); job.Insert(QStringLiteral("u_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(u_plane)));
job.Insert(QStringLiteral("v_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(v_plane))); job.Insert(QStringLiteral("v_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(v_plane)));
job.Insert(QStringLiteral("bits_per_pixel"), NodeValue(NodeValue::kInt, bits_per_pixel)); job.Insert(QStringLiteral("bits_per_pixel"), NodeValue(NodeValue::kInt, bits_per_pixel));
job.Insert(QStringLiteral("full_range"), NodeValue(NodeValue::kBoolean, f->color_range == AVCOL_RANGE_JPEG)); job.Insert(QStringLiteral("full_range"), NodeValue(NodeValue::kBoolean, hw_in->color_range == AVCOL_RANGE_JPEG));
const int *yuv_coeffs = sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(f.get()->colorspace)); const int *yuv_coeffs = sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(hw_in->colorspace));
job.Insert(QStringLiteral("yuv_crv"), NodeValue(NodeValue::kInt, yuv_coeffs[0])); job.Insert(QStringLiteral("yuv_crv"), NodeValue(NodeValue::kInt, yuv_coeffs[0]));
job.Insert(QStringLiteral("yuv_cgu"), NodeValue(NodeValue::kInt, yuv_coeffs[2])); job.Insert(QStringLiteral("yuv_cgu"), NodeValue(NodeValue::kInt, yuv_coeffs[2]));
job.Insert(QStringLiteral("yuv_cgv"), NodeValue(NodeValue::kInt, yuv_coeffs[3])); job.Insert(QStringLiteral("yuv_cgv"), NodeValue(NodeValue::kInt, yuv_coeffs[3]));
@@ -246,7 +243,7 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p)
int interlacing = 0; int interlacing = 0;
if (p.src_interlacing != VideoParams::kInterlaceNone) { if (p.src_interlacing != VideoParams::kInterlaceNone) {
if (frame_rate_tb_.isNull()) { if (frame_rate_tb_.isNull()) {
frame_rate_tb_ = av_guess_frame_rate(instance_.fmt_ctx(), instance_.avstream(), f.get()); frame_rate_tb_ = av_guess_frame_rate(instance_.fmt_ctx(), instance_.avstream(), hw_in);
// Double frame rate for interlaced fields // Double frame rate for interlaced fields
frame_rate_tb_ *= 2; frame_rate_tb_ *= 2;
@@ -256,7 +253,7 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p)
} }
int64_t req = Timecode::time_to_timestamp(p.time, frame_rate_tb_); int64_t req = Timecode::time_to_timestamp(p.time, frame_rate_tb_);
int64_t frm = Timecode::rescale_timestamp(f->pts - instance_.avstream()->start_time, instance_.avstream()->time_base, frame_rate_tb_); int64_t frm = Timecode::rescale_timestamp(hw_in->pts - instance_.avstream()->start_time, instance_.avstream()->time_base, frame_rate_tb_);
bool first = (req == frm); bool first = (req == frm);
bool top_first = (p.src_interlacing == VideoParams::kInterlacedTopFirst); bool top_first = (p.src_interlacing == VideoParams::kInterlacedTopFirst);
@@ -268,6 +265,8 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p)
tex = p.renderer->CreateTexture(vp); tex = p.renderer->CreateTexture(vp);
p.renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false); p.renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false);
av_frame_unref(working_frame_);
} }
} }
@@ -311,6 +310,18 @@ void FFmpegDecoder::CloseInternal()
native_output_pix_fmt_ = VideoParams::kFormatInvalid; native_output_pix_fmt_ = VideoParams::kFormatInvalid;
} }
rational FFmpegDecoder::GetAudioStartOffset() const
{
auto f = instance_.fmt_ctx();
if (f) {
rational fmt_start = rational(instance_.fmt_ctx()->start_time, AV_TIME_BASE);
rational str_start = rational(instance_.avstream()->time_base) * instance_.avstream()->start_time;
return str_start - fmt_start;
} else {
return 0;
}
}
QString FFmpegDecoder::id() const QString FFmpegDecoder::id() const
{ {
return QStringLiteral("ffmpeg"); return QStringLiteral("ffmpeg");
@@ -787,11 +798,19 @@ void FFmpegDecoder::ClearFrameCache()
} }
} }
AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, CancelAtom *cancelled) AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, VideoParams::Interlacing interlacing, CancelAtom *cancelled)
{ {
int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time); int64_t target_ts = Timecode::time_to_timestamp(time, instance_.avstream()->time_base);
const int64_t min_seek = -instance_.avstream()->start_time; if (interlacing != VideoParams::kInterlaceNone && !IsPixelFormatGLSLCompatible(static_cast<AVPixelFormat>(instance_.avstream()->codecpar->format))) {
target_ts *= 2;
}
if (instance_.fmt_ctx()->start_time != AV_NOPTS_VALUE) {
target_ts += av_rescale_q(instance_.fmt_ctx()->start_time, {1, AV_TIME_BASE}, instance_.avstream()->time_base);
}
const int64_t min_seek = 0;
int64_t seek_ts = std::max(min_seek, target_ts - MaximumQueueSize()); int64_t seek_ts = std::max(min_seek, target_ts - MaximumQueueSize());
bool still_seeking = false; bool still_seeking = false;
@@ -991,8 +1010,10 @@ bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params
// Link filters as necessary // Link filters as necessary
AVFilterContext *last_filter = buffersrc_ctx_; AVFilterContext *last_filter = buffersrc_ctx_;
bool glsl_available = IsPixelFormatGLSLCompatible(static_cast<AVPixelFormat>(input->format));
// Add deinterlace filter if necessary // Add deinterlace filter if necessary
if (filter_params_.src_interlacing != VideoParams::kInterlaceNone) { if (filter_params_.src_interlacing != VideoParams::kInterlaceNone && !glsl_available) {
AVFilterContext* deint_filter; AVFilterContext* deint_filter;
snprintf(filter_args, kFilterArgSz, "mode=1:parity=%s", snprintf(filter_args, kFilterArgSz, "mode=1:parity=%s",
@@ -1006,14 +1027,14 @@ bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params
} }
// Add scale filter if necessary // Add scale filter if necessary
int dst_width, dst_height;
if (filter_params_.divider > 1) { if (filter_params_.divider > 1) {
AVFilterContext* scale_filter; AVFilterContext* scale_filter;
int dst_width, dst_height;
dst_width = VideoParams::GetScaledDimension(src_width, filter_params_.divider); dst_width = VideoParams::GetScaledDimension(src_width, filter_params_.divider);
dst_height = VideoParams::GetScaledDimension(src_height, filter_params_.divider); dst_height = VideoParams::GetScaledDimension(src_height, filter_params_.divider);
snprintf(filter_args, kFilterArgSz, "w=%d:h=%d:flags=fast_bilinear:interl=-1", snprintf(filter_args, kFilterArgSz, "w=%d:h=%d:flags=fast_bilinear:interl=0",
dst_width, dst_width,
dst_height); dst_height);
@@ -1021,13 +1042,10 @@ bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params
avfilter_link(last_filter, 0, scale_filter, 0); avfilter_link(last_filter, 0, scale_filter, 0);
last_filter = scale_filter; last_filter = scale_filter;
} else {
dst_width = src_width;
dst_height = src_height;
} }
// Add format filter if necessary // Add format filter if necessary
if (ideal_pix_fmt != input->format && !IsPixelFormatGLSLCompatible(static_cast<AVPixelFormat>(input->format))) { if (ideal_pix_fmt != input->format && !glsl_available) {
AVFilterContext* format_filter; AVFilterContext* format_filter;
snprintf(filter_args, kFilterArgSz, "pix_fmts=%u", ideal_pix_fmt); snprintf(filter_args, kFilterArgSz, "pix_fmts=%u", ideal_pix_fmt);
@@ -1129,6 +1147,7 @@ int FFmpegDecoder::MaximumQueueSize()
FFmpegDecoder::Instance::Instance() : FFmpegDecoder::Instance::Instance() :
fmt_ctx_(nullptr), fmt_ctx_(nullptr),
codec_ctx_(nullptr), codec_ctx_(nullptr),
avstream_(nullptr),
opts_(nullptr) opts_(nullptr)
{ {
} }
+3 -1
View File
@@ -67,6 +67,8 @@ protected:
virtual bool ConformAudioInternal(const QVector<QString>& filenames, const AudioParams &params, CancelAtom *cancelled) override; virtual bool ConformAudioInternal(const QVector<QString>& filenames, const AudioParams &params, CancelAtom *cancelled) override;
virtual void CloseInternal() override; virtual void CloseInternal() override;
virtual rational GetAudioStartOffset() const override;
private: private:
class Instance class Instance
{ {
@@ -148,7 +150,7 @@ private:
void ClearFrameCache(); void ClearFrameCache();
AVFramePtr RetrieveFrame(const rational &time, CancelAtom *cancelled); AVFramePtr RetrieveFrame(const rational &time, VideoParams::Interlacing interlacing, CancelAtom *cancelled);
void RemoveFirstFrame(); void RemoveFirstFrame();
+5
View File
@@ -1365,6 +1365,11 @@ void Core::PushRecentlyOpenedProject(const QString& s)
recent_projects_.move(existing_index, 0); recent_projects_.move(existing_index, 0);
} else { } else {
recent_projects_.prepend(s); recent_projects_.prepend(s);
const int kMaximumRecentProjects = 10;
while (recent_projects_.size() > kMaximumRecentProjects) {
recent_projects_.removeLast();
}
} }
emit OpenRecentListChanged(); emit OpenRecentListChanged();
+13 -2
View File
@@ -47,7 +47,8 @@ namespace olive {
ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWidget *parent) : ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWidget *parent) :
super(parent), super(parent),
viewer_node_(viewer_node), viewer_node_(viewer_node),
stills_only_mode_(stills_only_mode) stills_only_mode_(stills_only_mode),
loading_presets_(false)
{ {
QHBoxLayout* layout = new QHBoxLayout(this); QHBoxLayout* layout = new QHBoxLayout(this);
@@ -258,7 +259,8 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWi
// If the viewer already has cached params, use them // If the viewer already has cached params, use them
if (!stills_only_mode_ && viewer_node_->GetLastUsedEncodingParams().IsValid()) { if (!stills_only_mode_ && viewer_node_->GetLastUsedEncodingParams().IsValid()) {
SetParams(viewer_node_->GetLastUsedEncodingParams()); // This will automatically set the param data
QtUtils::SetComboBoxData(preset_combobox_, kPresetLastUsed);
} else { } else {
SetDefaults(); SetDefaults();
} }
@@ -432,6 +434,10 @@ void ExportDialog::SavePreset()
void ExportDialog::PresetComboBoxChanged() void ExportDialog::PresetComboBoxChanged()
{ {
if (loading_presets_) {
return;
}
QComboBox *c = static_cast<QComboBox *>(sender()); QComboBox *c = static_cast<QComboBox *>(sender());
int preset_number = c->currentData().toInt(); int preset_number = c->currentData().toInt();
@@ -536,6 +542,8 @@ void ExportDialog::ResolutionChanged()
void ExportDialog::LoadPresets() void ExportDialog::LoadPresets()
{ {
loading_presets_ = true;
preset_combobox_->clear(); preset_combobox_->clear();
presets_.clear(); presets_.clear();
@@ -562,6 +570,8 @@ void ExportDialog::LoadPresets()
f.close(); f.close();
} }
} }
loading_presets_ = false;
} }
void ExportDialog::SetDefaultFilename() void ExportDialog::SetDefaultFilename()
@@ -697,6 +707,7 @@ EncodingParams ExportDialog::GenerateParams() const
void ExportDialog::SetParams(const EncodingParams &e) void ExportDialog::SetParams(const EncodingParams &e)
{ {
format_combobox_->SetFormat(e.format()); format_combobox_->SetFormat(e.format());
FormatChanged(format_combobox_->GetFormat());
if (e.has_custom_range() && viewer_node_->GetWorkArea()->enabled()) { if (e.has_custom_range() && viewer_node_->GetWorkArea()->enabled()) {
range_combobox_->setCurrentIndex(kRangeInToOut); range_combobox_->setCurrentIndex(kRangeInToOut);
+2
View File
@@ -125,6 +125,8 @@ private:
bool stills_only_mode_; bool stills_only_mode_;
bool loading_presets_;
private slots: private slots:
void BrowseFilename(); void BrowseFilename();
+2 -2
View File
@@ -364,7 +364,7 @@ void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int
} }
// Find connected viewer node // Find connected viewer node
auto viewers = FindInputNodesConnectedToInput<ViewerOutput>(NodeInput(this, kBufferIn)); auto viewers = FindInputNodesConnectedToInput<ViewerOutput>(NodeInput(this, kBufferIn), 1);
ViewerOutput *new_connected_viewer = viewers.isEmpty() ? nullptr : viewers.first(); ViewerOutput *new_connected_viewer = viewers.isEmpty() ? nullptr : viewers.first();
if (new_connected_viewer != connected_viewer_) { if (new_connected_viewer != connected_viewer_) {
@@ -543,7 +543,7 @@ TimeRange ClipBlock::media_range() const
MultiCamNode *ClipBlock::FindMulticam() MultiCamNode *ClipBlock::FindMulticam()
{ {
auto v = FindInputNodesConnectedToInput<MultiCamNode>(NodeInput(this, kBufferIn)); auto v = FindInputNodesConnectedToInput<MultiCamNode>(NodeInput(this, kBufferIn), 1);
if (v.empty()) { if (v.empty()) {
return nullptr; return nullptr;
} else { } else {
+68 -1
View File
@@ -1,11 +1,15 @@
#include "multicamnode.h" #include "multicamnode.h"
#include "node/project/sequence/sequence.h"
namespace olive { namespace olive {
#define super Node #define super Node
const QString MultiCamNode::kCurrentInput = QStringLiteral("current_in"); const QString MultiCamNode::kCurrentInput = QStringLiteral("current_in");
const QString MultiCamNode::kSourcesInput = QStringLiteral("sources_in"); const QString MultiCamNode::kSourcesInput = QStringLiteral("sources_in");
const QString MultiCamNode::kSequenceInput = QStringLiteral("sequence_in");
const QString MultiCamNode::kSequenceTypeInput = QStringLiteral("sequence_type_in");
MultiCamNode::MultiCamNode() MultiCamNode::MultiCamNode()
{ {
@@ -17,6 +21,11 @@ MultiCamNode::MultiCamNode()
AddInput(kSourcesInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray)); AddInput(kSourcesInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray));
SetInputProperty(kSourcesInput, QStringLiteral("arraystart"), 1); SetInputProperty(kSourcesInput, QStringLiteral("arraystart"), 1);
AddInput(kSequenceInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable));
AddInput(kSequenceTypeInput, NodeValue::kCombo, InputFlags(kInputFlagStatic | kInputFlagHidden));
sequence_ = nullptr;
} }
QString MultiCamNode::Name() const QString MultiCamNode::Name() const
@@ -43,7 +52,7 @@ Node::ActiveElements MultiCamNode::GetActiveElementsAtTime(const QString &input,
{ {
if (input == kSourcesInput) { if (input == kSourcesInput) {
int src = GetCurrentSource(); int src = GetCurrentSource();
if (src >= 0 && src < InputArraySize(kSourcesInput)) { if (src >= 0 && src < GetSourceCount()) {
Node::ActiveElements a; Node::ActiveElements a;
a.add(src); a.add(src);
return a; return a;
@@ -71,12 +80,70 @@ void MultiCamNode::IndexToRowCols(int index, int total_rows, int total_cols, int
*row = index/total_cols; *row = index/total_cols;
} }
Node *MultiCamNode::GetConnectedRenderOutput(const QString &input, int element) const
{
if (sequence_ && input == kSourcesInput && element >= 0 && element < GetSourceCount()) {
return GetTrackList()->GetTrackAt(element);
} else {
return Node::GetConnectedRenderOutput(input, element);
}
}
bool MultiCamNode::IsInputConnectedForRender(const QString &input, int element) const
{
if (sequence_ && input == kSourcesInput && element >= 0 && element < GetSourceCount()) {
return true;
} else {
return Node::IsInputConnectedForRender(input, element);
}
}
QVector<QString> MultiCamNode::IgnoreInputsForRendering() const
{
return {kSequenceInput};
}
void MultiCamNode::InputConnectedEvent(const QString &input, int element, Node *output)
{
if (input == kSequenceInput) {
if (Sequence *s = dynamic_cast<Sequence*>(output)) {
SetInputFlags(kSequenceTypeInput, GetInputFlags(kSequenceTypeInput) & InputFlag(~kInputFlagHidden));
sequence_ = s;
}
}
}
void MultiCamNode::InputDisconnectedEvent(const QString &input, int element, Node *output)
{
if (input == kSequenceInput) {
SetInputFlags(kSequenceTypeInput, GetInputFlags(kSequenceTypeInput) | kInputFlagHidden);
sequence_ = nullptr;
}
}
TrackList *MultiCamNode::GetTrackList() const
{
return sequence_->track_list(static_cast<Track::Type>(GetStandardValue(kSequenceTypeInput).toInt()));
}
void MultiCamNode::Retranslate() void MultiCamNode::Retranslate()
{ {
super::Retranslate(); super::Retranslate();
SetInputName(kCurrentInput, tr("Current")); SetInputName(kCurrentInput, tr("Current"));
SetInputName(kSourcesInput, tr("Sources")); SetInputName(kSourcesInput, tr("Sources"));
SetInputName(kSequenceInput, tr("Sequence"));
SetInputName(kSequenceTypeInput, tr("Sequence Type"));
SetComboBoxStrings(kSequenceTypeInput, {tr("Video"), tr("Audio")});
}
int MultiCamNode::GetSourceCount() const
{
if (sequence_) {
return GetTrackList()->GetTrackCount();
} else {
return InputArraySize(kSourcesInput);
}
} }
void MultiCamNode::GetRowsAndColumns(int sources, int *rows_in, int *cols_in) void MultiCamNode::GetRowsAndColumns(int sources, int *rows_in, int *cols_in)
+25 -4
View File
@@ -2,9 +2,12 @@
#define MULTICAMNODE_H #define MULTICAMNODE_H
#include "node/node.h" #include "node/node.h"
#include "node/output/track/tracklist.h"
namespace olive { namespace olive {
class Sequence;
class MultiCamNode : public Node class MultiCamNode : public Node
{ {
Q_OBJECT Q_OBJECT
@@ -26,16 +29,15 @@ public:
static const QString kCurrentInput; static const QString kCurrentInput;
static const QString kSourcesInput; static const QString kSourcesInput;
static const QString kSequenceInput;
static const QString kSequenceTypeInput;
int GetCurrentSource() const int GetCurrentSource() const
{ {
return GetStandardValue(kCurrentInput).toInt(); return GetStandardValue(kCurrentInput).toInt();
} }
int GetSourceCount() const int GetSourceCount() const;
{
return InputArraySize(kSourcesInput);
}
static void GetRowsAndColumns(int sources, int *rows, int *cols); static void GetRowsAndColumns(int sources, int *rows, int *cols);
void GetRowsAndColumns(int *rows, int *cols) const void GetRowsAndColumns(int *rows, int *cols) const
@@ -43,6 +45,11 @@ public:
return GetRowsAndColumns(GetSourceCount(), rows, cols); return GetRowsAndColumns(GetSourceCount(), rows, cols);
} }
void SetSequenceType(Track::Type t)
{
SetStandardValue(kSequenceTypeInput, t);
}
static void IndexToRowCols(int index, int total_rows, int total_cols, int *row, int *col); 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) static int RowsColsToIndex(int row, int col, int total_rows, int total_cols)
@@ -50,6 +57,20 @@ public:
return col + row * total_cols; return col + row * total_cols;
} }
virtual Node *GetConnectedRenderOutput(const QString& input, int element = -1) const override;
virtual bool IsInputConnectedForRender(const QString& input, int element = -1) const override;
virtual QVector<QString> IgnoreInputsForRendering() const override;
protected:
virtual void InputConnectedEvent(const QString &input, int element, Node *output) override;
virtual void InputDisconnectedEvent(const QString &input, int element, Node *output) override;
private:
TrackList *GetTrackList() const;
Sequence *sequence_;
}; };
} }
+20
View File
@@ -1287,6 +1287,26 @@ int Node::GetInternalInputArraySize(const QString &input)
return array_immediates_.value(input).size(); return array_immediates_.value(input).size();
} }
void FindWaysNodeArrivesHereRecursively(const Node *output, const Node *input, QVector<NodeInput> &v)
{
for (auto it=input->input_connections().cbegin(); it!=input->input_connections().cend(); it++) {
if (it->second == output) {
v.append(it->first);
} else {
FindWaysNodeArrivesHereRecursively(output, it->second, v);
}
}
}
QVector<NodeInput> Node::FindWaysNodeArrivesHere(const Node *output) const
{
QVector<NodeInput> v;
FindWaysNodeArrivesHereRecursively(output, this, v);
return v;
}
void Node::SetInputName(const QString &id, const QString &name) void Node::SetInputName(const QString &id, const QString &name)
{ {
Input* i = GetInternalInputData(id); Input* i = GetInternalInputData(id);
+133 -97
View File
@@ -212,6 +212,11 @@ public:
return input_ids_; return input_ids_;
} }
virtual QVector<QString> IgnoreInputsForRendering() const
{
return QVector<QString>();
}
class ActiveElements class ActiveElements
{ {
public: public:
@@ -418,6 +423,15 @@ public:
return IsInputConnected(input.input(), input.element()); return IsInputConnected(input.input(), input.element());
} }
virtual bool IsInputConnectedForRender(const QString& input, int element = -1) const
{
return IsInputConnected(input, element);
}
bool IsInputConnectedForRender(const NodeInput& input) const
{
return IsInputConnectedForRender(input.input(), input.element());
}
bool IsInputStatic(const QString& input, int element = -1) const bool IsInputStatic(const QString& input, int element = -1) const
{ {
return !IsInputConnected(input, element) && !IsInputKeyframing(input, element); return !IsInputConnected(input, element) && !IsInputKeyframing(input, element);
@@ -435,6 +449,16 @@ public:
return GetConnectedOutput(input.input(), input.element()); return GetConnectedOutput(input.input(), input.element());
} }
virtual Node *GetConnectedRenderOutput(const QString& input, int element = -1) const
{
return GetConnectedOutput(input, element);
}
Node *GetConnectedRenderOutput(const NodeInput& input) const
{
return GetConnectedRenderOutput(input.input(), input.element());
}
bool IsUsingStandardValue(const QString& input, int track, int element = -1) const; bool IsUsingStandardValue(const QString& input, int track, int element = -1) const;
NodeValue::Type GetInputDataType(const QString& id) const; NodeValue::Type GetInputDataType(const QString& id) const;
@@ -796,6 +820,15 @@ public:
*/ */
bool InputsFrom(const QString& id, bool recursively) const; bool InputsFrom(const QString& id, bool recursively) const;
/**
* @brief Find inputs that `output` outputs to in order to arrive at this node
*
* Traverse this node's inputs recursively looking for `output`, and return a list of
* edges that `output` uses to get to `this` node.
*/
QVector<NodeInput> FindWaysNodeArrivesHere(const Node *output) const;
/** /**
* @brief Determines how many paths go from this node out to another node * @brief Determines how many paths go from this node out to another node
*/ */
@@ -820,13 +853,13 @@ public:
* @brief Find nodes of a certain type that this Node takes inputs from * @brief Find nodes of a certain type that this Node takes inputs from
*/ */
template<class T> template<class T>
QVector<T*> FindInputNodes() const; QVector<T*> FindInputNodes(int maximum = 0) const;
/** /**
* @brief Find nodes of a certain type that this Node takes inputs from * @brief Find nodes of a certain type that this Node takes inputs from
*/ */
template<class T> template<class T>
static QVector<T*> FindInputNodesConnectedToInput(const NodeInput &input); static QVector<T*> FindInputNodesConnectedToInput(const NodeInput &input, int maximum = 0);
template<class T> template<class T>
/** /**
@@ -967,6 +1000,90 @@ public:
folder_ = folder; folder_ = folder;
} }
class ArrayInsertCommand : public UndoCommand
{
public:
ArrayInsertCommand(Node* node, const QString& input, int index) :
node_(node),
input_(input),
index_(index)
{
}
virtual Project* GetRelevantProject() const override;
protected:
virtual void redo() override
{
node_->InputArrayInsert(input_, index_, false);
}
virtual void undo() override
{
node_->InputArrayRemove(input_, index_, false);
}
private:
Node* node_;
QString input_;
int index_;
};
class ArrayResizeCommand : public UndoCommand
{
public:
ArrayResizeCommand(Node* node, const QString& input, int size) :
node_(node),
input_(input),
size_(size)
{}
virtual Project* GetRelevantProject() const override;
protected:
virtual void redo() override
{
old_size_ = node_->InputArraySize(input_);
if (old_size_ > size_) {
// Decreasing in size, disconnect any extraneous edges
for (int i=size_; i<old_size_; i++) {
try {
NodeInput input(node_, input_, i);
Node *output = node_->input_connections().at(input);
removed_connections_[input] = output;
DisconnectEdge(output, input);
} catch (std::out_of_range&) {}
}
}
node_->ArrayResizeInternal(input_, size_);
}
virtual void undo() override
{
for (auto it=removed_connections_.cbegin(); it!=removed_connections_.cend(); it++) {
ConnectEdge(it->second, it->first);
}
removed_connections_.clear();
node_->ArrayResizeInternal(input_, old_size_);
}
private:
Node* node_;
QString input_;
int size_;
int old_size_;
InputConnections removed_connections_;
};
class ArrayRemoveCommand : public UndoCommand class ArrayRemoveCommand : public UndoCommand
{ {
public: public:
@@ -1203,90 +1320,6 @@ signals:
void InputFlagsChanged(const QString &input, const InputFlags &flags); void InputFlagsChanged(const QString &input, const InputFlags &flags);
private: private:
class ArrayInsertCommand : public UndoCommand
{
public:
ArrayInsertCommand(Node* node, const QString& input, int index) :
node_(node),
input_(input),
index_(index)
{
}
virtual Project* GetRelevantProject() const override;
protected:
virtual void redo() override
{
node_->InputArrayInsert(input_, index_, false);
}
virtual void undo() override
{
node_->InputArrayRemove(input_, index_, false);
}
private:
Node* node_;
QString input_;
int index_;
};
class ArrayResizeCommand : public UndoCommand
{
public:
ArrayResizeCommand(Node* node, const QString& input, int size) :
node_(node),
input_(input),
size_(size)
{}
virtual Project* GetRelevantProject() const override;
protected:
virtual void redo() override
{
old_size_ = node_->InputArraySize(input_);
if (old_size_ > size_) {
// Decreasing in size, disconnect any extraneous edges
for (int i=size_; i<old_size_; i++) {
try {
NodeInput input(node_, input_, i);
Node *output = node_->input_connections().at(input);
removed_connections_[input] = output;
DisconnectEdge(output, input);
} catch (std::out_of_range&) {}
}
}
node_->ArrayResizeInternal(input_, size_);
}
virtual void undo() override
{
for (auto it=removed_connections_.cbegin(); it!=removed_connections_.cend(); it++) {
ConnectEdge(it->second, it->first);
}
removed_connections_.clear();
node_->ArrayResizeInternal(input_, old_size_);
}
private:
Node* node_;
QString input_;
int size_;
int old_size_;
InputConnections removed_connections_;
};
struct Input { struct Input {
NodeValue::Type type; NodeValue::Type type;
InputFlags flags; InputFlags flags;
@@ -1367,10 +1400,10 @@ private:
* @brief Find nodes of a certain type that this Node takes inputs from * @brief Find nodes of a certain type that this Node takes inputs from
*/ */
template<class T> template<class T>
static void FindInputNodesConnectedToInputInternal(const NodeInput &input, QVector<T *>& list); static void FindInputNodesConnectedToInputInternal(const NodeInput &input, QVector<T *>& list, int maximum);
template<class T> template<class T>
static void FindInputNodeInternal(const Node* n, QVector<T *>& list); static void FindInputNodeInternal(const Node* n, QVector<T *>& list, int maximum);
template<class T> template<class T>
static void FindOutputNodeInternal(const Node* n, QVector<T *>& list); static void FindOutputNodeInternal(const Node* n, QVector<T *>& list);
@@ -1479,7 +1512,7 @@ private slots:
}; };
template<class T> template<class T>
void Node::FindInputNodesConnectedToInputInternal(const NodeInput &input, QVector<T *> &list) void Node::FindInputNodesConnectedToInputInternal(const NodeInput &input, QVector<T *> &list, int maximum)
{ {
Node* edge = input.GetConnectedOutput(); Node* edge = input.GetConnectedOutput();
if (!edge) { if (!edge) {
@@ -1490,35 +1523,38 @@ void Node::FindInputNodesConnectedToInputInternal(const NodeInput &input, QVecto
if (cast_test) { if (cast_test) {
list.append(cast_test); list.append(cast_test);
if (maximum != 0 && list.size() == maximum) {
return;
}
} }
FindInputNodeInternal<T>(edge, list); FindInputNodeInternal<T>(edge, list, maximum);
} }
template<class T> template<class T>
QVector<T *> Node::FindInputNodesConnectedToInput(const NodeInput &input) QVector<T *> Node::FindInputNodesConnectedToInput(const NodeInput &input, int maximum)
{ {
QVector<T *> list; QVector<T *> list;
FindInputNodesConnectedToInputInternal<T>(input, list); FindInputNodesConnectedToInputInternal<T>(input, list, maximum);
return list; return list;
} }
template<class T> template<class T>
void Node::FindInputNodeInternal(const Node* n, QVector<T *> &list) void Node::FindInputNodeInternal(const Node* n, QVector<T *> &list, int maximum)
{ {
for (auto it=n->input_connections_.cbegin(); it!=n->input_connections_.cend(); it++) { for (auto it=n->input_connections_.cbegin(); it!=n->input_connections_.cend(); it++) {
FindInputNodesConnectedToInputInternal(it->first, list); FindInputNodesConnectedToInputInternal(it->first, list, maximum);
} }
} }
template<class T> template<class T>
QVector<T *> Node::FindInputNodes() const QVector<T *> Node::FindInputNodes(int maximum) const
{ {
QVector<T *> list; QVector<T *> list;
FindInputNodeInternal<T>(this, list); FindInputNodeInternal<T>(this, list, maximum);
return list; return list;
} }
@@ -1540,7 +1576,7 @@ void Node::FindOutputNodeInternal(const Node* n, QVector<T *>& list)
list.append(cast_test); list.append(cast_test);
} }
FindOutputNodeInternal<T>(connected); FindOutputNodeInternal<T>(connected, list);
} }
} }
+13 -8
View File
@@ -39,7 +39,8 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream
video_length_(0), video_length_(0),
audio_length_(0), audio_length_(0),
autocache_input_video_(false), autocache_input_video_(false),
autocache_input_audio_(false) autocache_input_audio_(false),
waveform_requests_enabled_(false)
{ {
AddInput(kVideoParamsInput, NodeValue::kVideoParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); AddInput(kVideoParamsInput, NodeValue::kVideoParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden));
@@ -231,7 +232,9 @@ void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from,
} }
} else if (from == kSamplesInput) { } else if (from == kSamplesInput) {
TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetAudioLength())); TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetAudioLength()));
connected->waveform_cache()->Request(range.Intersected(max_range)); if (waveform_requests_enabled_) {
connected->waveform_cache()->Request(range.Intersected(max_range));
}
if (autocache_input_audio_) { if (autocache_input_audio_) {
connected->audio_playback_cache()->Request(range.Intersected(max_range)); connected->audio_playback_cache()->Request(range.Intersected(max_range));
} }
@@ -387,13 +390,15 @@ Node::ValueHint ViewerOutput::GetConnectedSampleValueHint()
return GetValueHintForInput(kSamplesInput); return GetValueHintForInput(kSamplesInput);
} }
void ViewerOutput::ConnectedToPreviewEvent() void ViewerOutput::SetWaveformEnabled(bool e)
{ {
if (Node *connected = this->GetConnectedSampleOutput()) { if ((waveform_requests_enabled_ = e)) {
TimeRange max_range = InputTimeAdjustment(kSamplesInput, -1, TimeRange(0, GetAudioLength())); if (Node *connected = this->GetConnectedSampleOutput()) {
TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range); TimeRange max_range = InputTimeAdjustment(kSamplesInput, -1, TimeRange(0, GetAudioLength()));
for (const TimeRange &r : invalid) { TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range);
connected->waveform_cache()->Request(r); for (const TimeRange &r : invalid) {
connected->waveform_cache()->Request(r);
}
} }
} }
} }
+3 -1
View File
@@ -182,7 +182,7 @@ public:
virtual ValueHint GetConnectedSampleValueHint(); virtual ValueHint GetConnectedSampleValueHint();
virtual void ConnectedToPreviewEvent() override; void SetWaveformEnabled(bool e);
bool IsVideoAutoCacheEnabled() const { qDebug() << "sequence ac is a stub"; return false; } bool IsVideoAutoCacheEnabled() const { qDebug() << "sequence ac is a stub"; return false; }
void SetVideoAutoCacheEnabled(bool e) { qDebug() << "sequence ac is a stub"; } void SetVideoAutoCacheEnabled(bool e) { qDebug() << "sequence ac is a stub"; }
@@ -251,6 +251,8 @@ private:
EncodingParams last_used_encoding_params_; EncodingParams last_used_encoding_params_;
bool waveform_requests_enabled_;
}; };
} }
+8 -2
View File
@@ -276,7 +276,8 @@ void Footage::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV
// Adjust footage job's divider // Adjust footage job's divider
if (globals.vparams().divider() > 1) { if (globals.vparams().divider() > 1) {
// Use a divider appropriate for this target resolution // Use a divider appropriate for this target resolution
vp.set_divider(VideoParams::GetDividerForTargetResolution(vp.width(), vp.height(), globals.vparams().effective_width(), globals.vparams().effective_height())); int calculated = VideoParams::GetDividerForTargetResolution(vp.width(), vp.height(), globals.vparams().effective_width(), globals.vparams().effective_height());
vp.set_divider(std::min(calculated, globals.vparams().divider()));
} else { } else {
// Render everything at full res // Render everything at full res
vp.set_divider(1); vp.set_divider(1);
@@ -544,7 +545,12 @@ void Footage::CheckFootage()
if (!fn.isEmpty()) { if (!fn.isEmpty()) {
QFileInfo info(fn); QFileInfo info(fn);
qint64 current_file_timestamp = info.lastModified().toMSecsSinceEpoch(); qint64 current_file_timestamp;
if (!info.lastModified().isValid()) {
current_file_timestamp = 0;
} else {
current_file_timestamp = info.lastModified().toMSecsSinceEpoch();
}
if (current_file_timestamp != timestamp()) { if (current_file_timestamp != timestamp()) {
// File has changed! // File has changed!
+9 -4
View File
@@ -38,11 +38,16 @@ NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRa
} }
// We need to insert tables into the database for each input // We need to insert tables into the database for each input
auto ignore = node->IgnoreInputsForRendering();
foreach (const QString& input, node->inputs()) { foreach (const QString& input, node->inputs()) {
if (IsCancelled()) { if (IsCancelled()) {
return NodeValueDatabase(); return NodeValueDatabase();
} }
if (ignore.contains(input)) {
continue;
}
database.Insert(input, ProcessInput(node, input, range)); database.Insert(input, ProcessInput(node, input, range));
} }
@@ -187,12 +192,12 @@ NodeGlobals NodeTraverser::GenerateGlobals(const VideoParams &vparams, const Aud
NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& input, const TimeRange& range) NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& input, const TimeRange& range)
{ {
// If input is connected, retrieve value directly // If input is connected, retrieve value directly
if (node->IsInputConnected(input)) { if (node->IsInputConnectedForRender(input)) {
TimeRange adjusted_range = node->InputTimeAdjustment(input, -1, range); TimeRange adjusted_range = node->InputTimeAdjustment(input, -1, range);
// Value will equal something from the connected node, follow it // Value will equal something from the connected node, follow it
Node *output = node->GetConnectedOutput(input); Node *output = node->GetConnectedRenderOutput(input);
NodeValueTable table = GenerateTable(output, adjusted_range, node); NodeValueTable table = GenerateTable(output, adjusted_range, node);
return table; return table;
@@ -242,8 +247,8 @@ void NodeTraverser::ProcessInputElement(NodeValueTableArray &array_tbl, const No
NodeValueTable& sub_tbl = array_tbl[element]; NodeValueTable& sub_tbl = array_tbl[element];
TimeRange adjusted_range = node->InputTimeAdjustment(input, element, range); TimeRange adjusted_range = node->InputTimeAdjustment(input, element, range);
if (node->IsInputConnected(input, element)) { if (node->IsInputConnectedForRender(input, element)) {
Node *output = node->GetConnectedOutput(input, element); Node *output = node->GetConnectedRenderOutput(input, element);
sub_tbl = GenerateTable(output, adjusted_range, node); sub_tbl = GenerateTable(output, adjusted_range, node);
} else { } else {
QVariant input_value = node->GetValueAtTime(input, adjusted_range.in(), element); QVariant input_value = node->GetValueAtTime(input, adjusted_range.in(), element);
+5
View File
@@ -350,4 +350,9 @@ AudioParams::Format AudioParams::GetPlanarEquivalent(Format fmt)
return kFormatInvalid; return kFormatInvalid;
} }
void AudioParams::calculate_channel_count()
{
channel_count_ = av_get_channel_layout_nb_channels(channel_layout());
}
} }
+1 -4
View File
@@ -256,10 +256,7 @@ private:
duration_ = 0; duration_ = 0;
} }
void calculate_channel_count() void calculate_channel_count();
{
channel_count_ = av_get_channel_layout_nb_channels(channel_layout());
}
int sample_rate_; int sample_rate_;
+1 -1
View File
@@ -364,7 +364,7 @@ void OpenGLRenderer::Flush()
{ {
GL_PREAMBLE; GL_PREAMBLE;
functions_->glFinish(); functions_->glFlush();
} }
Color OpenGLRenderer::GetPixelFromTexture(Texture *texture, const QPointF &pt) Color OpenGLRenderer::GetPixelFromTexture(Texture *texture, const QPointF &pt)
+28 -17
View File
@@ -41,6 +41,7 @@ PreviewAutoCacher::PreviewAutoCacher(QObject *parent) :
viewer_node_(nullptr), viewer_node_(nullptr),
use_custom_range_(false), use_custom_range_(false),
pause_renders_(false), pause_renders_(false),
pause_thumbnails_(false),
single_frame_render_(nullptr), single_frame_render_(nullptr),
display_color_processor_(nullptr), display_color_processor_(nullptr),
multicam_(nullptr), multicam_(nullptr),
@@ -583,6 +584,14 @@ void PreviewAutoCacher::SetRendersPaused(bool e)
} }
} }
void PreviewAutoCacher::SetThumbnailsPaused(bool e)
{
pause_thumbnails_ = e;
if (!e) {
TryRender();
}
}
void PreviewAutoCacher::NodeAdded(Node *node) void PreviewAutoCacher::NodeAdded(Node *node)
{ {
graph_update_queue_.push_back({QueuedJob::kNodeAdded, node, NodeInput(), nullptr}); graph_update_queue_.push_back({QueuedJob::kNodeAdded, node, NodeInput(), nullptr});
@@ -663,29 +672,31 @@ void PreviewAutoCacher::TryRender()
const int max_tasks = 4; const int max_tasks = 4;
// Handle video tasks // Handle video tasks
while (!pending_video_jobs_.empty()) { if (!pause_thumbnails_) {
VideoJob &d = pending_video_jobs_.front(); while (!pending_video_jobs_.empty()) {
VideoJob &d = pending_video_jobs_.front();
if (Node *copy = copy_map_.value(d.node)) { if (Node *copy = copy_map_.value(d.node)) {
// Queue next frames // Queue next frames
rational t; rational t;
while (running_video_tasks_.size() < max_tasks && d.iterator.GetNext(&t)) { while (running_video_tasks_.size() < max_tasks && d.iterator.GetNext(&t)) {
RenderFrame(copy, t, d.cache, false); RenderFrame(copy, t, d.cache, false);
emit SignalCacheProxyTaskProgress(double(d.iterator.frame_index()) / double(d.iterator.size())); emit SignalCacheProxyTaskProgress(double(d.iterator.frame_index()) / double(d.iterator.size()));
if (!d.iterator.HasNext()) { if (!d.iterator.HasNext()) {
emit StopCacheProxyTasks(); emit StopCacheProxyTasks();
}
} }
} else {
qCritical() << "Failed to find node copy for video job";
} }
} else {
qCritical() << "Failed to find node copy for video job";
}
if (d.iterator.HasNext()) { if (d.iterator.HasNext()) {
break; break;
} else { } else {
pending_video_jobs_.pop_front(); pending_video_jobs_.pop_front();
}
} }
} }
+2
View File
@@ -89,6 +89,7 @@ public:
bool IsRenderingCustomRange() const; bool IsRenderingCustomRange() const;
void SetRendersPaused(bool e); void SetRendersPaused(bool e);
void SetThumbnailsPaused(bool e);
void SetMulticamNode(MultiCamNode *n) { multicam_ = n; } void SetMulticamNode(MultiCamNode *n) { multicam_ = n; }
@@ -178,6 +179,7 @@ private:
TimeRange custom_autocache_range_; TimeRange custom_autocache_range_;
bool pause_renders_; bool pause_renders_;
bool pause_thumbnails_;
RenderTicketPtr single_frame_render_; RenderTicketPtr single_frame_render_;
QMap<RenderTicketWatcher*, QVector<RenderTicketPtr> > video_immediate_passthroughs_; QMap<RenderTicketWatcher*, QVector<RenderTicketPtr> > video_immediate_passthroughs_;
+3 -5
View File
@@ -300,13 +300,11 @@ NodeValueDatabase RenderProcessor::GenerateDatabase(const Node *node, const Time
if (const MultiCamNode *multicam = dynamic_cast<const MultiCamNode*>(node)) { if (const MultiCamNode *multicam = dynamic_cast<const MultiCamNode*>(node)) {
if (Node::ValueToPtr<MultiCamNode>(ticket_->property("multicam")) == multicam) { if (Node::ValueToPtr<MultiCamNode>(ticket_->property("multicam")) == multicam) {
int sz = multicam->InputArraySize(multicam->kSourcesInput); int sz = multicam->GetSourceCount();
NodeValueTableArray arr;
QVector<TexturePtr> multicam_tex(sz); QVector<TexturePtr> multicam_tex(sz);
for (int i=0; i<sz; i++) { for (int i=0; i<sz; i++) {
ProcessInputElement(arr, multicam, multicam->kSourcesInput, i, range); NodeValueTable t = GenerateTable(multicam->GetConnectedRenderOutput(multicam->kSourcesInput, i), range, multicam);
NodeValue val = GenerateRowValueElement(multicam, multicam->kSourcesInput, i, &t, range);
NodeValue val = GenerateRowValueElement(multicam, multicam->kSourcesInput, i, &arr.at(i), range);
ResolveJobs(val); ResolveJobs(val);
multicam_tex[i] = val.toTexture(); multicam_tex[i] = val.toTexture();
+2 -1
View File
@@ -164,6 +164,7 @@ bool VideoParams::operator==(const VideoParams &rhs) const
return width() == rhs.width() return width() == rhs.width()
&& height() == rhs.height() && height() == rhs.height()
&& depth() == rhs.depth() && depth() == rhs.depth()
&& interlacing() == rhs.interlacing()
&& time_base() == rhs.time_base() && time_base() == rhs.time_base()
&& format() == rhs.format() && format() == rhs.format()
&& pixel_aspect_ratio() == rhs.pixel_aspect_ratio() && pixel_aspect_ratio() == rhs.pixel_aspect_ratio()
@@ -262,7 +263,7 @@ void VideoParams::calculate_effective_size()
{ {
effective_width_ = GetScaledDimension(width(), divider_); effective_width_ = GetScaledDimension(width(), divider_);
effective_height_ = GetScaledDimension(height(), divider_); effective_height_ = GetScaledDimension(height(), divider_);
effective_depth_ = GetScaledDimension(depth(), divider_); effective_depth_ = (depth() == 1) ? depth() : GetScaledDimension(depth(), divider_);
calculate_square_pixel_width(); calculate_square_pixel_width();
} }
+1 -2
View File
@@ -100,8 +100,7 @@ void main() {
mask = 1.0 - mask; mask = 1.0 - mask;
} }
col.rgb *= mask; col *= mask;
col.w = mask;
if (!mask_only_in) { if (!mask_only_in) {
frag_color = col; frag_color = col;
+14 -4
View File
@@ -58,10 +58,18 @@ MulticamWidget::MulticamWidget(QWidget *parent) :
void MulticamWidget::SetMulticamNodeInternal(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip) void MulticamWidget::SetMulticamNodeInternal(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip)
{ {
ConnectViewerNode(viewer); if (GetConnectedNode() != viewer) {
node_ = n; ConnectViewerNode(viewer);
display_->SetMulticamNode(n); }
clip_ = clip;
if (node_ != n) {
node_ = n;
display_->SetMulticamNode(n);
}
if (clip_ != clip) {
clip_ = clip;
}
} }
void MulticamWidget::SetMulticamNode(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip, const rational &time) void MulticamWidget::SetMulticamNode(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip, const rational &time)
@@ -145,6 +153,8 @@ void MulticamWidget::Switch(int source, bool split_clip)
Core::instance()->undo_stack()->push(command); Core::instance()->undo_stack()->push(command);
display_->update(); display_->update();
emit Switched();
} }
void MulticamWidget::DisplayClicked(const QPoint &p) void MulticamWidget::DisplayClicked(const QPoint &p)
+3
View File
@@ -42,6 +42,9 @@ protected:
virtual void DisconnectNodeEvent(ViewerOutput *n) override; virtual void DisconnectNodeEvent(ViewerOutput *n) override;
virtual void TimeChangedEvent(const rational &t) override; virtual void TimeChangedEvent(const rational &t) override;
signals:
void Switched();
private: private:
void SetMulticamNodeInternal(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip); void SetMulticamNodeInternal(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip);
+61 -27
View File
@@ -1235,30 +1235,23 @@ void TimelineWidget::ShowContextMenu()
reveal_in_project->setData(reinterpret_cast<quintptr>(clip->connected_viewer())); reveal_in_project->setData(reinterpret_cast<quintptr>(clip->connected_viewer()));
connect(reveal_in_project, &QAction::triggered, this, &TimelineWidget::RevealInProject); connect(reveal_in_project, &QAction::triggered, this, &TimelineWidget::RevealInProject);
/*if (Sequence *sequence = dynamic_cast<Sequence*>(clip->connected_viewer())) { if (Sequence *sequence = dynamic_cast<Sequence*>(clip->connected_viewer())) {
Menu *multicam_menu = new Menu(tr("Multi-Cam"), &menu); QAction *multicam_enabled = menu.addAction(tr("Multi-Cam"));
menu.addMenu(multicam_menu);
QAction *multicam_enabled = multicam_menu->addAction(tr("Enabled"));
multicam_enabled->setCheckable(true); multicam_enabled->setCheckable(true);
auto mcn = sequence->FindOutputNode<MultiCamNode>(); MultiCamNode *mcn = nullptr;
multicam_enabled->setChecked(!mcn.empty()); auto paths = clip->FindWaysNodeArrivesHere(sequence);
multicam_menu->addSeparator(); for (const NodeInput &i : paths) {
if ((mcn = dynamic_cast<MultiCamNode*>(i.node()))) {
QAction *multicam_update = multicam_menu->addAction(tr("Update")); break;
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));
} }
multicam_enabled->setChecked(mcn);
connect(multicam_enabled, &QAction::triggered, this, &TimelineWidget::MulticamEnabledTriggered); connect(multicam_enabled, &QAction::triggered, this, &TimelineWidget::MulticamEnabledTriggered);
connect(multicam_update, &QAction::triggered, this, &TimelineWidget::MulticamUpdateTriggered); }
}*/
} }
} }
@@ -1492,16 +1485,57 @@ void TimelineWidget::CacheDiscard()
void TimelineWidget::MulticamEnabledTriggered(bool e) void TimelineWidget::MulticamEnabledTriggered(bool e)
{ {
if (e) { MultiUndoCommand *command = new MultiUndoCommand();
// Add multicam node
} else if (MultiCamNode *m = Node::ValueToPtr<MultiCamNode>(sender()->property("multicam"))) {
// Remove multicam node
}
}
void TimelineWidget::MulticamUpdateTriggered() for (Block *b : qAsConst(selected_blocks_)) {
{ if (ClipBlock *c = dynamic_cast<ClipBlock*>(b)) {
// Update multicam node if (Sequence *s = dynamic_cast<Sequence*>(c->connected_viewer())) {
if (e) {
// Adding multicams
// Create multicam node and add it to the graph
MultiCamNode *n = new MultiCamNode();
n->SetSequenceType(c->GetTrackType());
command->add_child(new NodeAddCommand(s->parent(), n));
// For each output the sequence has to this clip, disconnect it and
// connect to the multicam instead
QVector<NodeInput> inputs = c->FindWaysNodeArrivesHere(s);
for (const NodeInput &i : inputs) {
command->add_child(new NodeEdgeRemoveCommand(s, i));
command->add_child(new NodeEdgeAddCommand(n, i));
}
command->add_child(new NodeEdgeAddCommand(s, NodeInput(n, n->kSequenceInput)));
// Move sequence node one unit back, and place multicam in sequence's spot
QPointF sequence_pos = c->GetNodePositionInContext(s);
command->add_child(new NodeSetPositionCommand(s, c, sequence_pos - QPointF(1, 0)));
command->add_child(new NodeSetPositionCommand(n, c, sequence_pos));
} else {
// Removing multicams
// Locate first multicam that specifically ends up at this clip
QVector<NodeInput> inputs = c->FindWaysNodeArrivesHere(s);
for (const NodeInput &i : inputs) {
if (MultiCamNode *mcn = dynamic_cast<MultiCamNode*>(i.node())) {
for (auto it=mcn->output_connections().cbegin(); it!=mcn->output_connections().cend(); it++) {
command->add_child(new NodeEdgeRemoveCommand(it->first, it->second));
command->add_child(new NodeEdgeAddCommand(s, it->second));
}
command->add_child(new NodeRemoveAndDisconnectCommand(mcn));
}
}
}
}
}
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
} }
void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost) void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost)
@@ -447,7 +447,6 @@ private slots:
void CacheDiscard(); void CacheDiscard();
void MulticamEnabledTriggered(bool e); void MulticamEnabledTriggered(bool e);
void MulticamUpdateTriggered();
}; };
+20 -3
View File
@@ -427,7 +427,15 @@ void ViewerWidget::StartCapture(TimelineWidget *source, const TimeRange &time, c
void ViewerWidget::ConnectMulticamWidget(MulticamWidget *p) void ViewerWidget::ConnectMulticamWidget(MulticamWidget *p)
{ {
if (multicam_panel_) {
disconnect(multicam_panel_, &MulticamWidget::Switched, this, &ViewerWidget::DetectMulticamNodeNow);
}
multicam_panel_ = p; multicam_panel_ = p;
if (multicam_panel_) {
connect(multicam_panel_, &MulticamWidget::Switched, this, &ViewerWidget::DetectMulticamNodeNow);
}
} }
FramePtr ViewerWidget::DecodeCachedImage(const QString &cache_path, const QUuid &cache_id, const int64_t& time) FramePtr ViewerWidget::DecodeCachedImage(const QString &cache_path, const QUuid &cache_id, const int64_t& time)
@@ -607,6 +615,11 @@ void ViewerWidget::SaveFrameAsImage()
Core::instance()->OpenExportDialogForViewer(GetConnectedNode(), GetTime(), true); Core::instance()->OpenExportDialogForViewer(GetConnectedNode(), GetTime(), true);
} }
void ViewerWidget::DetectMulticamNodeNow()
{
DetectMulticamNode(GetTime());
}
void ViewerWidget::CloseAudioProcessor() void ViewerWidget::CloseAudioProcessor()
{ {
audio_processor_.Close(); audio_processor_.Close();
@@ -695,6 +708,10 @@ void ViewerWidget::UpdateWaveformViewFromMode()
waveform_view_->setVisible(waveform_mode_ == kWFViewerAndWaveform || waveform_mode_ == kWFWaveformOnly || (waveform_mode_ == kWFAutomatic && prefer_waveform)); waveform_view_->setVisible(waveform_mode_ == kWFViewerAndWaveform || waveform_mode_ == kWFWaveformOnly || (waveform_mode_ == kWFAutomatic && prefer_waveform));
waveform_view_->setSizePolicy(QSizePolicy::Expanding, waveform_mode_ == kWFViewerAndWaveform ? QSizePolicy::Maximum : QSizePolicy::Expanding); waveform_view_->setSizePolicy(QSizePolicy::Expanding, waveform_mode_ == kWFViewerAndWaveform ? QSizePolicy::Maximum : QSizePolicy::Expanding);
if (GetConnectedNode()) {
GetConnectedNode()->SetWaveformEnabled(waveform_view_->isVisible());
}
} }
void ViewerWidget::QueueNextAudioBuffer() void ViewerWidget::QueueNextAudioBuffer()
@@ -903,7 +920,7 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
if (viewer != this) { if (viewer != this) {
viewer->PauseInternal(); viewer->PauseInternal();
} }
viewer->auto_cacher_->SetRendersPaused(true); viewer->auto_cacher_->SetThumbnailsPaused(true);
} }
RenderManager::instance()->SetAggressiveGarbageCollection(true); RenderManager::instance()->SetAggressiveGarbageCollection(true);
@@ -1005,7 +1022,7 @@ void ViewerWidget::PauseInternal()
UpdateAudioProcessor(); UpdateAudioProcessor();
foreach (ViewerWidget* viewer, instances_) { foreach (ViewerWidget* viewer, instances_) {
viewer->auto_cacher_->SetRendersPaused(false); viewer->auto_cacher_->SetThumbnailsPaused(false);
} }
UpdateTextureFromNode(); UpdateTextureFromNode();
@@ -1796,7 +1813,7 @@ void ViewerWidget::SetZoomFromMenu(QAction *action)
void ViewerWidget::ViewerInvalidatedVideoRange(const TimeRange &range) void ViewerWidget::ViewerInvalidatedVideoRange(const TimeRange &range)
{ {
// If our current frame is within this range, we need to update // If our current frame is within this range, we need to update
if (GetTime() >= range.in() && (GetTime() < range.out() || range.in() == range.out())) { if (!IsPlaying() && GetTime() >= range.in() && (GetTime() < range.out() || range.in() == range.out())) {
QMetaObject::invokeMethod(this, &ViewerWidget::UpdateTextureFromNode, Qt::QueuedConnection); QMetaObject::invokeMethod(this, &ViewerWidget::UpdateTextureFromNode, Qt::QueuedConnection);
} }
} }
+4 -2
View File
@@ -117,7 +117,7 @@ public:
if (!IsPlaying()) { if (!IsPlaying()) {
// If is playing, this will happen by the next frame automatically // If is playing, this will happen by the next frame automatically
DetectMulticamNode(GetTime()); DetectMulticamNodeNow();
UpdateTextureFromNode(); UpdateTextureFromNode();
} }
} }
@@ -128,7 +128,7 @@ public:
if (!IsPlaying()) { if (!IsPlaying()) {
// If is playing, this will happen by the next frame automatically // If is playing, this will happen by the next frame automatically
DetectMulticamNode(GetTime()); DetectMulticamNodeNow();
UpdateTextureFromNode(); UpdateTextureFromNode();
} }
} }
@@ -421,6 +421,8 @@ private slots:
void SaveFrameAsImage(); void SaveFrameAsImage();
void DetectMulticamNodeNow();
}; };
} }
+3 -1
View File
@@ -555,7 +555,9 @@ void MainWindow::UpdateTitle()
void MainWindow::TimelineCloseRequested() void MainWindow::TimelineCloseRequested()
{ {
RemoveTimelinePanel(static_cast<TimelinePanel*>(sender())); TimelinePanel *t = static_cast<TimelinePanel*>(sender());
RemoveTimelinePanel(t);
main_time_panels_.removeOne(t);
} }
void MainWindow::ProjectCloseRequested() void MainWindow::ProjectCloseRequested()