diff --git a/app/common/timerange.h b/app/common/timerange.h index fe73796d5..882b14d36 100644 --- a/app/common/timerange.h +++ b/app/common/timerange.h @@ -115,6 +115,17 @@ public: bool contains(const TimeRange& range, bool in_inclusive = true, bool out_inclusive = true) const; + bool contains(const rational &r) const + { + for (const TimeRange &range : array_) { + if (range.Contains(r)) { + return true; + } + } + + return false; + } + bool isEmpty() const { return array_.isEmpty(); diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 1b179f5fa..2c4fe9f7a 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -260,6 +260,7 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu NodeTraverser::NodeTraverser() : cancel_(nullptr), + heard_cancel_(false), transform_(nullptr) { } diff --git a/app/node/traverser.h b/app/node/traverser.h index afe56db2b..e129096ab 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -128,11 +128,17 @@ protected: QVector2D GenerateResolution() const; - bool IsCancelled() const + bool IsCancelled() { - return cancel_ && *cancel_; + bool c = cancel_ && *cancel_; + if (c) { + heard_cancel_ = true; + } + return c; } + bool HeardCancel() const { return heard_cancel_; } + const QAtomicInt *GetCancelPointer() const { return cancel_; @@ -155,6 +161,7 @@ private: AudioParams audio_params_; const QAtomicInt *cancel_; + bool heard_cancel_; const Node *transform_start_; const Node *transform_now_; diff --git a/app/render/framehashcache.h b/app/render/framehashcache.h index a4978a564..fab330e6a 100644 --- a/app/render/framehashcache.h +++ b/app/render/framehashcache.h @@ -49,16 +49,19 @@ public: void ValidateTimestamp(const int64_t &ts); void ValidateTime(const rational &time); - /** - * @brief Return the path of the cached image at this time - */ - QString CachePathName(const int64_t &time) const; - QString CachePathName(const rational &time) const + bool IsFrameCached(const rational &time) const { - return CachePathName(ToTimestamp(time, Timecode::kFloor)); + return GetValidatedRanges().contains(time); } - static QString CachePathName(const QString& cache_path, const QUuid &cache_id, const int64_t &time); + QString GetValidCacheFilename(const rational &time) const + { + if (IsFrameCached(time)) { + return CachePathName(time); + } else { + return QString(); + } + } static bool SaveCacheFrame(const QString& filename, FramePtr frame); bool SaveCacheFrame(const int64_t &time, FramePtr frame) const; @@ -71,6 +74,17 @@ private: rational ToTime(const int64_t &ts) const; int64_t ToTimestamp(const rational &ts, Timecode::Rounding rounding = Timecode::kRound) const; + /** + * @brief Return the path of the cached image at this time + */ + QString CachePathName(const int64_t &time) const; + QString CachePathName(const rational &time) const + { + return CachePathName(ToTimestamp(time, Timecode::kRound)); + } + + static QString CachePathName(const QString& cache_path, const QUuid &cache_id, const int64_t &time); + rational timebase_; QUuid uuid_; diff --git a/app/render/playbackcache.h b/app/render/playbackcache.h index 3572c053d..fa67f8866 100644 --- a/app/render/playbackcache.h +++ b/app/render/playbackcache.h @@ -59,6 +59,8 @@ public: void Invalidate(const TimeRange& r, bool signal = true); + const TimeRangeList &GetValidatedRanges() const { return validated_; } + public slots: void InvalidateAll(); diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index b51c4d6c3..81b6a46c4 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -429,7 +429,7 @@ void PreviewAutoCacher::StartCachingAudioRange(const TimeRange &range) void PreviewAutoCacher::SetPlayhead(const rational &playhead) { cache_range_ = TimeRange(playhead - OLIVE_CONFIG("DiskCacheBehind").value(), - playhead + OLIVE_CONFIG("DiskCacheAhead").value()); + playhead + OLIVE_CONFIG("DiskCacheAhead").value()); RequeueFrames(); } @@ -546,19 +546,10 @@ void PreviewAutoCacher::TryRender() if (single_frame_render_) { // Check if already caching this - rational time = single_frame_render_->property("time").value(); - RenderTicketWatcher* watcher; - if ((watcher = video_tasks_.key(time))) { - video_immediate_passthroughs_[watcher].append(single_frame_render_); - } else if ((watcher = video_download_tasks_.key(time))) { - single_frame_render_->Finish(watcher->property("frame")); - } else { - watcher = RenderFrame(single_frame_render_->property("time").value(), - RenderTicketPriority(single_frame_render_->property("priority").toInt()), - !viewer_node_->GetVideoAutoCacheEnabled()); - - video_immediate_passthroughs_[watcher].append(single_frame_render_); - } + RenderTicketWatcher *watcher = RenderFrame(single_frame_render_->property("time").value(), + RenderTicketPriority(single_frame_render_->property("priority").toInt()), + !viewer_node_->GetVideoAutoCacheEnabled()); + video_immediate_passthroughs_[watcher].append(single_frame_render_); single_frame_render_ = nullptr; } diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 90115ec03..c39ea908a 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -164,7 +164,7 @@ void RenderProcessor::Run() texture = render_ctx_->InterlaceTexture(top, bottom, GetCacheVideoParams()); } - if (ticket_->IsCancelled()) { + if (HeardCancel()) { // Finish cancelled ticket with nothing since we can't guarantee the frame we generated // is actually "complete ticket_->Finish(); @@ -208,7 +208,7 @@ void RenderProcessor::Run() ticket_->setProperty("waveform", QVariant::fromValue(vis)); } - if (ticket_->IsCancelled()) { + if (HeardCancel()) { ticket_->Finish(); } else { ticket_->Finish(QVariant::fromValue(samples)); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index f15656272..3a3ec6070 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -420,7 +420,14 @@ FramePtr ViewerWidget::DecodeCachedImage(const QString &cache_path, const QUuid void ViewerWidget::DecodeCachedImage(RenderTicketPtr ticket, const QString &cache_path, const QUuid &cache_id, const int64_t& time) { ticket->Start(); - ticket->Finish(QVariant::fromValue(DecodeCachedImage(cache_path, cache_id, time))); + + FramePtr f = DecodeCachedImage(cache_path, cache_id, time); + + if (f) { + ticket->Finish(QVariant::fromValue(f)); + } else { + ticket->Finish(); + } } bool ViewerWidget::ShouldForceWaveform() const @@ -606,14 +613,6 @@ void ViewerWidget::ReceivedAudioBufferForScrubbing() SampleBuffer samples = watcher->Get().value(); if (samples.is_allocated()) { if (samples.audio_params().channel_count() > 0) { - /* Fade code - const int kFadeSz = qMin(200, samples->sample_count()/4); - for (int i=0; itransform_volume_for_sample(i, amt); - samples->transform_volume_for_sample(samples->sample_count() - i - 1, amt); - }*/ - AudioProcessor::Buffer buf; int r = audio_processor_.Convert(samples.to_raw_ptrs().data(), samples.sample_count(), &buf); @@ -880,7 +879,7 @@ void ViewerWidget::SetColorTransform(const ColorTransform &transform, ViewerDisp QString ViewerWidget::GetCachedFilenameFromTime(const rational &time) { if (FrameExistsAtTime(time)) { - return GetConnectedNode()->video_frame_cache()->CachePathName(time); + return GetConnectedNode()->video_frame_cache()->GetValidCacheFilename(time); } return QString(); @@ -923,7 +922,7 @@ void ViewerWidget::RequestNextFrameForQueue(RenderTicketPriority priority, bool RenderTicketPtr ViewerWidget::GetFrame(const rational &t, RenderTicketPriority priority) { - QString cache_fn = GetConnectedNode()->video_frame_cache()->CachePathName(t); + QString cache_fn = GetConnectedNode()->video_frame_cache()->GetValidCacheFilename(t); if (!QFileInfo::exists(cache_fn)) { // Frame hasn't been cached, start render job @@ -1062,17 +1061,7 @@ void ViewerWidget::RendererGeneratedFrame() } } - // If the frame we received is not the most recent frame we're waiting for (i.e. if nonqueue_watchers_ - // is not empty), we discard the frame - because otherwise if frames are taking a while (i.e. - // uncached frames) it can be a little disconcerting to the user for a bunch of frames to - // slowly go by - UNLESS the time it took to make this frame was fairly short (under 100ms here), - // in which case, we DO show it because it can be disconcerting in the other direction to see - // a scrub just jump to the final frame without seeing the frames in between. It's just a - // little nicer to get to see that in that situation, so we handle both. - qint64 frame_start_time = ticket->property("start").value(); - if (nonqueue_watchers_.isEmpty() || (QDateTime::currentMSecsSinceEpoch() - frame_start_time < 100)) { - SetDisplayImage(ticket->Get()); - } + SetDisplayImage(ticket->Get()); } }