From 6371fe0bc07833cc05be0b2c4b078807295caa90 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 24 May 2022 17:18:22 -0700 Subject: [PATCH 1/9] decoder: removed interlacing property Interlacing should be moved somewhere else in the pipeline --- app/codec/decoder.cpp | 4 ++-- app/codec/decoder.h | 10 +++------- app/codec/ffmpeg/ffmpegdecoder.cpp | 20 +++----------------- app/render/renderprocessor.cpp | 2 -- 4 files changed, 8 insertions(+), 28 deletions(-) diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index d63c3bc45..9fa9765fb 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -199,14 +199,14 @@ DecoderPtr Decoder::CreateFromID(const QString &id) return nullptr; } -int64_t Decoder::GetTimeInTimebaseUnits(const rational &time, const rational &timebase, int64_t start_time, VideoParams::Interlacing interlacing) +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, VideoParams::Interlacing interlacing) +rational Decoder::GetTimestampInTimeUnits(int64_t time, const rational &timebase, int64_t start_time) { time -= start_time; return Timecode::timestamp_to_time(time, timebase); diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 8bab6f6ca..ad778eafb 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -150,13 +150,9 @@ public: RetrieveVideoParams() { divider = 1; - src_interlacing = VideoParams::kInterlaceNone; - dst_interlacing = VideoParams::kInterlaceNone; } int divider; - VideoParams::Interlacing src_interlacing; - VideoParams::Interlacing dst_interlacing; void reset() { @@ -165,7 +161,7 @@ public: bool operator==(const RetrieveVideoParams& rhs) const { - return divider == rhs.divider && src_interlacing == rhs.src_interlacing && dst_interlacing == rhs.dst_interlacing; + return divider == rhs.divider; } bool operator!=(const RetrieveVideoParams& rhs) const @@ -295,8 +291,8 @@ protected: return stream_; } - static int64_t GetTimeInTimebaseUnits(const rational& time, const rational& timebase, int64_t start_time, VideoParams::Interlacing interlacing); - static rational GetTimestampInTimeUnits(int64_t time, const rational& timebase, int64_t start_time, VideoParams::Interlacing interlacing); + static int64_t GetTimeInTimebaseUnits(const rational& time, const rational& timebase, int64_t start_time); + static rational GetTimestampInTimeUnits(int64_t time, const rational& timebase, int64_t start_time); signals: /** diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index b990824d5..d191efde6 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -654,7 +654,7 @@ void FFmpegDecoder::ClearFrameCache() AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, const QAtomicInt *cancelled) { - int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time, filter_params_.src_interlacing); + int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time); const int64_t min_seek = -instance_.avstream()->start_time; int64_t seek_ts = target_ts; @@ -843,19 +843,6 @@ bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params // Link filters as necessary AVFilterContext *last_filter = buffersrc_ctx_; - // Add interlacing filter if necessary - if (filter_params_.src_interlacing != VideoParams::kInterlaceNone) { - // Footage is interlaced, our renderer works in progressive so we'll need to de-interlace - AVFilterContext* interlace_filter; - - snprintf(filter_args, kFilterArgSz, "mode=1:parity=%s", - GetInterlacingModeInFFmpeg(filter_params_.src_interlacing)); - avfilter_graph_create_filter(&interlace_filter, avfilter_get_by_name("yadif"), "yadif", filter_args, nullptr, filter_graph_); - - avfilter_link(last_filter, 0, interlace_filter, 0); - last_filter = interlace_filter; - } - // Add scale filter if necessary int dst_width, dst_height; if (filter_params_.divider > 1) { @@ -864,10 +851,9 @@ bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params dst_width = VideoParams::GetScaledDimension(src_width, filter_params_.divider); dst_height = VideoParams::GetScaledDimension(src_height, filter_params_.divider); - snprintf(filter_args, kFilterArgSz, "w=%d:h=%d:flags=fast_bilinear:interl=%d", + snprintf(filter_args, kFilterArgSz, "w=%d:h=%d:flags=fast_bilinear:interl=-1", dst_width, - dst_height, - params.dst_interlacing != VideoParams::kInterlaceNone); + dst_height); avfilter_graph_create_filter(&scale_filter, avfilter_get_by_name("scale"), "scale", filter_args, nullptr, filter_graph_); diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 97f03d827..0eb5d294e 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -446,8 +446,6 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ if (decoder) { Decoder::RetrieveVideoParams p; p.divider = stream.video_params().divider(); - p.src_interlacing = stream_data.interlacing(); - p.dst_interlacing = GetCacheVideoParams().interlacing(); if (!IsCancelled()) { From c2570b23c36e348c6477a7a27675222e932d3fe7 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 24 May 2022 18:49:29 -0700 Subject: [PATCH 2/9] oiiodecoder: improved code --- app/codec/oiio/oiiodecoder.cpp | 88 ++++++++++++++++++---------------- app/codec/oiio/oiiodecoder.h | 8 ++-- app/render/renderprocessor.cpp | 18 +++---- 3 files changed, 61 insertions(+), 53 deletions(-) diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index 9f755216f..1d98bae6c 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -20,7 +20,6 @@ #include "oiiodecoder.h" -#include #include #include #include @@ -36,8 +35,7 @@ namespace olive { QStringList OIIODecoder::supported_formats_; OIIODecoder::OIIODecoder() : - image_(nullptr), - buffer_(nullptr) + image_(nullptr) { } @@ -75,17 +73,11 @@ FootageDescription OIIODecoder::Probe(const QString &filename, const QAtomicInt* bool stream_enabled = true; for (int i=0; in->seek_subimage(i, 0); i++) { - VideoParams video_params; - OIIO::ImageSpec spec = in->spec(); + VideoParams video_params = GetVideoParamsFromImageSpec(spec); + video_params.set_stream_index(i); - video_params.set_width(spec.width); - video_params.set_height(spec.height); - video_params.set_format(OIIOUtils::GetFormatFromOIIOBasetype(static_cast(spec.format.basetype))); - video_params.set_channel_count(spec.nchannels); - video_params.set_pixel_aspect_ratio(OIIOUtils::GetPixelAspectRatioFromOIIO(spec)); - video_params.set_video_type(VideoParams::kVideoTypeStill); if (i > 1) { // This is a multilayer image and this image might have an offset @@ -128,31 +120,40 @@ bool OIIODecoder::RetrieveVideoInternal(TexturePtr destination, const rational & Q_UNUSED(timecode) Q_UNUSED(cancelled) - VideoParams vp(buffer_->spec().width, - buffer_->spec().height, - pix_fmt_, - channel_count_, - OIIOUtils::GetPixelAspectRatioFromOIIO(buffer_->spec()), - VideoParams::kInterlaceNone, // FIXME: Does OIIO deinterlace for us? - params.divider); + VideoParams vp = GetVideoParamsFromImageSpec(image_->spec()); + vp.set_divider(params.divider); - if (params.divider == 1) { + if (!buffer_.is_allocated() || last_params_ != params) { + last_params_ = params; - destination->Upload(buffer_->localpixels(), buffer_->scanline_stride() / vp.GetBytesPerPixel()); + buffer_.destroy(); + buffer_.set_video_params(vp); + buffer_.allocate(); - } else { + if (params.divider == 1) { + // Just upload straight to the buffer + image_->read_image(oiio_pix_fmt_, buffer_.data(), OIIO::AutoStride, buffer_.linesize_bytes()); + } else { + OIIO::ImageBuf buf(image_->spec()); + image_->read_image(image_->spec().format, buf.localpixels(), buf.pixel_stride(), buf.scanline_stride(), buf.z_stride()); - // Will need to resize the image - OIIO::ImageBuf dst(OIIO::ImageSpec(vp.effective_width(), vp.effective_height(), buffer_->spec().nchannels, buffer_->spec().format)); + // Roughly downsample image for divider (for some reason OIIO::ImageBufAlgo::resample failed here) + int px_sz = vp.GetBytesPerPixel(); + for (int dst_y=0; dst_y(buf.localpixels()) + buf.scanline_stride() * src_y + px_sz * src_x, + px_sz); + } + } } - - destination->Upload(dst.localpixels(), dst.scanline_stride() / vp.GetBytesPerPixel()); - } + destination->Upload(buffer_.data(), buffer_.linesize_pixels()); + return true; } @@ -201,9 +202,6 @@ bool OIIODecoder::OpenImageHandler(const QString &fn, int subimage) // Check if we can work with this pixel format const OIIO::ImageSpec& spec = image_->spec(); - // Store channel count - channel_count_ = spec.nchannels; - // We use RGBA frames because that tends to be the native format of GPUs pix_fmt_ = OIIOUtils::GetFormatFromOIIOBasetype(static_cast(spec.format.basetype)); @@ -212,18 +210,13 @@ bool OIIODecoder::OpenImageHandler(const QString &fn, int subimage) return false; } - OIIO::TypeDesc::BASETYPE type = OIIOUtils::GetOIIOBaseTypeFromFormat(pix_fmt_); + oiio_pix_fmt_ = OIIOUtils::GetOIIOBaseTypeFromFormat(pix_fmt_); - if (type == OIIO::TypeDesc::UNKNOWN) { + if (oiio_pix_fmt_ == OIIO::TypeDesc::UNKNOWN) { qCritical() << "Failed to determine appropriate OIIO basetype from native format"; return false; } - buffer_ = new OIIO::ImageBuf(OIIO::ImageSpec(spec.width, spec.height, spec.nchannels, type), - OIIO::InitializePixels::No); - - image_->read_image(type, buffer_->localpixels()); - return true; } @@ -234,10 +227,21 @@ void OIIODecoder::CloseImageHandle() image_ = nullptr; } - if (buffer_) { - delete buffer_; - buffer_ = nullptr; - } + buffer_.destroy(); +} + +VideoParams OIIODecoder::GetVideoParamsFromImageSpec(const OIIO::ImageSpec &spec) +{ + VideoParams video_params; + + video_params.set_width(spec.width); + video_params.set_height(spec.height); + video_params.set_format(OIIOUtils::GetFormatFromOIIOBasetype(static_cast(spec.format.basetype))); + video_params.set_channel_count(spec.nchannels); + video_params.set_pixel_aspect_ratio(OIIOUtils::GetPixelAspectRatioFromOIIO(spec)); + video_params.set_video_type(VideoParams::kVideoTypeStill); + + return video_params; } } diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index b5360e042..3d863da4e 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -56,11 +56,13 @@ private: void CloseImageHandle(); + static VideoParams GetVideoParamsFromImageSpec(const OIIO::ImageSpec &spec); + VideoParams::Format pix_fmt_; + OIIO::TypeDesc::BASETYPE oiio_pix_fmt_; - int channel_count_; - - OIIO::ImageBuf* buffer_; + Frame buffer_; + RetrieveVideoParams last_params_; static QStringList supported_formats_; diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 0eb5d294e..064279753 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -424,23 +424,25 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ DecoderPtr decoder = nullptr; - if (stream_data.video_type() == VideoParams::kVideoTypeVideo) { + switch (stream_data.video_type()) { + case VideoParams::kVideoTypeVideo: + case VideoParams::kVideoTypeStill: decoder = ResolveDecoderFromInput(decoder_id, default_codec_stream); - } else { + break; + case VideoParams::kVideoTypeImageSequence: + { // Since image sequences involve multiple files, we don't engage the decoder cache decoder = Decoder::CreateFromID(decoder_id); QString frame_filename; - if (stream_data.video_type() == VideoParams::kVideoTypeImageSequence) { - int64_t frame_number = stream_data.get_time_in_timebase_units(input_time); - frame_filename = Decoder::TransformImageSequenceFileName(stream.filename(), frame_number); - } else { - frame_filename = stream.filename(); - } + int64_t frame_number = stream_data.get_time_in_timebase_units(input_time); + frame_filename = Decoder::TransformImageSequenceFileName(stream.filename(), frame_number); // Decoder will close automatically since it's a stream_ptr decoder->Open(Decoder::CodecStream(frame_filename, stream_data.stream_index())); + break; + } } if (decoder) { From 6a65c0fa3881ea65f199fa3ec159b1663c7ed462 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 27 May 2022 10:04:30 -0700 Subject: [PATCH 3/9] render: clamp audio samples when finalized --- app/codec/samplebuffer.cpp | 37 ++++++++++++++++++++++++++++++++++ app/codec/samplebuffer.h | 6 ++++++ app/render/renderprocessor.cpp | 14 ++++++++----- 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/app/codec/samplebuffer.cpp b/app/codec/samplebuffer.cpp index 3c0096867..fd4dfa5a7 100644 --- a/app/codec/samplebuffer.cpp +++ b/app/codec/samplebuffer.cpp @@ -191,6 +191,13 @@ void SampleBuffer::transform_volume_for_sample_on_channel(int sample_index, int data_[channel][sample_index] *= volume; } +void SampleBuffer::clamp() +{ + for (int i=0; iproperty("enablewaveforms").toBool()) { - AudioVisualWaveform vis; - vis.set_channel_count(samples.audio_params().channel_count()); - vis.OverwriteSamples(samples, samples.audio_params().sample_rate()); - ticket_->setProperty("waveform", QVariant::fromValue(vis)); + if (samples.is_allocated()) { + samples.clamp(); + + if (ticket_->property("enablewaveforms").toBool()) { + AudioVisualWaveform vis; + vis.set_channel_count(samples.audio_params().channel_count()); + vis.OverwriteSamples(samples, samples.audio_params().sample_rate()); + ticket_->setProperty("waveform", QVariant::fromValue(vis)); + } } if (HeardCancel()) { From 15087b2cd2686bbe350220a6d685964e36423242 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 28 May 2022 10:27:28 -0700 Subject: [PATCH 4/9] render: clear task lists when changing node --- app/render/previewautocacher.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index f697a6aca..9ea8439cb 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -739,12 +739,14 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) if (!video_tasks_.isEmpty()) { // Cancel any video tasks and wait for them to finish CancelVideoTasks(true); + video_tasks_.clear(); } // Handle audio rendering tasks if (!audio_tasks_.isEmpty()) { // Cancel any audio tasks and wait for them to finish CancelAudioTasks(true); + audio_tasks_.clear(); } // Clear iterators From fc68a123baa977db0b6bf75cdb8eb39e40b3fef4 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 28 May 2022 12:25:50 -0700 Subject: [PATCH 5/9] project: reprobe and merge params when file changes are found --- app/codec/ffmpeg/ffmpegdecoder.cpp | 1 - app/node/node.h | 2 + app/node/output/viewer/viewer.cpp | 12 +- app/node/output/viewer/viewer.h | 1 + app/node/project/footage/footage.cpp | 161 ++++++++++++------ app/node/project/footage/footage.h | 6 + .../project/serializer/serializer210528.cpp | 2 + .../project/serializer/serializer210907.cpp | 2 + .../project/serializer/serializer211228.cpp | 2 + .../project/serializer/serializer220403.cpp | 2 + 10 files changed, 133 insertions(+), 58 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index d191efde6..29af60cd6 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -271,7 +271,6 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, const QAtomicIn frame); compatible_pix_fmt = FFmpegUtils::GetCompatiblePixelFormat(static_cast(avstream->codecpar->format)); - qDebug() << "GOT IT FROM FRAME" << compatible_pix_fmt; } // Read second frame diff --git a/app/node/node.h b/app/node/node.h index d123c0e72..f43a4afcc 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -982,6 +982,8 @@ public: InputFlags GetInputFlags(const QString& input) const; void SetInputFlags(const QString &input, const InputFlags &f); + virtual void LoadFinishedEvent(){} + static void SetValueAtTime(const NodeInput &input, const rational &time, const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key); static std::list FindPath(Node *from, Node *to, int path_index = 0); diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 34103e887..50caee988 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -485,6 +485,11 @@ void ViewerOutput::set_parameters_from_footage(const QVector foo } int ViewerOutput::AddStream(Track::Type type, const QVariant& value) +{ + return SetStream(type, value, -1); +} + +int ViewerOutput::SetStream(Track::Type type, const QVariant &value, int index_in) { QString id; @@ -499,8 +504,11 @@ int ViewerOutput::AddStream(Track::Type type, const QVariant& value) } // Add another video/audio param to the array for this stream - int index = InputArraySize(id); - InputArrayAppend(id); + int index = (index_in == -1) ? InputArraySize(id) : index_in; + + if (index >= InputArraySize(id)) { + InputArrayResize(id, index+1); + } SetStandardValue(id, value, index); diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index b0ba3ec8c..c68225df8 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -201,6 +201,7 @@ protected: virtual void InputValueChangedEvent(const QString& input, int element) override; int AddStream(Track::Type type, const QVariant &value); + int SetStream(Track::Type type, const QVariant &value, int index); private: rational last_length_; diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 2feda5725..4a5e5f0a5 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -43,6 +43,8 @@ const QString Footage::kLoopModeInput = QStringLiteral("loop_in"); Footage::Footage(const QString &filename) : ViewerOutput(false, false), + timestamp_(0), + valid_(false), cancelled_(nullptr) { SetCacheTextures(true); @@ -53,7 +55,9 @@ Footage::Footage(const QString &filename) : Clear(); - set_filename(filename); + if (!filename.isEmpty()) { + set_filename(filename); + } QTimer *check_timer = new QTimer(this); check_timer->setInterval(5000); @@ -76,59 +80,7 @@ void Footage::InputValueChangedEvent(const QString &input, int element) // Reset internal stream cache Clear(); - // Determine if file still exists - QFileInfo info(filename()); - - if (info.exists()) { - // Grab timestamp - set_timestamp(info.lastModified().toMSecsSinceEpoch()); - - // Determine if we've already cached the metadata of this file - QString meta_cache_file = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)).filePath(FileFunctions::GetUniqueFileIdentifier(filename())); - - FootageDescription footage_info; - - // Try to load footage info from cache - if (!QFileInfo::exists(meta_cache_file) || !footage_info.Load(meta_cache_file)) { - - // Probe and create cache - QVector decoder_list = Decoder::ReceiveListOfAllDecoders(); - - foreach (DecoderPtr decoder, decoder_list) { - footage_info = decoder->Probe(filename(), cancelled_); - - if (footage_info.IsValid()) { - break; - } - } - - if (!footage_info.Save(meta_cache_file)) { - qWarning() << "Failed to save stream cache, footage will have to be re-probed"; - } - - } - - if (footage_info.IsValid()) { - decoder_ = footage_info.decoder(); - - for (int i=0; ifilename(); + + // In case of failure to load file, set timestamp to a value that will always be invalid so we + // continuously reprobe + set_timestamp(0); + + if (!filename.isEmpty()) { + QFileInfo info(filename); + + if (info.exists()) { + // Grab timestamp + set_timestamp(info.lastModified().toMSecsSinceEpoch()); + + // Determine if we've already cached the metadata of this file + QString meta_cache_file = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)).filePath(FileFunctions::GetUniqueFileIdentifier(filename)); + + FootageDescription footage_info; + + // Try to load footage info from cache + if (!QFileInfo::exists(meta_cache_file) || !footage_info.Load(meta_cache_file)) { + + // Probe and create cache + QVector decoder_list = Decoder::ReceiveListOfAllDecoders(); + + foreach (DecoderPtr decoder, decoder_list) { + footage_info = decoder->Probe(filename, cancelled_); + + if (footage_info.IsValid()) { + break; + } + } + + if (!footage_info.Save(meta_cache_file)) { + qWarning() << "Failed to save stream cache, footage will have to be re-probed"; + } + + } + + if (footage_info.IsValid()) { + decoder_ = footage_info.decoder(); + + InputArrayResize(kVideoParamsInput, footage_info.GetVideoStreams().size()); + for (int i=0; iGetVideoParams(i); + if (existing.is_valid()) { + video_stream = MergeVideoStream(video_stream, existing); + } + } + + SetStream(Track::kVideo, QVariant::fromValue(video_stream), i); + } + + InputArrayResize(kAudioParamsInput, footage_info.GetAudioStreams().size()); + for (int i=0; iskipCurrentElement(); } } + + node->LoadFinishedEvent(); } void ProjectSerializer210528::LoadInput(Node *node, QXmlStreamReader *reader, XMLNodeData &xml_node_data) const diff --git a/app/node/project/serializer/serializer210907.cpp b/app/node/project/serializer/serializer210907.cpp index 0ab48339b..edcb86ae3 100644 --- a/app/node/project/serializer/serializer210907.cpp +++ b/app/node/project/serializer/serializer210907.cpp @@ -240,6 +240,8 @@ void ProjectSerializer210907::LoadNode(Node *node, XMLNodeData &xml_node_data, Q reader->skipCurrentElement(); } } + + node->LoadFinishedEvent(); } void ProjectSerializer210907::LoadInput(Node *node, QXmlStreamReader *reader, XMLNodeData &xml_node_data) const diff --git a/app/node/project/serializer/serializer211228.cpp b/app/node/project/serializer/serializer211228.cpp index cbd8cf57f..41254b105 100644 --- a/app/node/project/serializer/serializer211228.cpp +++ b/app/node/project/serializer/serializer211228.cpp @@ -290,6 +290,8 @@ void ProjectSerializer211228::LoadNode(Node *node, XMLNodeData &xml_node_data, Q reader->skipCurrentElement(); } } + + node->LoadFinishedEvent(); } void ProjectSerializer211228::LoadInput(Node *node, QXmlStreamReader *reader, XMLNodeData &xml_node_data) const diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index 5a0a39822..2075b6ce8 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -548,6 +548,8 @@ void ProjectSerializer220403::LoadNode(Node *node, XMLNodeData &xml_node_data, Q reader->skipCurrentElement(); } } + + node->LoadFinishedEvent(); } void ProjectSerializer220403::SaveNode(Node *node, QXmlStreamWriter *writer) const From 8455e94de367606886e104035aef56cba01df56a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 28 May 2022 19:20:33 -0700 Subject: [PATCH 6/9] nodeparamview: fix crash when body is recreated --- app/widget/nodeparamview/nodeparamviewitem.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 9c4e2d1fc..603072e1a 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -83,7 +83,10 @@ void NodeParamViewItem::Retranslate() void NodeParamViewItem::RecreateBody() { - delete body_; + if (body_) { + body_->setParent(nullptr); + body_->deleteLater(); + } body_ = new NodeParamViewItemBody(node_, create_checkboxes_); connect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode); From abb9c409e2c2d7816a21affa3215acaae14d09b1 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 29 May 2022 09:20:12 -0700 Subject: [PATCH 7/9] sequence: add test preview option --- app/config/config.cpp | 1 + app/dialog/sequence/sequence.cpp | 23 +++++++++++++++++++ .../sequence/sequencedialogparametertab.cpp | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app/config/config.cpp b/app/config/config.cpp index d2cb089db..43668c467 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -102,6 +102,7 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("StopPlaybackOnLastFrame"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("UseLegacyColorInInputTab"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("ReassocLinToNonLin"), NodeValue::kBoolean, false); + SetEntryInternal(QStringLiteral("PreviewNonFloatDontAskAgain"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("AutoCacheDelay"), NodeValue::kInt, 1000); diff --git a/app/dialog/sequence/sequence.cpp b/app/dialog/sequence/sequence.cpp index 5e8d08e8e..28f8f98e6 100644 --- a/app/dialog/sequence/sequence.cpp +++ b/app/dialog/sequence/sequence.cpp @@ -106,6 +106,29 @@ void SequenceDialog::accept() return; } + if (!VideoParams::FormatIsFloat(parameter_tab_->GetSelectedPreviewFormat()) + && !OLIVE_CONFIG("PreviewNonFloatDontAskAgain").toBool()) { + QMessageBox b(this); + QCheckBox *dont_show_again_ = new QCheckBox(tr("Don't ask me again")); + + b.setIcon(QMessageBox::Warning); + b.setWindowTitle(tr("Low Quality Preview")); + b.setText(tr("The preview resolution has been set to a non-float format. This may cause banding and clipping artifacts in the preview.\n\n" + "Do you wish to continue?")); + b.setCheckBox(dont_show_again_); + + b.addButton(QMessageBox::Yes); + b.addButton(QMessageBox::No); + + if (b.exec() == QMessageBox::No) { + return; + } + + if (dont_show_again_->isChecked()) { + OLIVE_CONFIG("PreviewNonFloatDontAskAgain") = true; + } + } + // Generate video and audio parameter structs from data VideoParams video_params = VideoParams(parameter_tab_->GetSelectedVideoWidth(), parameter_tab_->GetSelectedVideoHeight(), diff --git a/app/dialog/sequence/sequencedialogparametertab.cpp b/app/dialog/sequence/sequencedialogparametertab.cpp index 73eff0f1e..364d34315 100644 --- a/app/dialog/sequence/sequencedialogparametertab.cpp +++ b/app/dialog/sequence/sequencedialogparametertab.cpp @@ -71,7 +71,7 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg preview_layout->addWidget(preview_resolution_label_, row, 2); row++; preview_layout->addWidget(new QLabel(tr("Quality:")), row, 0); - preview_format_field_ = new PixelFormatComboBox(true); + preview_format_field_ = new PixelFormatComboBox(false); preview_layout->addWidget(preview_format_field_, row, 1, 1, 2); row++; preview_layout->addWidget(new QLabel(tr("Auto-Cache:")), row, 0); From bbf6f8f9f96e16bdfc540f321b63598424e58552 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 29 May 2022 10:15:32 -0700 Subject: [PATCH 8/9] export: fixed stills --- app/common/timerange.cpp | 2 +- app/core.cpp | 14 +++++++++----- app/core.h | 2 +- app/dialog/export/export.cpp | 2 +- app/dialog/export/export.h | 8 ++++++++ app/widget/viewer/viewer.cpp | 5 +++-- app/widget/viewer/viewer.h | 7 +++++++ 7 files changed, 30 insertions(+), 10 deletions(-) diff --git a/app/common/timerange.cpp b/app/common/timerange.cpp index 7326794ef..6c70ec748 100644 --- a/app/common/timerange.cpp +++ b/app/common/timerange.cpp @@ -368,7 +368,7 @@ void TimeRangeListFrameIterator::UpdateIndexIfNecessary() range_index_++; if (range_index_ < list_.size()) { - current_ = Timecode::snap_time_to_timebase(list_.at(range_index_).in(), timebase_, Timecode::kCeil); + current_ = Timecode::snap_time_to_timebase(list_.at(range_index_).in(), timebase_, Timecode::kRound); } } } diff --git a/app/core.cpp b/app/core.cpp index f5bea63d4..f6f18e2fe 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -390,10 +390,12 @@ void Core::DialogProjectPropertiesShow() void Core::DialogExportShow() { - ViewerOutput* viewer = GetSequenceToExport(); + ViewerOutput* viewer; + rational time; - if (viewer) { + if (GetSequenceToExport(&viewer, &time)) { ExportDialog* ed = new ExportDialog(viewer, main_window_); + ed->SetTime(time); connect(ed, &ExportDialog::finished, ed, &ExportDialog::deleteLater); ed->open(); } @@ -857,7 +859,7 @@ void Core::SaveProjectInternal(Project* project, const QString& override_filenam psm->deleteLater(); } -ViewerOutput* Core::GetSequenceToExport() +bool Core::GetSequenceToExport(ViewerOutput **viewer, rational *time) { // First try the most recently focused time based window TimeBasedPanel* time_panel = PanelManager::instance()->MostRecentlyFocused(); @@ -875,7 +877,9 @@ ViewerOutput* Core::GetSequenceToExport() tr("This Sequence is empty. There is nothing to export."), QMessageBox::Ok); } else { - return time_panel->GetConnectedViewer(); + *viewer = time_panel->GetConnectedViewer(); + *time = time_panel->GetTime(); + return true; } } else { QMessageBox::critical(main_window_, @@ -884,7 +888,7 @@ ViewerOutput* Core::GetSequenceToExport() QMessageBox::Ok); } - return nullptr; + return false; } QString Core::GetAutoRecoveryIndexFilename() diff --git a/app/core.h b/app/core.h index 429fbb549..1c7a923ef 100644 --- a/app/core.h +++ b/app/core.h @@ -545,7 +545,7 @@ private: /** * @brief Retrieves the currently most active sequence for exporting */ - ViewerOutput *GetSequenceToExport(); + bool GetSequenceToExport(ViewerOutput **viewer, rational *time); static QString GetAutoRecoveryIndexFilename(); diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index be37d2f10..d805f8726 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -526,7 +526,7 @@ ExportParams ExportDialog::GenerateParams() const if (ExportCodec::IsCodecAStillImage(video_tab_->GetSelectedCodec()) && !video_tab_->IsImageSequenceSet()) { // Exporting as image without exporting image sequence, only export one frame rational export_time = video_tab_->GetStillImageTime(); - params.set_custom_range(TimeRange(export_time, export_time)); + params.set_custom_range(TimeRange(export_time, export_time + GetSelectedTimebase())); } else if (range_combobox_->currentIndex() == kRangeInToOut) { // Assume if this combobox is enabled, workarea is enabled - a check that we make in this dialog's constructor params.set_custom_range(viewer_node_->GetTimelinePoints()->workarea()->range()); diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h index dae5ee996..df16c4850 100644 --- a/app/dialog/export/export.h +++ b/app/dialog/export/export.h @@ -46,6 +46,14 @@ public: rational GetSelectedTimebase() const; + void SetTime(const rational &time) + { + preview_viewer_->SetAudioScrubbingEnabled(false); + preview_viewer_->SetTime(time); + video_tab_->SetTime(time); + preview_viewer_->SetAudioScrubbingEnabled(true); + } + protected: virtual void closeEvent(QCloseEvent *e) override; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index a32c5df02..41681b009 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -68,7 +68,8 @@ ViewerWidget::ViewerWidget(QWidget *parent) : prequeuing_video_(false), prequeuing_audio_(0), record_armed_(false), - recording_(false) + recording_(false), + enable_audio_scrubbing_(true) { // Set up main layout QVBoxLayout* layout = new QVBoxLayout(this); @@ -838,7 +839,7 @@ void ViewerWidget::PauseInternal() void ViewerWidget::PushScrubbedAudio() { - if (!IsPlaying() && GetConnectedNode() && OLIVE_CONFIG("AudioScrubbing").toBool()) { + if (!IsPlaying() && GetConnectedNode() && OLIVE_CONFIG("AudioScrubbing").toBool() && enable_audio_scrubbing_) { // Get audio src device from renderer const AudioParams& params = GetConnectedNode()->audio_playback_cache()->GetParameters(); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index bc998748c..b19e8f1f5 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -89,6 +89,11 @@ public: void StartCapture(TimelineWidget *source, const TimeRange &time, const Track::Reference &track); + void SetAudioScrubbingEnabled(bool e) + { + enable_audio_scrubbing_ = e; + } + public slots: void Play(bool in_to_out_only); @@ -276,6 +281,8 @@ private: qint64 queue_starved_start_; + bool enable_audio_scrubbing_; + private slots: void PlaybackTimerUpdate(); From 29908f2d8b2f1b78a054647a780177d69cc1155f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 29 May 2022 11:13:05 -0700 Subject: [PATCH 9/9] math: revised timestamp calculations around epsilon --- app/common/timecodefunctions.cpp | 14 ++++++++++++-- app/common/timerange.cpp | 2 +- tests/general/timerange-tests.cpp | 20 ++++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/app/common/timecodefunctions.cpp b/app/common/timecodefunctions.cpp index a0f97efb1..cbd9d4074 100644 --- a/app/common/timecodefunctions.cpp +++ b/app/common/timecodefunctions.cpp @@ -304,14 +304,24 @@ int64_t Timecode::time_to_timestamp(const double &time, const rational &timebase return 0; } + const double eps = 0.000000000001; + switch (floor) { case kRound: default: return qRound64(d); case kFloor: - return qFloor(d); + if (d > qCeil(d)-eps) { + return qCeil(d); + } else { + return qFloor(d); + } case kCeil: - return qCeil(d); + if (d < qFloor(d)+eps) { + return qFloor(d); + } else { + return qCeil(d); + } } } diff --git a/app/common/timerange.cpp b/app/common/timerange.cpp index 6c70ec748..7326794ef 100644 --- a/app/common/timerange.cpp +++ b/app/common/timerange.cpp @@ -368,7 +368,7 @@ void TimeRangeListFrameIterator::UpdateIndexIfNecessary() range_index_++; if (range_index_ < list_.size()) { - current_ = Timecode::snap_time_to_timebase(list_.at(range_index_).in(), timebase_, Timecode::kRound); + current_ = Timecode::snap_time_to_timebase(list_.at(range_index_).in(), timebase_, Timecode::kCeil); } } } diff --git a/tests/general/timerange-tests.cpp b/tests/general/timerange-tests.cpp index fe4d117fa..6ddfe14e1 100644 --- a/tests/general/timerange-tests.cpp +++ b/tests/general/timerange-tests.cpp @@ -105,4 +105,24 @@ OLIVE_ADD_TEST(TimeRangeListFrameIteratorSize) OLIVE_TEST_END; } +OLIVE_ADD_TEST(TimeRangeListFrameIteratorSize2) +{ + const rational timebase(1001, 30000); + + TimeRangeList ranges; + + ranges.insert(TimeRange(rational(247247, 30000), rational(31031, 3750))); // 1 + + TimeRange tr(rational(247247, 30000), rational(31031, 3750)); + + TimeRangeListFrameIterator iterator(ranges, timebase); + + QVector vec = iterator.ToVector(); + + OLIVE_ASSERT_EQUAL(vec.size(), 1); + OLIVE_ASSERT_EQUAL(iterator.size(), vec.size()); + + OLIVE_TEST_END; +} + }